/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id: $ * * MOD Codec for rockbox * * Written from scratch by Rainer Sinsch * exclusivly for Rockbox in February 2008 * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /************** * This version is quite experimental and supports modules up to 486kbyte, only! * The reason for this is due to internal rockbox memory management *************/ #include "debug.h" #include "codeclib.h" #include #include #include #include #include CODEC_HEADER #define CHUNK_SIZE (1024*2) /* This codec supports MOD Files: * */ static int32_t samples[CHUNK_SIZE] IBSS_ATTR; /* The sample buffer */ static unsigned char modfile[486*1024]; /* Static buffer for module */ // Instrument Data struct TInstrument { //char szDescription[22]; // Sample name / description unsigned short nLength; // Sample length in bytes signed char nFineTune; // Sample finetuning (-8 - +7) signed char nVolume; // Sample volume (0 - 64) unsigned short nRepeatOffset; // Sample Repeat Position unsigned short nRepeatLength; // Sample Repeat Length unsigned int nSampleDataOffset; // Offset to sample data }; // Song Data struct TSong { //char szTitle[20]; // Song name / title description unsigned char nNoOfChannels; // No. of channels in song unsigned char nNoOfInstruments; // No. of instruments used (either 15 or 31) unsigned char nSongLength; // How many patterns are beeing played? unsigned char nSongEndJumpPosition; // Where to jump after the song end? unsigned char *pPatternOrderTable; // Pointer to the Pattern Order Table void *pPatternData; // Pointer to the pattern data signed char *pSampleData; struct TInstrument Instrument[31]; // Instrument data }; struct TMODChannel { signed char nVolume; // Current Volume unsigned short nPeriodTableOffset; // Current Offset to period in PeriodTable of note beeing played unsigned short nPeriod; // Current Period beeing played unsigned char nEffect; // Current effect unsigned char nEffectParameter; // Current parameters of effect unsigned char nInstrument; // Current Instrument beeing played unsigned char nVibratoSpeed; // Current Vibrato Speed unsigned char nVibratoDepth; // Current Vibrato Depth unsigned char nVibratoSinPos; // Current Position for Vibrato in SinTable unsigned char nTremoloSpeed; // Current Tremolo Speed unsigned char nTremoloDepth; // Current Tremolo Depth unsigned char nTremoloSinPos; // Current Position for Tremolo in SinTable unsigned char nSlideUpSpeed; // Current Speed of Effect "Slide Note up" unsigned char nSlideDownSpeed; // Current Speed of Effect "Slide Note down" unsigned char nSlideToNoteSpeed; // Current Speed of the "Slide to Note" effect unsigned short nSlideToNotePeriod; // Current Period of the "Slide to Note" effect }; struct TMODPlayer { unsigned char nTicksPerLine; // Ticks per Line unsigned char nBPM; // Beats per Minute unsigned char nPatternTablePosition; // Position of the Song in the Pattern Table (0-127) char nCurrentLine; // Current Line char nCurrentTick; // Current Tick unsigned int nSamplesPerTick; // How many samples are required to calculate for each tick? struct TMODChannel MODChannel[8]; // Information about the channels unsigned short periodTable[37*8+1]; // The Amiga Period Table (+1 because we use index 0 for period 0, = no new note) signed short sinTable[0x40]; // The sinus table [-255,255] bool bGlissandoEnabled; // Is the glissando effect enabled? (0 = off, else on) bool bAmigaFilterEnabled; // Is the Amiga Filter enabled? unsigned char nLoopStartLine; // The pattern-line where the loop is carried out (set with e6 command) unsigned char nLoopTimes; // Number of times to loop }; struct TChannel { unsigned char nPanning; // Panning (0 = left, 16 = right) unsigned short nFrequency; unsigned int nSamplePos; // Position of the sample currently played unsigned int nSampleFractPos; // Fractual Position of the sample currently playerd bool bLoopSample; // Loop Sample unsigned int nLoopStart; // Loop Position Start unsigned int nLoopEnd; // Loop Position End bool bChannelActive; // Is The Channel beeing played? signed char nVolume; // The Volume (0..64) signed short iLastSampleData; // The last sampledata beeing played (required for interpolation) }; struct TMixer { struct TChannel Channel[32]; // The Channels }; struct TSong MODSong IDATA_ATTR; // The Song struct TMODPlayer MODPlayer IDATA_ATTR; // The Module Player struct TMixer Mixer IDATA_ATTR; const unsigned short nMixingRate = 44100; STATICIRAM void MixerPlaySample(int nChannel, int nInstrument) ICODE_ATTR; void MixerPlaySample(int nChannel, int nInstrument) { struct TChannel *pChannel = &Mixer.Channel[nChannel]; struct TInstrument *pInstrument = &MODSong.Instrument[nInstrument]; pChannel->bChannelActive = true; pChannel->nSamplePos = pInstrument->nSampleDataOffset; pChannel->nSampleFractPos = 0; pChannel->bLoopSample = (pInstrument->nRepeatLength > 2) ? true : false; if (pChannel->bLoopSample) { pChannel->nLoopStart = pInstrument->nRepeatOffset + pInstrument->nSampleDataOffset; pChannel->nLoopEnd = pChannel->nLoopStart + pInstrument->nRepeatLength; } else pChannel->nLoopEnd = pInstrument->nLength + pInstrument->nSampleDataOffset; // Remember the instrument MODPlayer.MODChannel[nChannel].nInstrument = nInstrument; } inline void MixerStopSample(int nChannel) { Mixer.Channel[nChannel].bChannelActive = false; } inline void MixerSetVolume(int nChannel, int nVolume) { Mixer.Channel[nChannel].nVolume = nVolume; } inline void MixerSetPanning(int nChannel, int nPanning) { Mixer.Channel[nChannel].nPanning = nPanning; } inline void MixerSetAmigaPeriod(int nChannel, int nAmigaPeriod) { Mixer.Channel[nChannel].nFrequency = 3579546 / nAmigaPeriod; } // Initialize the MOD Player with default values and precalc tables STATICIRAM void InitMODPlayer(void) ICODE_ATTR; void InitMODPlayer(void) { unsigned int i,c; // Calculate Amiga Period Values // Start with Period 907 (= C-1 with Finetune -8) and work upwards double f = 907.0f; MODPlayer.periodTable[0] = 0; // Index 0 stands for no note (and therefore no period) for (i=1;i<297;i++) { MODPlayer.periodTable[i] = (unsigned short) f; f /= 1.0072464122237039; // = pow(2.0f, 1.0f/(12.0f*8.0f)); } // Calculate Protracker Vibrato sine table // The routine makes use of the Harmonical Oscillator Approach for calculating sine tables // (see http://membres.lycos.fr/amycoders/tutorials/sintables.html) // The routine presented here calculates a complete sine wave with 64 values in range [-255,255] float a, b, d, dd; d = 0.09817475f; // = 2*PI/64 dd = d*d; a = 0; b = d; for (i=0;i<0x40;i++) { MODPlayer.sinTable[i] = (int)(255*a); a = a+b; b = b-dd*a; } // Set Default Player Values MODPlayer.nCurrentLine = 0; MODPlayer.nCurrentTick = 0; MODPlayer.nPatternTablePosition = 0; MODPlayer.nBPM = 125; MODPlayer.nTicksPerLine = 6; MODPlayer.bGlissandoEnabled = false; // Disable glissando MODPlayer.bAmigaFilterEnabled = false; // Disable the Amiga Filter // Default Panning Values int nPanningValues[8] = {4,12,12,4,4,12,12,4}; for (c=0;c<8;c++) { // Set Default Panning MixerSetPanning(c, nPanningValues[c]); // Reset Channels in the MOD Player memset(&MODPlayer.MODChannel[c], 0, sizeof(struct TMODChannel)); // Don't play anything Mixer.Channel[c].bChannelActive = false; } } // Load the MOD File from memory STATICIRAM bool LoadMOD(void *pMODFile) ICODE_ATTR; bool LoadMOD(void *pMODFile) { int i; // We don't support PowerPacker 2.0 Files if (memcmp((char*) pMODFile, "PP20", 4) == 0) return false; // Get the File Format Tag char *pFileFormatTag = (char*)pMODFile + 1080; // Find out how many channels and instruments are used if (memcmp(pFileFormatTag, "2CHN", 4) == 0) {MODSong.nNoOfChannels = 2; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "M.K.", 4) == 0) {MODSong.nNoOfChannels = 4; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "M!K!", 4) == 0) {MODSong.nNoOfChannels = 4; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "4CHN", 4) == 0) {MODSong.nNoOfChannels = 4; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "FLT4", 4) == 0) {MODSong.nNoOfChannels = 4; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "6CHN", 4) == 0) {MODSong.nNoOfChannels = 6; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "8CHN", 4) == 0) {MODSong.nNoOfChannels = 8; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "OKTA", 4) == 0) {MODSong.nNoOfChannels = 8; MODSong.nNoOfInstruments = 31;} else if (memcmp(pFileFormatTag, "CD81", 4) == 0) {MODSong.nNoOfChannels = 8; MODSong.nNoOfInstruments = 31;} else { // The file has no format tag, so we take a guess MODSong.nNoOfChannels = 4; // For the no of instruments we check if there is a sample #16 and // if it has a (ascii) name char *p = (char *)pMODFile + 470; if ((*p >= 32) && (*p <= 126)) MODSong.nNoOfInstruments = 31; else MODSong.nNoOfInstruments = 15; } // Get the Song title // Skipped here // strncpy(MODSong.szTitle, (char*)pMODFile, 20); // Get the Instrument information for (i=0;iszDescription, (char*)p, 22); p += 22; pInstrument->nLength = (((p[0])<<8) + p[1]) << 1; p+=2; pInstrument->nFineTune = *p++ & 0x0f; // Treat finetuning as signed nibble if (pInstrument->nFineTune > 7) pInstrument->nFineTune -= 16; pInstrument->nVolume = *p++; pInstrument->nRepeatOffset = (((p[0])<<8) + p[1]) << 1; p+= 2; pInstrument->nRepeatLength = (((p[0])<<8) + p[1]) << 1; } // Get the pattern information unsigned char *p = (unsigned char *)pMODFile + 20 + MODSong.nNoOfInstruments*30; MODSong.nSongLength = *p++; MODSong.nSongEndJumpPosition = *p++; MODSong.pPatternOrderTable = p; // Find out how many patterns are used within this song int nMaxPatterns = 0; for (i=0;i<128;i++) if (MODSong.pPatternOrderTable[i] > nMaxPatterns) nMaxPatterns = MODSong.pPatternOrderTable[i]; nMaxPatterns++; // Get the pattern data MODSong.pPatternData = (char*)pMODFile + 20 + MODSong.nNoOfInstruments*30 + 134; // Convert the period values in the mod file to offsets in our periodtable p = (unsigned char *) MODSong.pPatternData; int nNote, nNote2, nChannel; for (nNote=0;nNote> 8) | (p[0] & 0xf0); p[1] = nPeriodOffset & 0xff; p += 4; } // Get the samples // Calculation: The Samples come after the pattern data // We know that there are nMaxPatterns and each pattern requires // 4 bytes per note and per channel. And of course there are always lines in each channel MODSong.pSampleData = (signed char*) MODSong.pPatternData + nMaxPatterns*4*MODSong.nNoOfChannels*64; int nSampleDataOffset = 0; for (i=0;i> 7 is used in the original protracker source code MixerSetAmigaPeriod(nChannel, pMODChannel->nPeriod+ ((pMODChannel->nVibratoDepth * MODPlayer.sinTable[pMODChannel->nVibratoSinPos])>>7)); // Foward in Sine Table pMODChannel->nVibratoSinPos += pMODChannel->nVibratoSpeed; pMODChannel->nVibratoSinPos &= 0x3f; } // Apply tremolo to channel (same as vibrato, but only apply on volume instead of pitch) STATICIRAM void Tremolo(int nChannel) ICODE_ATTR; void Tremolo(int nChannel) { struct TMODChannel *pMODChannel = &MODPlayer.MODChannel[nChannel]; // Apply Tremolo // >> 6 is used in the original protracker source code int nVolume = (pMODChannel->nVolume * MODPlayer.sinTable[pMODChannel->nTremoloSinPos])>>6; if (nVolume > 64) nVolume = 64; else if (nVolume < 0) nVolume = 0; MixerSetVolume(nChannel, nVolume); // Foward in Sine Table pMODChannel->nTremoloSinPos += pMODChannel->nTremoloSinPos; pMODChannel->nTremoloSinPos &= 0x3f; } // Apply Slide to Note effect to channel STATICIRAM void SlideToNote(int nChannel) ICODE_ATTR; void SlideToNote(int nChannel) { struct TMODChannel *pMODChannel = &MODPlayer.MODChannel[nChannel]; // Slide note up if (pMODChannel->nSlideToNotePeriod > pMODChannel->nPeriod) { pMODChannel->nPeriod += pMODChannel->nSlideToNoteSpeed; if (pMODChannel->nPeriod > pMODChannel->nSlideToNotePeriod) pMODChannel->nPeriod = pMODChannel->nSlideToNotePeriod; } // Slide note down else if (pMODChannel->nSlideToNotePeriod < pMODChannel->nPeriod) { pMODChannel->nPeriod -= pMODChannel->nSlideToNoteSpeed; if (pMODChannel->nPeriod < pMODChannel->nSlideToNotePeriod) pMODChannel->nPeriod = pMODChannel->nSlideToNotePeriod; } MixerSetAmigaPeriod(nChannel, pMODChannel->nPeriod); } // Apply Slide to Note effect on channel, but this time with glissando enabled STATICIRAM void SlideToNoteGlissando(int nChannel) ICODE_ATTR; void SlideToNoteGlissando(int nChannel) { struct TMODChannel *pMODChannel = &MODPlayer.MODChannel[nChannel]; // Slide note up if (pMODChannel->nSlideToNotePeriod > pMODChannel->nPeriod) { pMODChannel->nPeriod = MODPlayer.periodTable[pMODChannel->nPeriodTableOffset+=8]; if (pMODChannel->nPeriod > pMODChannel->nSlideToNotePeriod) pMODChannel->nPeriod = pMODChannel->nSlideToNotePeriod; } // Slide note down else { pMODChannel->nPeriod = MODPlayer.periodTable[pMODChannel->nPeriodTableOffset-=8]; if (pMODChannel->nPeriod < pMODChannel->nSlideToNotePeriod) pMODChannel->nPeriod = pMODChannel->nSlideToNotePeriod; } MixerSetAmigaPeriod(nChannel, pMODChannel->nPeriod); } // Apply Volume Slide STATICIRAM void VolumeSlide(int nChannel, int nEffectX, int nEffectY) ICODE_ATTR; void VolumeSlide(int nChannel, int nEffectX, int nEffectY) { struct TMODChannel *pMODChannel = &MODPlayer.MODChannel[nChannel]; // If both X and Y Parameters are non-zero, then the y value is ignored if (nEffectX > 0) { pMODChannel->nVolume += nEffectX; if (pMODChannel->nVolume > 64) pMODChannel->nVolume = 64; } else { pMODChannel->nVolume -= nEffectY; if (pMODChannel->nVolume < 0) pMODChannel->nVolume = 0; } MixerSetVolume(nChannel, pMODChannel->nVolume); } // Play the current line (at tick 0) STATICIRAM void PlayLine(int nPattern, int nLine) ICODE_ATTR; void PlayLine(int nPattern, int nLine) { int c; // Get pointer to the current pattern unsigned char *pLine = (unsigned char*)MODSong.pPatternData; pLine += nPattern*64*4*MODSong.nNoOfChannels; pLine += nLine*4*MODSong.nNoOfChannels; // Only allow one Patternbreak Commando per Line bool bPatternBreakDone = false; for (c=0;c> 4); unsigned short nPeriodTableOffset = ((pNote[0] & 0x0f) << 8) | pNote[1]; pMODChannel->nEffect = pNote[2] & 0x0f; pMODChannel->nEffectParameter = pNote[3]; // Remember Instrument and set Volume if new Instrument triggered if (nSampleNumber > 0) { // And trigger new sample, if new instrument was set if (nSampleNumber-1 != pMODChannel->nInstrument) { // Advance the new sample to the same offset the old sample was beeing played int nOldSampleOffset = Mixer.Channel[c].nSamplePos - MODSong.Instrument[pMODChannel->nInstrument].nSampleDataOffset; MixerPlaySample(c, nSampleNumber-1); Mixer.Channel[c].nSamplePos += nOldSampleOffset; } // Remember last played instrument on channel pMODChannel->nInstrument = nSampleNumber-1; // Set Volume to standard instrument volume, if not overwritten by volume effect if (pMODChannel->nEffect != 0x0c) { pMODChannel->nVolume = MODSong.Instrument[pMODChannel->nInstrument].nVolume; MixerSetVolume(c, pMODChannel->nVolume); } } // Trigger new sample if note available if (nPeriodTableOffset > 0) { // Restart instrument only when new sample triggered if (nSampleNumber != 0) MixerPlaySample(c, (nSampleNumber > 0) ? nSampleNumber-1 : pMODChannel->nInstrument); // Set the new amiga period (but only, if there is no slide to note effect) if (pMODChannel->nEffect != 0x3) { // Apply finetuning to sample pMODChannel->nPeriodTableOffset = nPeriodTableOffset + MODSong.Instrument[pMODChannel->nInstrument].nFineTune; pMODChannel->nPeriod = MODPlayer.periodTable[nPeriodTableOffset]; MixerSetAmigaPeriod(c, pMODChannel->nPeriod); } } int nEffectX = pMODChannel->nEffectParameter>>4; int nEffectY = pMODChannel->nEffectParameter&0x0f; switch (pMODChannel->nEffect) { // Slide up (Portamento up) case 0x01: if (pMODChannel->nEffectParameter > 0) pMODChannel->nSlideUpSpeed = pMODChannel->nEffectParameter; break; // Slide down (Portamento down) case 0x02: if (pMODChannel->nEffectParameter > 0) pMODChannel->nSlideDownSpeed = pMODChannel->nEffectParameter; break; // Slide to Note case 0x03: if (pMODChannel->nEffectParameter > 0) pMODChannel->nSlideToNoteSpeed = pMODChannel->nEffectParameter; // Get the slide to note directly from the pattern buffer if (nPeriodTableOffset > 0) pMODChannel->nSlideToNotePeriod = MODPlayer.periodTable[nPeriodTableOffset + MODSong.Instrument[pMODChannel->nInstrument].nFineTune]; // If glissando is enabled apply the effect directly here if (MODPlayer.bGlissandoEnabled) SlideToNoteGlissando(c); break; // Set Vibrato case 0x04: if (nEffectX > 0) pMODChannel->nVibratoSpeed = nEffectX; if (nEffectY > 0) pMODChannel->nVibratoDepth = nEffectY; break; // Effect 0x05 and 0x06 are "Continue Effects" // They are not processed on tick 0 // Set Tremolo case 0x07: if (nEffectX > 0) pMODChannel->nTremoloDepth = nEffectX; if (nEffectY > 0) pMODChannel->nTremoloSpeed = nEffectY; break; // Set fine panning case 0x08: // Internal panning goes from 0..15 // Scale the fine panning value to that range Mixer.Channel[c].nPanning = pMODChannel->nEffectParameter>>4; break; // Set Sample Offset case 0x09: { struct TInstrument *pInstrument = &MODSong.Instrument[pMODChannel->nInstrument]; int nSampleOffset = pInstrument->nSampleDataOffset; if (nSampleOffset > pInstrument->nLength) nSampleOffset = pInstrument->nLength; // Forward the new offset to the mixer Mixer.Channel[c].nSamplePos = pInstrument->nSampleDataOffset + (pMODChannel->nEffectParameter<<8); Mixer.Channel[c].nSampleFractPos = 0; break; } // Effect 0x0a (Volume slide) is not processed on tick 0 // Position Jump case 0x0b: MODPlayer.nCurrentLine = -1; MODPlayer.nPatternTablePosition = (nEffectX<<4)+nEffectY; break; // Set Volume case 0x0c: pMODChannel->nVolume = pMODChannel->nEffectParameter; MixerSetVolume(c, pMODChannel->nVolume); break; // Pattern break case 0x0d: MODPlayer.nCurrentLine = nEffectX*10 + nEffectY - 1; if (!bPatternBreakDone) { bPatternBreakDone = true; MODPlayer.nPatternTablePosition++; } break; // Extended Effects case 0x0e: switch (nEffectX) { // Set Filter case 0x0: MODPlayer.bAmigaFilterEnabled = (nEffectY>0) ? false : true; break; // Fineslide up case 0x1: MixerSetAmigaPeriod(c, pMODChannel->nPeriod -= nEffectY); if (pMODChannel->nPeriod < MODPlayer.periodTable[37*8]) pMODChannel->nPeriod = 100; // Find out the new offset in the period table if (pMODChannel->nPeriodTableOffset < 36*8) while (MODPlayer.periodTable[pMODChannel->nPeriodTableOffset+8] >= pMODChannel->nPeriod) pMODChannel->nPeriodTableOffset+=8; break; // Fineslide down case 0x2: MixerSetAmigaPeriod(c, pMODChannel->nPeriod += nEffectY); if (pMODChannel->nPeriodTableOffset > 8) while (MODPlayer.periodTable[pMODChannel->nPeriodTableOffset-8] <= pMODChannel->nPeriod) pMODChannel->nPeriodTableOffset-=8; break; // Set glissando on/off case 0x3: MODPlayer.bGlissandoEnabled = nEffectY; break; // Set Vibrato waveform case 0x4: // Currently not implemented break; // Set Finetune value case 0x5: if (nEffectY > 7) nEffectY -= 16; // Treat as signed nibble pMODChannel->nPeriodTableOffset += nEffectY - MODSong.Instrument[pMODChannel->nInstrument].nFineTune; pMODChannel->nPeriod = MODPlayer.periodTable[pMODChannel->nPeriodTableOffset]; MODSong.Instrument[pMODChannel->nInstrument].nFineTune = nEffectY; break; // Pattern loop case 0x6: if (nEffectY == 0) MODPlayer.nLoopStartLine = nLine; else { if (MODPlayer.nLoopTimes == 0) MODPlayer.nCurrentLine = MODPlayer.nLoopStartLine-1; else MODPlayer.nLoopTimes--; } break; // Set Tremolo waveform case 0x7: // Not yet implemented break; // Enhanced Effect 8 is not used case 0x8: break; // Retrigger sample case 0x9: // Only processed on subsequent ticks break; // Fine volume slide up case 0xa: pMODChannel->nVolume += nEffectY; if (pMODChannel->nVolume > 64) pMODChannel->nVolume = 64; MixerSetVolume(c, pMODChannel->nVolume); break; // Fine volume slide down case 0xb: pMODChannel->nVolume -= nEffectY; if (pMODChannel->nVolume < 0) pMODChannel->nVolume = 0; MixerSetVolume(c, pMODChannel->nVolume); break; // Cut sample case 0xc: // Only processed on subsequent ticks break; // Note delay (Usage: $ED + ticks to delay note.) case 0xd: // We stop the sample here on tick 0 and restart it later in the effect if (nEffectY > 0) Mixer.Channel[c].bChannelActive = false; break; } break; // Set Speed case 0x0f: if (pMODChannel->nEffectParameter < 32) MODPlayer.nTicksPerLine = pMODChannel->nEffectParameter; else MODPlayer.nBPM = pMODChannel->nEffectParameter; break; } } } // Play the current effect of the note (ticks 1..speed) STATICIRAM void PlayEffect(int nCurrentTick) ICODE_ATTR; void PlayEffect(int nCurrentTick) { int c; for (c=0;cnPeriod == 0) continue; unsigned char nEffectX = pMODChannel->nEffectParameter>>4; unsigned char nEffectY = pMODChannel->nEffectParameter&0x0f; switch (pMODChannel->nEffect) { // Effect 0: Arpeggio case 0x00: if (pMODChannel->nEffectParameter > 0) { unsigned short nNewPeriodTableOffset; switch (nCurrentTick % 3) { case 0: MixerSetAmigaPeriod(c, MODPlayer.periodTable[pMODChannel->nPeriodTableOffset]); break; case 1: nNewPeriodTableOffset = pMODChannel->nPeriodTableOffset+(nEffectX<<3); if (nNewPeriodTableOffset < 37*8) MixerSetAmigaPeriod(c, MODPlayer.periodTable[nNewPeriodTableOffset]); break; case 2: nNewPeriodTableOffset = pMODChannel->nPeriodTableOffset+(nEffectY<<3); if (nNewPeriodTableOffset < 37*8) MixerSetAmigaPeriod(c, MODPlayer.periodTable[nNewPeriodTableOffset]); break; } } break; // Effect 1: Slide Up case 0x01: MixerSetAmigaPeriod(c, pMODChannel->nPeriod -= pMODChannel->nSlideUpSpeed); // Find out the new offset in the period table if (pMODChannel->nPeriodTableOffset < 36*8) while (MODPlayer.periodTable[pMODChannel->nPeriodTableOffset+8] >= pMODChannel->nPeriod) pMODChannel->nPeriodTableOffset+=8; break; // Effect 2: Slide Down case 0x02: MixerSetAmigaPeriod(c, pMODChannel->nPeriod += pMODChannel->nSlideDownSpeed); // Find out the new offset in the period table if (pMODChannel->nPeriodTableOffset > 8) while (MODPlayer.periodTable[pMODChannel->nPeriodTableOffset-8] <= pMODChannel->nPeriod) pMODChannel->nPeriodTableOffset-=8; break; // Effect 3: Slide to Note case 0x03: // Apply smooth sliding, if no glissando is enabled if (MODPlayer.bGlissandoEnabled == 0) SlideToNote(c); break; // Effect 4: Vibrato case 0x04: Vibrate(c); break; // Effect 5: Continue effect 3:'Slide to note', but also do Volume slide case 0x05: SlideToNote(c); VolumeSlide(c, nEffectX, nEffectY); break; // Effect 6: Continue effect 4:'Vibrato', but also do Volume slide case 0x06: Vibrate(c); VolumeSlide(c, nEffectX, nEffectY); break; // Effect 7: Tremolo case 0x07: Tremolo(c); break; // Effect 8 (Set fine panning) is only processed at tick 0 // Effect 9 (Set sample offset) is only processed at tick 0 // Effect A: Volume slide case 0x0a: VolumeSlide(c, nEffectX, nEffectY); break; // Effect B (Position jump) is only processed at tick 0 // Effect C (Set Volume) is only processed at tick 0 // Effect D (Pattern Preak) is only processed at tick 0 // Effect E (Enhanced Effect) case 0x0e: switch (nEffectX) { // Retrigger sample ($E9 + Tick to Retrig note at) case 0x9: // Don't device by zero if (nEffectY == 0) nEffectY = 1; // Apply retrig if (nCurrentTick % nEffectY == 0) MixerPlaySample(c, pMODChannel->nInstrument); break; // Cut note (Usage: $EC + Tick to Cut note at) case 0xc: if (nCurrentTick == nEffectY) MixerStopSample(c); break; // Delay note (Usage: $ED + ticks to delay note) case 0xd: // If this is the correct tick, we start playing the sample now if (nCurrentTick == nEffectY) Mixer.Channel[c].bChannelActive = true; break; } break; // Effect F (Set Speed) is only processed at tick 0 } } } static inline int Clip(int i) { if (i > 32767) return(32767); else if (i < -32768) return(-32768); else return(i); } STATICIRAM void SynthRender(void *pRenderBuffer, int iSampleCount) ICODE_ATTR; static void SynthRender(void *pRenderBuffer, int iSampleCount) { // 125bpm entspricht 50Hz (= 0.02s) // => ein tick = MixingRate/50, bzw. // samples die fr einen tick vergehen: MixingRate/(bpm/2.5) = 2.5*MixingRate/bpm int *pLeft = (int *) pRenderBuffer; // int in rockbox int *pRight = pLeft+1; signed short s; int qfDistance, qfDistance2; int i; int c, Left, Right; for (i=0;i= MODPlayer.nTicksPerLine) { MODPlayer.nCurrentLine++; MODPlayer.nCurrentTick = 0; if (MODPlayer.nCurrentLine == 64) { MODPlayer.nPatternTablePosition++; if (MODPlayer.nPatternTablePosition >= MODSong.nSongLength) // This is for Noise Tracker // MODPlayer.nPatternTablePosition=MODSong.nSongEndJumpPosition; // More compatible approach is restart from 0 MODPlayer.nPatternTablePosition=0; MODPlayer.nCurrentLine = 0; } } MODPlayer.nSamplesPerTick = (20*nMixingRate/MODPlayer.nBPM)>>3; } // Mix buffers from here // Walk through all channels Left=0, Right=0; // If song has not stopped playing if (MODPlayer.nPatternTablePosition < 127) // Loop through all Channels for (c=0;c= Mixer.Channel[c].nLoopEnd) { if (Mixer.Channel[c].bLoopSample) Mixer.Channel[c].nSamplePos -= (Mixer.Channel[c].nLoopEnd-Mixer.Channel[c].nLoopStart); else Mixer.Channel[c].bChannelActive = false; } // If the sample has stopped playing don't mix it if (!Mixer.Channel[c].bChannelActive) continue; // Get the sample s = (signed short)(MODSong.pSampleData[Mixer.Channel[c].nSamplePos]*Mixer.Channel[c].nVolume); // Interpolate if the sample-frequency is lower than the mixing rate // If you don't want interpolation simply skip this part if (Mixer.Channel[c].nFrequency < nMixingRate) { // Low precision linear interpolation (fast integer based) qfDistance = Mixer.Channel[c].nSampleFractPos<<16 / nMixingRate; qfDistance2 = (1<<16)-qfDistance; s = (qfDistance*s + qfDistance2*Mixer.Channel[c].iLastSampleData)>>16; /* // ALTERNATIVE MIXER: HIGH QUALITY (Don't know if you really can hear the difference) // High precision linear Interpolation (slow floating point based) // Sounds better than the above integer routine. Maybe this can be used on faster systems double fDistance = (double)Mixer.Channel[c].nSampleFractPos / Mixer.nMixingRate; double fDistance2 = 1-fDistance; s = (short) (fDistance*s + fDistance2*Mixer.Channel[c].iLastSampleData); */ } // Save the last played sample for interpolation purposes Mixer.Channel[c].iLastSampleData = s; // Pan the sample Left += s*(16-Mixer.Channel[c].nPanning)>>2; Right += s*Mixer.Channel[c].nPanning>>2; // Advance sample Mixer.Channel[c].nSampleFractPos += Mixer.Channel[c].nFrequency; while (Mixer.Channel[c].nSampleFractPos > nMixingRate) { Mixer.Channel[c].nSampleFractPos -= nMixingRate; Mixer.Channel[c].nSamplePos++; } } // If we have more than 4 channels we have to make sure that we apply clipping if (MODSong.nNoOfChannels > 4) { *pLeft = Clip(Left)<<13; *pRight = Clip(Right)<<13; } else { *pLeft = Left<<13; *pRight = Right<<13; } pLeft+=2; pRight+=2; } } enum codec_status codec_main(void) { size_t n, bytesfree; unsigned char *p; unsigned int filesize; int old_nPatternTablePosition; int bytesdone; ci->configure(CODEC_SET_FILEBUF_WATERMARK, 1024*512); next_track: if (codec_init()) { return CODEC_ERROR; } while (!*ci->taginfo_ready && !ci->stop_codec) ci->sleep(1); codec_set_replaygain(ci->id3); /* Load MOD file */ p = modfile; bytesfree=sizeof(modfile); while ((n = ci->read_filebuf(p, bytesfree)) > 0) { p += n; bytesfree -= n; } filesize = p-modfile; if (filesize == 0) return CODEC_ERROR; InitMODPlayer(); LoadMOD(modfile); /* Make use of 44.1khz */ ci->configure(DSP_SET_FREQUENCY, 44100); /* Sample depth is 28 bit host endian */ ci->configure(DSP_SET_SAMPLE_DEPTH, 28); /* Stereo output */ ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); /* The main decoder loop */ ci->set_elapsed(0); bytesdone = 0; old_nPatternTablePosition = 0; while (1) { ci->yield(); if (ci->stop_codec || ci->new_track) break; if (ci->seek_time) { /* New time is ready in ci->seek_time */ MODPlayer.nPatternTablePosition = ci->seek_time/1000; MODPlayer.nCurrentLine = 0; ci->seek_complete(); } if(old_nPatternTablePosition != MODPlayer.nPatternTablePosition) { ci->set_elapsed(MODPlayer.nPatternTablePosition*1000+500); old_nPatternTablePosition=MODPlayer.nPatternTablePosition; } SynthRender(samples, CHUNK_SIZE/2); bytesdone += CHUNK_SIZE; ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE/2); } if (ci->request_next_track()) goto next_track; return CODEC_OK; }