/**************************************************** *Part of SPC2IT, read readme.md for more information* ****************************************************/ #include #include #include #include #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); }