1
0
mirror of https://github.com/ScrelliCopter/VGM-Tools synced 2025-02-21 04:09:25 +11:00
Files
VGM-Tools/spctools/spc2it/it.c

547 lines
16 KiB
C

/****************************************************
*Part of SPC2IT, read readme.md for more information*
****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "it.h"
#include "sound.h"
#include "emu.h"
static itdata ITdata[8]; // Temp memory for patterns before going to file
static sndsamp *ITSamples[IT_SAMPLE_MAX];
static u8 *ITpattbuf[NUM_PATT_BUFS]; // Where patterns are going to be , before writing to file
static u8 *ITPatterns;
static u32 ITPatternsSize;
static s32 ITpattlen[NUM_PATT_BUFS]; // lengths of each pattern
static s32 ITcurbuf, ITbufpos, ITcurrow; // Pointers into temp pattern buffers
static s32 ITrows; // Number of rows per pattern
static pcm_t p1, p2;
static s32 offset[IT_PATTERN_MAX]; // table of offsets into temp file to each pattern
static s32 curpatt; // which pattern we are on in temp file
static s32 curoffs; // where we are in file
static sndsamp *ITAllocateSample(s32 size)
{
sndsamp *s;
if (((s = calloc(1, sizeof(sndsamp))) == NULL) || ((s->buf = calloc(1, size * 2)) == NULL))
return (NULL);
s->length = size;
s->loopto = -1;
s->freq = 0;
return (s);
}
static s32 ITGetBRRPrediction(u8 filter, pcm_t p1, pcm_t p2)
{
s32 p;
switch (filter)
{
case 0:
return 0;
case 1:
p = p1;
p -= p1 >> 4;
return p;
case 2:
p = p1 << 1;
p += (-(p1 + (p1 << 1))) >> 5;
p -= p2;
p += p2 >> 4;
return p;
case 3:
p = p1 << 1;
p += (-(p1 + (p1 << 2) + (p1 << 3))) >> 6;
p -= p2;
p += (p2 + (p2 << 1)) >> 4;
return p;
}
return 0;
}
static void ITDecodeSampleInternal(s8 s, u8 shift_am, u8 filter)
{
s32 a;
if (shift_am <= 0x0c) // Valid shift count
a = ((s < 8 ? s : s - 16) << shift_am) >> 1;
else
a = s < 8 ? 1 << 11 : (-1) << 11; // Values "invalid" shift counts
a += ITGetBRRPrediction(filter, p1, p2);
if (a > 0x7fff)
a = 0x7fff;
else if (a < -0x8000)
a = -0x8000;
if (a > 0x3fff)
a -= 0x8000;
else if (a < -0x4000)
a += 0x8000;
p2 = p1;
p1 = a;
}
static s32 ITDecodeSample(u16 start, sndsamp **sp)
{
sndsamp *s;
u8 *src;
u16 end;
u32 brrptr, sampptr = 0;
s32 i;
src = &SPCRAM[start];
for (end = 0; !(src[end] & 1); end += 9)
;
i = (end + 9) / 9 * 16;
*sp = s = ITAllocateSample(i);
if (s == NULL)
return 1;
if (src[end] & 2)
s->loopto = 0;
for (brrptr = 0; brrptr <= end;)
{
u8 range = src[brrptr++];
u8 filter = (range & 0x0c) >> 2;
u8 shift_amount = (range >> 4) & 0x0F;
for (i = 0; i < 8; i++, brrptr++)
{
ITDecodeSampleInternal(src[brrptr] >> 4, shift_amount, filter); // Decode high nybble
s->buf[sampptr++] = 2 * p1;
ITDecodeSampleInternal(src[brrptr] & 0x0F, shift_amount, filter); // Decode low nybble
s->buf[sampptr++] = 2 * p1;
}
}
return 0;
}
static void ITUpdateSample(s32 s)
{
s32 i;
struct
{
u16 vptr, lptr;
} *SRCDIR;
i = SPC_DSP[0x5D] << 8; //sample directory table...
SRCDIR = (void *)&SPCRAM[i];
if (ITDecodeSample(SRCDIR[s].vptr, &ITSamples[s]))
return;
if (ITSamples[s]->loopto != -1)
{
ITSamples[s]->loopto = (SRCDIR[s].lptr - SRCDIR[s].vptr) / 9 * 16;
if ((ITSamples[s]->loopto > ITSamples[s]->length) || (ITSamples[s]->loopto < 0))
ITSamples[s]->loopto = -1;
}
}
static s32 ITPitchToNote(s32 pitch, s32 base)
{
f64 tmp;
s32 note;
tmp = log2((f64)pitch / (f64)base) * 12 + 60;
if (tmp > 127)
tmp = 127;
else if (tmp < 0)
tmp = 0;
note = (s32)tmp;
if ((s32)(tmp * 2) != (note * 2))
note++; // correct rounding
return note;
}
static void ITWriteDSPCallback(u8 v)
{
s32 addr_lo = SPC_DSP_ADDR & 0xF;
s32 addr_hi = SPC_DSP_ADDR >> 4;
if (!(addr_lo == 12))
return;
if (!(addr_hi == 4))
return;
s32 i, cursamp, pitch;
v &= 0xFF; // ext
for (i = 0; i < 8; i++)
{
if (v & (1 << i))
{
cursamp = SPC_DSP[4 + (i << 4)]; // Number of current sample
if (cursamp < IT_SAMPLE_MAX) // Only 99 samples supported, sorry
{
if (ITSamples[cursamp] == NULL)
ITUpdateSample(cursamp);
pitch = (s32)(*(u16 *)&SPC_DSP[(i << 4) + 0x02]) * 7.8125; // Ext, Get pitch
if (ITSamples[cursamp]->freq == 0)
ITSamples[cursamp]->freq = pitch;
if ((pitch != 0) && (ITSamples[cursamp] != NULL) &&
(ITSamples[cursamp]->freq != 0)) // Ext, Sample is actually useful?
{
ITdata[i].mask |= IT_MASK_NOTE_SAMPLE_ADJUSTVOLUME; // Update note, sample, and adjust the volume.
ITdata[i].note = ITPitchToNote(pitch, ITSamples[cursamp]->freq); // change pitch to note
ITdata[i].pitch = (s32)(pow(2, ((f64)ITdata[i].note - 60) / 12) *
(f64)ITSamples[cursamp]->freq); // needed for pitch slide detection
ITdata[i].lvol = 0;
ITdata[i].rvol = 0;
// IT code will get sample from DSP buffer
}
}
}
}
}
static void ITSSave(sndsamp *s, FILE *f) // Save sample
{
s32 loopto = -1;
s32 length = 0;
s32 freq = 0;
s32 ofs = ftell(f);
ITFileSample *sHeader = calloc(1, sizeof(ITFileSample));
if (sHeader == NULL)
{
printf("Error: could not allocate memory for ITFileSample struct\n");
exit(1);
}
if (s != NULL)
{
loopto = s->loopto;
length = s->length;
freq = s->freq;
}
else
{
freq = 8363;
loopto = 0;
}
memcpy(sHeader->magic, "IMPS", 4);
if (length)
strcpy(sHeader->fileName, "SPC2ITSAMPLE");
sHeader->GlobalVolume = 64;
sHeader->Flags |= 2; // Bit 1 (16 bit)
if (length)
sHeader->Flags |= 1; // Bit 0 (sample included with header)
sHeader->Volume = 64;
if (length)
strcpy(sHeader->SampleName, "SPC2ITSAMPLE");
sHeader->Convert = 1;
sHeader->DefaultPan = 0;
sHeader->NumberOfSamples = length;
if (loopto != -1)
{
sHeader->Flags |= 16; // Bit 4 (Use loop)
sHeader->LoopBeginning = loopto;
sHeader->LoopEnd = length;
}
sHeader->C5Speed = freq;
if (length)
sHeader->SampleOffset = ofs + sizeof(ITFileSample);
fwrite(sHeader, sizeof(ITFileSample), 1, f);
free(sHeader);
if (length)
fwrite(s->buf, s->length * 2, 1, f); // Write the sample itself... 2x length.
}
static void ITWritePattern(ITPatternInfo *pInfo) {
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Channel;
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Mask;
if (pInfo->Mask & IT_MASK_NOTE)
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Note;
if (pInfo->Mask & IT_MASK_SAMPLE)
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Sample;
if (pInfo->Mask & IT_MASK_ADJUSTVOLUME)
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Volume;
if (pInfo->Mask & IT_MASK_PITCHSLIDE)
{
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Command;
ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->CommandValue;
}
}
s32 ITStart(s32 rows) // Opens up temporary file and inits writing
{
SPCAddWriteDSPCallback(&ITWriteDSPCallback);
s32 i;
ITrows = rows;
for (i = 0; i < NUM_PATT_BUFS; i++)
{
ITpattbuf[i] = calloc(1, Mem64k - 8); //Don't include the 8 byte header
if (ITpattbuf[i] == NULL)
{
printf("Error: could not allocate memory for IT pattern buffer\n");
exit(1);
}
ITpattlen[i] = 0;
}
ITPatterns = calloc(1, Mem64k * IT_PATTERN_MAX);
if (ITPatterns == NULL)
{
printf("Error: could not allocate memory for IT pattern storage\n");
exit(1);
}
ITPatternsSize = 0;
ITcurbuf = 0;
ITbufpos = 0;
ITcurrow = 0;
curoffs = 0;
for (i = 0; i < IT_PATTERN_MAX; i++)
offset[i] = -1; // -1 means unused pattern
curpatt = 0;
for (i = 0; i < 8; i++)
ITdata[i].mask = 0;
return 0;
}
s32 ITUpdate() // Dumps pattern buffers to file
{
u8 *tmpptr;
s32 i;
ITFilePattern *pHeader = calloc(1, sizeof(ITFilePattern));
if (pHeader == NULL)
{
printf("Error: could not allocate memory for ITFilePattern struct\n");
exit(1);
}
for (i = 0; i < ITcurbuf; i++)
{
offset[curpatt] = curoffs;
pHeader->Length = ITpattlen[i];
pHeader->Rows = ITrows;
memcpy(&ITPatterns[ITPatternsSize], pHeader, sizeof(ITFilePattern));
ITPatternsSize += sizeof(ITFilePattern);
memcpy(&ITPatterns[ITPatternsSize], ITpattbuf[i], ITpattlen[i]);
ITPatternsSize += ITpattlen[i];
curoffs += ITpattlen[i] + 8;
if (curpatt < IT_PATTERN_MAX)
curpatt++; // Continue counting if we haven't reached the limit yet
}
free(pHeader);
tmpptr = ITpattbuf[0];
ITpattbuf[0] = ITpattbuf[ITcurbuf];
ITpattbuf[ITcurbuf] = tmpptr;
ITcurbuf = 0;
return 0;
}
s32 ITWrite(char *fn) // Write the final IT file
{
FILE *f;
s32 i, t, numsamps, ofs;
ITPatternInfo *pInfo = calloc(1, sizeof(ITPatternInfo));
if (pInfo == NULL)
{
printf("Error: could not allocate memory for ITPatternInfo struct\n");
exit(1);
}
// START IT CLEANUP
if (fn == NULL)
{
printf("Error: no IT filename\n");
exit(1);
}
pInfo->Mask = 1;
pInfo->Note = 254; //note cut
// Stop all notes and loop back to the beginning
for (i = 0; i < 15; i++) // Save the last channel to put loop in
{
pInfo->Channel = (i + 1) | 128; // Channels are 1 based (Channels start at 1, not 0, ITTECH.TXT is WRONG) !!!
ITWritePattern(pInfo);
}
pInfo->Channel = (15 + 1) | 128;
pInfo->Mask = 9; // 1001 (note, special command)
pInfo->Command = 2; // Effect B: jump to...
pInfo->CommandValue = 0; //...order 0 (Loop to beginning)
ITWritePattern(pInfo);
free(pInfo);
while (ITcurrow++ < ITrows)
ITpattbuf[ITcurbuf][ITbufpos++] = 0; // end-of-row
ITpattlen[ITcurbuf++] = ITbufpos;
ITUpdate(); // Save the changes we just made
// END IT CLEANUP
f = fopen(fn, "wb");
if (f == NULL)
{
printf("Error: could not open IT file\n");
exit(1);
}
ITFileHeader *fHeader = calloc(1, sizeof(ITFileHeader));
if (fHeader == NULL)
{
printf("Error: could not allocate memory for ITFileHeader struct\n");
exit(1);
}
memcpy(fHeader->magic, "IMPM", 4);
if (SPCInfo->SongTitle[0])
strncpy(fHeader->songName, SPCInfo->SongTitle, 25);
else
strcpy(fHeader->songName, "spc2it conversion"); // default string
fHeader->OrderNumber = curpatt + 1; // number of orders + terminating order
for (numsamps = IT_SAMPLE_MAX; ITSamples[numsamps - 1] == NULL; numsamps--)
; // Count the number of samples (the reason of the minus one is because c arrays start at 0)
numsamps++;
fHeader->SampleNumber = numsamps; // Number of samples
fHeader->PatternNumber = curpatt; // Number of patterns
fHeader->TrackerCreatorVersion = 0xDAEB; // Created with this tracker version
fHeader->TrackerFormatVersion = 0x200; // Compatible with this tracker version
fHeader->Flags = 9; // Flags: Stereo, Linear Slides
fHeader->GlobalVolume = 128; // Global volume
fHeader->MixVolume = 100; // Mix volume
fHeader->InitialSpeed = 1; // Initial speed (fastest)
fHeader->InitialTempo = (u8)(SPCUpdateRate * 2.5); // Initial tempo (determined by update rate)
fHeader->PanningSeperation = 128; // Stereo separation (max)
for (i = 0; i < 8; i++)
fHeader->ChannelPan[i] = 0; // Channel pan: Set 8 channels to left
for (i = 8; i < 16; i++)
fHeader->ChannelPan[i] = 64; // Set 8 channels to right
for (i = 16; i < 64; i++)
fHeader->ChannelPan[i] = 128; // Disable the rest of the channels (Value: +128)
for (i = 0; i < 16; i++)
fHeader->ChannelVolume[i] = 64; // Channel Vol: set 16 channels loud
fwrite(fHeader, sizeof(ITFileHeader), 1, f);
free(fHeader);
// orders
for (i = 0; i < curpatt; i++)
fputc(i, f); // Write from 0 to the number of patterns (max: 0xFD)
fputc(255, f); // terminating order
// Sample offsets
ofs = sizeof(ITFileHeader) + (curpatt + 1) + ((numsamps * sizeof(s32)) + (curpatt * sizeof(s32)));
for (i = 0; i < numsamps; i++)
{
fwrite(&ofs, sizeof(s32), 1, f);
ofs += sizeof(ITFileSample);
if (ITSamples[i] != NULL) // Sample is going to be put in file? Add the length of the sample.
ofs += (ITSamples[i]->length * 2);
}
// Pattern offsets
for (i = 0; i < curpatt; i++)
{
t = offset[i] + ofs;
fwrite(&t, sizeof(s32), 1, f);
}
// samples
for (i = 0; i < numsamps; i++)
ITSSave(ITSamples[i], f);
// patterns
fwrite(ITPatterns, ITPatternsSize, 1, f);
for (i = 0; i < NUM_PATT_BUFS; i++)
free(ITpattbuf[i]);
free(ITPatterns);
fclose(f);
return 0;
}
void ITMix()
{
s32 envx, pitchslide, lvol = 0, rvol = 0, pitch, temp = 0, voice;
ITPatternInfo *pInfo = calloc(1, sizeof(ITPatternInfo));
if (pInfo == NULL)
{
printf("Error: could not allocate memory for ITPatternInfo struct\n");
exit(1);
}
u8 mastervolume = SPC_DSP[0x0C];
for (voice = 0; voice < 8; voice++)
{
if ((SPC_DSP[0x4C] & (1 << voice))) // 0x4C == key on
{
envx = SNDDoEnv(voice);
lvol = (envx >> 24) * (s32)((s8)SPC_DSP[(voice << 4) ]) * mastervolume >> 14; // Ext
rvol = (envx >> 24) * (s32)((s8)SPC_DSP[(voice << 4) + 0x01]) * mastervolume >> 14; // Ext
// Volume no echo: (s32)((s8)SPC_DSP[(voice << 4) ]) * mastervolume >> 7;
pitch = (s32)(*(u16 *)&SPC_DSP[(voice << 4) + 0x02]) * 7.8125; // This code merges 2 numbers together, high and low 8 bits, to make 16 bits.
// adjust for negative volumes
if (lvol < 0)
lvol = -lvol;
if (rvol < 0)
rvol = -rvol;
// lets see if we need to pitch slide
if (pitch && ITdata[voice].pitch)
{
pitchslide = (s32)(log2((f64)pitch / (f64)ITdata[voice].pitch) * 768.0);
if (pitchslide)
ITdata[voice].mask |= IT_MASK_PITCHSLIDE; // enable pitch slide
}
// adjust volume?
if ((lvol != ITdata[voice].lvol) || (rvol != ITdata[voice].rvol))
{
ITdata[voice].mask |= IT_MASK_ADJUSTVOLUME; // Enable adjust volume
ITdata[voice].lvol = lvol;
ITdata[voice].rvol = rvol;
}
}
pInfo->Channel = (voice + 1) | 128; //Channels here are 1 based!
pInfo->Mask = ITdata[voice].mask;
if (ITdata[voice].mask & IT_MASK_NOTE)
pInfo->Note = ITdata[voice].note;
if (ITdata[voice].mask & IT_MASK_SAMPLE)
pInfo->Sample = SPC_DSP[(voice << 4) + 4] + 1;
if (ITdata[voice].mask & IT_MASK_ADJUSTVOLUME)
pInfo->Volume = (lvol > 64) ? 64 : lvol;
if (ITdata[voice].mask & IT_MASK_PITCHSLIDE)
{
if (pitchslide > 0xF)
{
temp = pitchslide >> 2;
if (temp > 0xF)
temp = 0xF;
temp |= FINE_SLIDE;
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)((temp & 0xF) << 2) / 768.0));
pInfo->Command = EFFECT_F;
pInfo->CommandValue = temp;
}
else if (pitchslide > 0)
{
temp = pitchslide | EXTRA_FINE_SLIDE;
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)(temp & 0xF) / 768.0));
pInfo->Command = EFFECT_F;
pInfo->CommandValue = temp;
}
else if (pitchslide > -0x10)
{
temp = (-pitchslide) | EXTRA_FINE_SLIDE;
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)(temp & 0xF) / -768.0));
pInfo->Command = EFFECT_E;
pInfo->CommandValue = temp;
}
else
{
temp = (-pitchslide) >> 2;
if (temp > 0xF)
temp = 0xF;
temp |= FINE_SLIDE;
ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)((temp & 0xF) << 2) / -768.0));
pInfo->Command = EFFECT_E;
pInfo->CommandValue = temp;
}
}
ITWritePattern(pInfo); // Write for left channel
pInfo->Channel = (voice + 8 + 1) | 128;
if (ITdata[voice].mask & IT_MASK_ADJUSTVOLUME)
pInfo->Volume = (rvol > 64) ? 64 : rvol;
ITWritePattern(pInfo); // Write for right channel
ITdata[voice].mask = 0; // Clear the mask
}
ITpattbuf[ITcurbuf][ITbufpos++] = 0; // End-of-row
if (++ITcurrow >= ITrows)
{
ITpattlen[ITcurbuf++] = ITbufpos;
ITbufpos = 0; // Reset buffer pos
ITcurrow = 0; // Reset current row
}
free(pInfo);
}