From c0f7eb9f9dda1941ff80571df74d5616a4604ed7 Mon Sep 17 00:00:00 2001 From: Thom Johansen Date: Wed, 21 May 2008 11:19:58 +0000 Subject: [PATCH] FS #8680. MOD codec by Rainer Sinsch. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17595 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 1 + apps/codecs/Makefile | 1 + apps/codecs/SOURCES | 1 + apps/codecs/mod.c | 1327 ++++++++++++++++++++++++++++++ apps/filetypes.c | 1 + apps/metadata.c | 8 + apps/metadata/metadata_parsers.h | 1 + apps/metadata/mod.c | 62 ++ firmware/export/id3.h | 1 + firmware/id3.c | 3 + 10 files changed, 1406 insertions(+) create mode 100644 apps/codecs/mod.c create mode 100644 apps/metadata/mod.c diff --git a/apps/SOURCES b/apps/SOURCES index 8c7cc15ecb..b9b763486f 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -134,6 +134,7 @@ metadata/mp4.c metadata/mpc.c metadata/ogg.c metadata/sid.c +metadata/mod.c metadata/spc.c metadata/vorbis.c metadata/wave.c diff --git a/apps/codecs/Makefile b/apps/codecs/Makefile index 5a65341d02..7c066d284d 100644 --- a/apps/codecs/Makefile +++ b/apps/codecs/Makefile @@ -47,6 +47,7 @@ all: $(LINKCODEC) $(ROCKS) ifndef SIMVER $(BUILDDIR)/%.a : % $(CODECDEPS) +$(OBJDIR)/mod.elf : $(OBJDIR)/mod.o $(OBJDIR)/codec_crt0.o $(OBJDIR)/wav.elf : $(OBJDIR)/wav.o $(OBJDIR)/codec_crt0.o $(OBJDIR)/sid.elf : $(OBJDIR)/sid.o $(OBJDIR)/codec_crt0.o $(OBJDIR)/adx.elf : $(OBJDIR)/adx.o $(OBJDIR)/codec_crt0.o diff --git a/apps/codecs/SOURCES b/apps/codecs/SOURCES index a93cb6c0cc..3c0118c1ce 100644 --- a/apps/codecs/SOURCES +++ b/apps/codecs/SOURCES @@ -13,6 +13,7 @@ wma.c aac.c #endif ape.c +mod.c shorten.c aiff.c speex.c diff --git a/apps/codecs/mod.c b/apps/codecs/mod.c new file mode 100644 index 0000000000..0d3eadf773 --- /dev/null +++ b/apps/codecs/mod.c @@ -0,0 +1,1327 @@ +/*************************************************************************** + * __________ __ ___. + * 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 supports large files directly from internal memory management. + * There is a drawback however: It may happen that a song is not completely + * loaded when the internal rockbox-ringbuffer (approx. 28MB) is filled up + * As a workaround make sure you don't have directories with mods larger + * than a total of 28MB + *************/ + +#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 */ + +/* Instrument Data */ +struct s_instrument { + /* Sample name / description */ + /*char description[22];*/ + + /* Sample length in bytes */ + unsigned short length; + + /* Sample finetuning (-8 - +7) */ + signed char finetune; + + /* Sample volume (0 - 64) */ + signed char volume; + + /* Sample Repeat Position */ + unsigned short repeatoffset; + + /* Sample Repeat Length */ + unsigned short repeatlength; + + /* Offset to sample data */ + unsigned int sampledataoffset; +}; + +/* Song Data */ +struct s_song { + /* Song name / title description */ + /*char szTitle[20];*/ + + /* No. of channels in song */ + unsigned char noofchannels; + + /* No. of instruments used (either 15 or 31) */ + unsigned char noofinstruments; + + /* How many patterns are beeing played? */ + unsigned char songlength; + + /* Where to jump after the song end? */ + unsigned char songendjumpposition; + + /* Pointer to the Pattern Order Table */ + unsigned char *patternordertable; + + /* Pointer to the pattern data */ + void *patterndata; + + /* Pointer to the sample buffer */ + signed char *sampledata; + + /* Instrument data */ + struct s_instrument instrument[31]; +}; + +struct s_modchannel { + /* Current Volume */ + signed char volume; + + /* Current Offset to period in PeriodTable of notebeeing played + (can be temporarily negative) */ + short periodtableoffset; + + /* Current Period beeing played */ + short period; + + /* Current effect */ + unsigned char effect; + + /* Current parameters of effect */ + unsigned char effectparameter; + + /* Current Instrument beeing played */ + unsigned char instrument; + + /* Current Vibrato Speed */ + unsigned char vibratospeed; + + /* Current Vibrato Depth */ + unsigned char vibratodepth; + + /* Current Position for Vibrato in SinTable */ + unsigned char vibratosinpos; + + /* Current Tremolo Speed */ + unsigned char tremolospeed; + + /* Current Tremolo Depth */ + unsigned char tremolodepth; + + /* Current Position for Tremolo in SinTable */ + unsigned char tremolosinpos; + + /* Current Speed of Effect "Slide Note up" */ + unsigned char slideupspeed; + + /* Current Speed of Effect "Slide Note down" */ + unsigned char slidedownspeed; + + /* Current Speed of the "Slide to Note" effect */ + unsigned char slidetonotespeed; + + /* Current Period of the "Slide to Note" effect */ + unsigned short slidetonoteperiod; +}; + +struct s_modplayer { + /* Ticks per Line */ + unsigned char ticksperline; + + /* Beats per Minute */ + unsigned char bpm; + + /* Position of the Song in the Pattern Table (0-127) */ + unsigned char patterntableposition; + + /* Current Line (may be temporarily < 0) */ + signed char currentline; + + /* Current Tick */ + signed char currenttick; + + /* How many samples are required to calculate for each tick? */ + unsigned int samplespertick; + + /* Information about the channels */ + struct s_modchannel modchannel[8]; + + /* The Amiga Period Table + (+1 because we use index 0 for period 0 = no new note) */ + unsigned short periodtable[37*8+1]; + + /* The sinus table [-255,255] */ + signed short sintable[0x40]; + + /* Is the glissando effect enabled? */ + bool glissandoenabled; + + /* Is the Amiga Filter enabled? */ + bool amigafilterenabled; + + /* The pattern-line where the loop is carried out (set with e6 command) */ + unsigned char loopstartline; + + /* Number of times to loop */ + unsigned char looptimes; +}; + +struct s_channel { + /* Panning (0 = left, 16 = right) */ + unsigned char panning; + + /* Sample frequency of the channel */ + unsigned short frequency; + + /* Position of the sample currently played */ + unsigned int samplepos; + + /* Fractual Position of the sample currently player */ + unsigned int samplefractpos; + + /* Loop Sample */ + bool loopsample; + + /* Loop Position Start */ + unsigned int loopstart; + + /* Loop Position End */ + unsigned int loopend; + + /* Is The channel beeing played? */ + bool channelactive; + + /* The Volume (0..64) */ + signed char volume; + + /* The last sampledata beeing played (required for interpolation) */ + signed short lastsampledata; +}; + +struct s_mixer { + /* The channels */ + struct s_channel channel[32]; +}; + +struct s_song modsong IDATA_ATTR; /* The Song */ +struct s_modplayer modplayer IDATA_ATTR; /* The Module Player */ +struct s_mixer mixer IDATA_ATTR; + +const unsigned short mixingrate = 44100; + +STATICIRAM void mixer_playsample(int channel, int instrument) ICODE_ATTR; +void mixer_playsample(int channel, int instrument) +{ + struct s_channel *p_channel = &mixer.channel[channel]; + struct s_instrument *p_instrument = &modsong.instrument[instrument]; + + p_channel->channelactive = true; + p_channel->samplepos = p_instrument->sampledataoffset; + p_channel->samplefractpos = 0; + p_channel->loopsample = (p_instrument->repeatlength > 2) ? true : false; + if (p_channel->loopsample) { + p_channel->loopstart = p_instrument->repeatoffset + + p_instrument->sampledataoffset; + p_channel->loopend = p_channel->loopstart + + p_instrument->repeatlength; + } + else p_channel->loopend = p_instrument->length + + p_instrument->sampledataoffset; + + /* Remember the instrument */ + modplayer.modchannel[channel].instrument = instrument; +} + +inline void mixer_stopsample(int channel) +{ + mixer.channel[channel].channelactive = false; +} + +inline void mixer_continuesample(int channel) +{ + mixer.channel[channel].channelactive = true; +} + +inline void mixer_setvolume(int channel, int volume) +{ + mixer.channel[channel].volume = volume; +} + +inline void mixer_setpanning(int channel, int panning) +{ + mixer.channel[channel].panning = panning; +} + +inline void mixer_setamigaperiod(int channel, int amigaperiod) +{ + /* Just to make sure we don't devide by zero + * amigaperiod shouldn't 0 anyway - if it is the case + * then something terribly went wrong */ + if (amigaperiod == 0) + return; + + mixer.channel[channel].frequency = 3579546 / amigaperiod; +} + +/* 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; + /* Index 0 stands for no note (and therefore no period) */ + modplayer.periodtable[0] = 0; + for (i=1;i<297;i++) + { + modplayer.periodtable[i] = (unsigned short) f; + f /= 1.0072464122237039; /* = pow(2.0f, 1.0f/(12.0f*8.0f)); */ + } + + /* + * This is a more accurate but also time more consuming approach + * to calculate the amiga period table + * Commented out for speed purposes + const int finetuning = 8; + const int octaves = 3; + for (int halftone=0;halftone<=finetuning*octaves*12+7;halftone++) + { + float e = pow(2.0f, halftone/(12.0f*8.0f)); + float f = 906.55f/e; + modplayer.periodtable[halfetone+1] = (int)(f+0.5f); + } + */ + + /* 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.currentline = 0; + modplayer.currenttick = 0; + modplayer.patterntableposition = 0; + modplayer.bpm = 125; + modplayer.ticksperline = 6; + modplayer.glissandoenabled = false; /* Disable glissando */ + modplayer.amigafilterenabled = false; /* Disable the Amiga Filter */ + + /* Default Panning Values */ + int panningvalues[8] = {4,12,12,4,4,12,12,4}; + for (c=0;c<8;c++) + { + /* Set Default Panning */ + mixer_setpanning(c, panningvalues[c]); + /* Reset channels in the MOD Player */ + memset(&modplayer.modchannel[c], 0, sizeof(struct s_modchannel)); + /* Don't play anything */ + mixer.channel[c].channelactive = false; + } + +} + +/* Load the MOD File from memory */ +STATICIRAM bool loadmod(void *modfile) ICODE_ATTR; +bool loadmod(void *modfile) +{ + int i; + bool periodsconverted = false; + + /* We don't support PowerPacker 2.0 Files */ + if (memcmp((char*) modfile, "PP20", 4) == 0) return false; + + /* Get the File Format Tag */ + char *fileformattag = (char*)modfile + 1080; + + /* Find out how many channels and instruments are used */ + if (memcmp(fileformattag, "2CHN", 4) == 0) + {modsong.noofchannels = 2; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "M.K.", 4) == 0) + {modsong.noofchannels = 4; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "M!K!", 4) == 0) + {modsong.noofchannels = 4; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "4CHN", 4) == 0) + {modsong.noofchannels = 4; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "FLT4", 4) == 0) + {modsong.noofchannels = 4; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "6CHN", 4) == 0) + {modsong.noofchannels = 6; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "8CHN", 4) == 0) + {modsong.noofchannels = 8; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "OKTA", 4) == 0) + {modsong.noofchannels = 8; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "CD81", 4) == 0) + {modsong.noofchannels = 8; modsong.noofinstruments = 31;} + else if (memcmp(fileformattag, "RS", 2) == 0) + { + /* This is a special internal in-memory fileformat + * where the periods have been already converted to offsets + * in our periodtable. + * It comes to use when rockbox reloads an already played + * module (e.g. on rewinding) */ + modsong.noofchannels = fileformattag[2]; + modsong.noofinstruments = fileformattag[3]; + periodsconverted = true; + } + else { + /* The file has no format tag, so we take a guess */ + modsong.noofchannels = 4; + + /* For the no of instruments we check if there is a sample #16 and + * if it has a (ascii) name */ + char *p = (char *)modfile + 470; + if ((*p >= 32) && (*p <= 126)) modsong.noofinstruments = 31; + else modsong.noofinstruments = 15; + } + + /* Get the Song title + * Skipped here + * strncpy(modsong.szTitle, (char*)pMODFile, 20); */ + + /* Get the Instrument information */ + for (i=0;idescription, (char*)p, 22); */ + p += 22; + instrument->length = (((p[0])<<8) + p[1]) << 1; p+=2; + instrument->finetune = *p++ & 0x0f; + /* Treat finetuning as signed nibble */ + if (instrument->finetune > 7) instrument->finetune -= 16; + instrument->volume = *p++; + instrument->repeatoffset = (((p[0])<<8) + p[1]) << 1; p+= 2; + instrument->repeatlength = (((p[0])<<8) + p[1]) << 1; + } + + /* Get the pattern information */ + unsigned char *p = (unsigned char *)modfile + 20 + + modsong.noofinstruments*30; + modsong.songlength = *p++; + modsong.songendjumpposition = *p++; + modsong.patternordertable = p; + + /* Find out how many patterns are used within this song */ + int maxpatterns = 0; + for (i=0;i<128;i++) + if (modsong.patternordertable[i] > maxpatterns) + maxpatterns = modsong.patternordertable[i]; + maxpatterns++; + + /* Get the pattern data */ + modsong.patterndata = (char*)modfile + 20 + + modsong.noofinstruments*30 + 134; + + /* Convert the period values in the mod file to offsets + * in our periodtable (but only, if we haven't done this yet) */ + p = (unsigned char *) modsong.patterndata; + if (!periodsconverted) + { + int note, note2, channel; + for (note=0;note> 8) | (p[0] & 0xf0); + p[1] = periodoffset & 0xff; + p += 4; + } + /* Remember that we already converted the periods, + * in case the file gets reloaded by rewinding */ + fileformattag[0] = 'R'; fileformattag[1] = 'S'; + fileformattag[2] = modsong.noofchannels; + fileformattag[3] = modsong.noofinstruments; + } + + /* 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.sampledata = (signed char*) modsong.patterndata + + maxpatterns*4*modsong.noofchannels*64; + int sampledataoffset = 0; + for (i=0;i> 7 is used in the original protracker source code */ + mixer_setamigaperiod(channel, p_modchannel->period+ + ((p_modchannel->vibratodepth * + modplayer.sintable[p_modchannel->vibratosinpos])>>7)); + + /* Foward in Sine Table */ + p_modchannel->vibratosinpos += p_modchannel->vibratospeed; + p_modchannel->vibratosinpos &= 0x3f; +} + +/* Apply tremolo to channel + * (same as vibrato, but only apply on volume instead of pitch) */ +STATICIRAM void tremolo(int channel) ICODE_ATTR; +void tremolo(int channel) +{ + struct s_modchannel *p_modchannel = &modplayer.modchannel[channel]; + + /* Apply Tremolo + * >> 6 is used in the original protracker source code */ + int volume = (p_modchannel->volume * + modplayer.sintable[p_modchannel->tremolosinpos])>>6; + if (volume > 64) volume = 64; + else if (volume < 0) volume = 0; + mixer_setvolume(channel, volume); + + /* Foward in Sine Table */ + p_modchannel->tremolosinpos += p_modchannel->tremolosinpos; + p_modchannel->tremolosinpos &= 0x3f; +} + +/* Apply Slide to Note effect to channel */ +STATICIRAM void slidetonote(int channel) ICODE_ATTR; +void slidetonote(int channel) +{ + struct s_modchannel *p_modchannel = &modplayer.modchannel[channel]; + + /* If there hasn't been any slide-to note set up, then return */ + if (p_modchannel->slidetonoteperiod == 0) return; + + /* Slide note up */ + if (p_modchannel->slidetonoteperiod > p_modchannel->period) + { + p_modchannel->period += p_modchannel->slidetonotespeed; + if (p_modchannel->period > p_modchannel->slidetonoteperiod) + p_modchannel->period = p_modchannel->slidetonoteperiod; + } + /* Slide note down */ + else if (p_modchannel->slidetonoteperiod < p_modchannel->period) + { + p_modchannel->period -= p_modchannel->slidetonotespeed; + if (p_modchannel->period < p_modchannel->slidetonoteperiod) + p_modchannel->period = p_modchannel->slidetonoteperiod; + } + mixer_setamigaperiod(channel, p_modchannel->period); +} + +/* Apply Slide to Note effect on channel, + * but this time with glissando enabled */ +STATICIRAM void slidetonoteglissando(int channel) ICODE_ATTR; +void slidetonoteglissando(int channel) +{ + struct s_modchannel *p_modchannel = &modplayer.modchannel[channel]; + + /* Slide note up */ + if (p_modchannel->slidetonoteperiod > p_modchannel->period) + { + p_modchannel->period = + modplayer.periodtable[p_modchannel->periodtableoffset+=8]; + if (p_modchannel->period > p_modchannel->slidetonoteperiod) + p_modchannel->period = p_modchannel->slidetonoteperiod; + } + /* Slide note down */ + else + { + p_modchannel->period = + modplayer.periodtable[p_modchannel->periodtableoffset-=8]; + if (p_modchannel->period < p_modchannel->slidetonoteperiod) + p_modchannel->period = p_modchannel->slidetonoteperiod; + } + mixer_setamigaperiod(channel, p_modchannel->period); +} + +/* Apply Volume Slide */ +STATICIRAM void volumeslide(int channel, int effectx, int effecty) ICODE_ATTR; +void volumeslide(int channel, int effectx, int effecty) +{ + struct s_modchannel *p_modchannel = &modplayer.modchannel[channel]; + + /* If both X and Y Parameters are non-zero, then the y value is ignored */ + if (effectx > 0) { + p_modchannel->volume += effectx; + if (p_modchannel->volume > 64) p_modchannel->volume = 64; + } + else { + p_modchannel->volume -= effecty; + if (p_modchannel->volume < 0) p_modchannel->volume = 0; + } + + mixer_setvolume(channel, p_modchannel->volume); +} + +/* Play the current line (at tick 0) */ +STATICIRAM void playline(int pattern, int line) ICODE_ATTR; +void playline(int pattern, int line) +{ + int c; + + /* Get pointer to the current pattern */ + unsigned char *p_line = (unsigned char*)modsong.patterndata; + p_line += pattern*64*4*modsong.noofchannels; + p_line += line*4*modsong.noofchannels; + + /* Only allow one Patternbreak Commando per Line */ + bool patternbreakdone = false; + + for (c=0;c> 4); + short periodtableoffset = ((p_note[0] & 0x0f) << 8) | p_note[1]; + + p_modchannel->effect = p_note[2] & 0x0f; + p_modchannel->effectparameter = p_note[3]; + + /* Remember Instrument and set Volume if new Instrument triggered */ + if (samplenumber > 0) + { + /* And trigger new sample, if new instrument was set */ + if (samplenumber-1 != p_modchannel->instrument) + { + /* Advance the new sample to the same offset + * the old sample was beeing played */ + int oldsampleoffset = mixer.channel[c].samplepos - + modsong.instrument[ + p_modchannel->instrument].sampledataoffset; + mixer_playsample(c, samplenumber-1); + mixer.channel[c].samplepos += oldsampleoffset; + } + + /* Remember last played instrument on channel */ + p_modchannel->instrument = samplenumber-1; + + /* Set Volume to standard instrument volume, + * if not overwritten by volume effect */ + if (p_modchannel->effect != 0x0c) + { + p_modchannel->volume = modsong.instrument[ + p_modchannel->instrument].volume; + mixer_setvolume(c, p_modchannel->volume); + } + } + /* Trigger new sample if note available */ + if (periodtableoffset > 0) + { + /* Restart instrument only when new sample triggered */ + if (samplenumber != 0) + mixer_playsample(c, (samplenumber > 0) ? + samplenumber-1 : p_modchannel->instrument); + + /* Set the new amiga period + * (but only, if there is no slide to note effect) */ + if ((p_modchannel->effect != 0x3) && + (p_modchannel->effect != 0x5)) + { + /* Apply finetuning to sample */ + p_modchannel->periodtableoffset = periodtableoffset + + modsong.instrument[p_modchannel->instrument].finetune; + p_modchannel->period = modplayer.periodtable[ + p_modchannel->periodtableoffset]; + mixer_setamigaperiod(c, p_modchannel->period); + /* When a new note is played without slide to note setup, + * then disable slide to note */ + modplayer.modchannel[c].slidetonoteperiod = + p_modchannel->period; + } + } + int effectx = p_modchannel->effectparameter>>4; + int effecty = p_modchannel->effectparameter&0x0f; + + switch (p_modchannel->effect) + { + /* Effect 0: Arpeggio */ + case 0x00: + /* Set the base period on tick 0 */ + if (p_modchannel->effectparameter > 0) + mixer_setamigaperiod(c, + modplayer.periodtable[ + p_modchannel->periodtableoffset]); + break; + /* Slide up (Portamento up) */ + case 0x01: + if (p_modchannel->effectparameter > 0) + p_modchannel->slideupspeed = + p_modchannel->effectparameter; + break; + + /* Slide down (Portamento down) */ + case 0x02: + if (p_modchannel->effectparameter > 0) + p_modchannel->slidedownspeed = + p_modchannel->effectparameter; + break; + + /* Slide to Note */ + case 0x03: + if (p_modchannel->effectparameter > 0) + p_modchannel->slidetonotespeed = + p_modchannel->effectparameter; + /* Get the slide to note directly from the pattern buffer */ + if (periodtableoffset > 0) + p_modchannel->slidetonoteperiod = + modplayer.periodtable[periodtableoffset + + modsong.instrument[ + p_modchannel->instrument].finetune]; + /* If glissando is enabled apply the effect directly here */ + if (modplayer.glissandoenabled) + slidetonoteglissando(c); + break; + + /* Set Vibrato */ + case 0x04: + if (effectx > 0) p_modchannel->vibratospeed = effectx; + if (effecty > 0) p_modchannel->vibratodepth = effecty; + break; + + /* Effect 0x06: Slide to note */ + case 0x05: + /* Get the slide to note directly from the pattern buffer */ + if (periodtableoffset > 0) + p_modchannel->slidetonoteperiod = + modplayer.periodtable[periodtableoffset + + modsong.instrument[ + p_modchannel->instrument].finetune]; + break; + + /* Effect 0x06 is "Continue Effects" */ + /* It is not processed on tick 0 */ + case 0x06: + break; + + /* Set Tremolo */ + case 0x07: + if (effectx > 0) p_modchannel->tremolodepth = effectx; + if (effecty > 0) p_modchannel->tremolospeed = effecty; + break; + + /* Set fine panning */ + case 0x08: + /* Internal panning goes from 0..15 + * Scale the fine panning value to that range */ + mixer.channel[c].panning = p_modchannel->effectparameter>>4; + break; + + /* Set Sample Offset */ + case 0x09: + { + struct s_instrument *p_instrument = + &modsong.instrument[p_modchannel->instrument]; + int sampleoffset = p_instrument->sampledataoffset; + if (sampleoffset > p_instrument->length) + sampleoffset = p_instrument->length; + /* Forward the new offset to the mixer */ + mixer.channel[c].samplepos = + p_instrument->sampledataoffset + + (p_modchannel->effectparameter<<8); + mixer.channel[c].samplefractpos = 0; + break; + } + + /* Effect 0x0a (Volume slide) is not processed on tick 0 */ + + /* Position Jump */ + case 0x0b: + modplayer.currentline = -1; + modplayer.patterntableposition = (effectx<<4)+effecty; + break; + + /* Set Volume */ + case 0x0c: + p_modchannel->volume = p_modchannel->effectparameter; + mixer_setvolume(c, p_modchannel->volume); + break; + + /* Pattern break */ + case 0x0d: + modplayer.currentline = effectx*10 + effecty - 1; + if (!patternbreakdone) + { + patternbreakdone = true; + modplayer.patterntableposition++; + } + break; + + /* Extended Effects */ + case 0x0e: + switch (effectx) + { + /* Set Filter */ + case 0x0: + modplayer.amigafilterenabled = + (effecty>0) ? false : true; + break; + /* Fineslide up */ + case 0x1: + mixer_setamigaperiod(c, p_modchannel->period -= + effecty); + if (p_modchannel->period < + modplayer.periodtable[37*8]) p_modchannel->period = 100; + /* Find out the new offset in the period table */ + if (p_modchannel->periodtableoffset < 36*8) + while (modplayer.periodtable[ + p_modchannel->periodtableoffset+8] >= p_modchannel->period) + p_modchannel->periodtableoffset+=8; + break; + /* Fineslide down */ + case 0x2: + mixer_setamigaperiod(c, + p_modchannel->period += effecty); + if (p_modchannel->periodtableoffset > 8) + while (modplayer.periodtable[ + p_modchannel->periodtableoffset-8] + <= p_modchannel->period) + p_modchannel->periodtableoffset-=8; + break; + /* Set glissando on/off */ + case 0x3: + modplayer.glissandoenabled = + (effecty > 0) ? true:false; + break; + /* Set Vibrato waveform */ + case 0x4: + /* Currently not implemented */ + break; + /* Set Finetune value */ + case 0x5: + /* Treat as signed nibble */ + if (effecty > 7) effecty -= 16; + + p_modchannel->periodtableoffset += + effecty - + modsong.instrument[ + p_modchannel->instrument].finetune; + p_modchannel->period = + modplayer.periodtable[ + p_modchannel->periodtableoffset]; + modsong.instrument[ + p_modchannel->instrument].finetune = effecty; + break; + /* Pattern loop */ + case 0x6: + if (effecty == 0) + modplayer.loopstartline = line-1; + else + { + if (modplayer.looptimes == 0) + { + modplayer.currentline = + modplayer.loopstartline; + modplayer.looptimes = effecty; + } + else modplayer.looptimes--; + if (modplayer.looptimes > 0) + modplayer.currentline = + modplayer.loopstartline; + } + 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: + p_modchannel->volume += effecty; + if (p_modchannel->volume > 64) + p_modchannel->volume = 64; + mixer_setvolume(c, p_modchannel->volume); + break; + /* Fine volume slide down */ + case 0xb: + p_modchannel->volume -= effecty; + if (p_modchannel->volume < 0) + p_modchannel->volume = 0; + mixer_setvolume(c, p_modchannel->volume); + break; + /* Cut sample */ + case 0xc: + /* Continue sample */ + mixer_continuesample(c); + 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 (effecty > 0) + mixer.channel[c].channelactive = false; + break; + } + break; + + /* Set Speed */ + case 0x0f: + if (p_modchannel->effectparameter < 32) + modplayer.ticksperline = p_modchannel->effectparameter; + else + modplayer.bpm = p_modchannel->effectparameter; + break; + } + } +} + +/* Play the current effect of the note (ticks 1..speed) */ +STATICIRAM void playeffect(int currenttick) ICODE_ATTR; +void playeffect(int currenttick) +{ + int c; + + for (c=0;cperiod == 0) continue; + + unsigned char effectx = p_modchannel->effectparameter>>4; + unsigned char effecty = p_modchannel->effectparameter&0x0f; + + switch (p_modchannel->effect) + { + /* Effect 0: Arpeggio */ + case 0x00: + if (p_modchannel->effectparameter > 0) + { + unsigned short newperiodtableoffset; + switch (currenttick % 3) + { + case 0: + mixer_setamigaperiod(c, + modplayer.periodtable[ + p_modchannel->periodtableoffset]); + break; + case 1: + newperiodtableoffset = + p_modchannel->periodtableoffset+(effectx<<3); + if (newperiodtableoffset < 37*8) + mixer_setamigaperiod(c, + modplayer.periodtable[ + newperiodtableoffset]); + break; + case 2: + newperiodtableoffset = + p_modchannel->periodtableoffset+(effecty<<3); + if (newperiodtableoffset < 37*8) + mixer_setamigaperiod(c, + modplayer.periodtable[ + newperiodtableoffset]); + break; + } + } + break; + + /* Effect 1: Slide Up */ + case 0x01: + mixer_setamigaperiod(c, + p_modchannel->period -= p_modchannel->slideupspeed); + /* Find out the new offset in the period table */ + if (p_modchannel->periodtableoffset <= 37*8) + while (modplayer.periodtable[ + p_modchannel->periodtableoffset] > + p_modchannel->period) + { + p_modchannel->periodtableoffset++; + /* Make sure we don't go out of range */ + if (p_modchannel->periodtableoffset > 37*8) + { + p_modchannel->periodtableoffset = 37*8; + break; + } + } + break; + + /* Effect 2: Slide Down */ + case 0x02: + mixer_setamigaperiod(c, p_modchannel->period += + p_modchannel->slidedownspeed); + /* Find out the new offset in the period table */ + if (p_modchannel->periodtableoffset > 8) + while (modplayer.periodtable[ + p_modchannel->periodtableoffset] < + p_modchannel->period) + { + p_modchannel->periodtableoffset--; + /* Make sure we don't go out of range */ + if (p_modchannel->periodtableoffset < 1) + { + p_modchannel->periodtableoffset = 1; + break; + } + } + break; + + /* Effect 3: Slide to Note */ + case 0x03: + /* Apply smooth sliding, if no glissando is enabled */ + if (modplayer.glissandoenabled == 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, effectx, effecty); + break; + + /* Effect 6: Continue effect 4:'Vibrato', + * but also do Volume slide */ + case 0x06: + vibrate(c); + volumeslide(c, effectx, effecty); + 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, effectx, effecty); + 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 (effectx) + { + /* Retrigger sample ($E9 + Tick to Retrig note at) */ + case 0x9: + /* Don't device by zero */ + if (effecty == 0) effecty = 1; + /* Apply retrig */ + if (currenttick % effecty == 0) + mixer_playsample(c, p_modchannel->instrument); + break; + /* Cut note (Usage: $EC + Tick to Cut note at) */ + case 0xc: + if (currenttick == effecty) + mixer_stopsample(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 (currenttick == effecty) + mixer.channel[c].channelactive = true; + break; + + } + break; + /* Effect F (Set Speed) is only processed at tick 0 */ + + } + } +} + +inline int clip(int i) +{ + if (i > 32767) return(32767); + else if (i < -32768) return(-32768); + else return(i); +} + +STATICIRAM void synthrender(void *renderbuffer, int samplecount) ICODE_ATTR; +void synthrender(void *renderbuffer, int samplecount) +{ + /* 125bpm equals to 50Hz (= 0.02s) + * => one tick = mixingrate/50, + * samples passing in one tick: + * mixingrate/(bpm/2.5) = 2.5*mixingrate/bpm */ + + int *p_left = (int *) renderbuffer; /* int in rockbox */ + int *p_right = p_left+1; + signed short s; + int qf_distance, qf_distance2; + + int i; + + int c, left, right; + + for (i=0;i= modplayer.ticksperline) + { + modplayer.currentline++; + modplayer.currenttick = 0; + if (modplayer.currentline == 64) + { + modplayer.patterntableposition++; + if (modplayer.patterntableposition >= modsong.songlength) + /* This is for Noise Tracker + * modplayer.patterntableposition = + * modsong.songendjumpposition; + * More compatible approach is restart from 0 */ + modplayer.patterntableposition=0; + modplayer.currentline = 0; + } + } + + modplayer.samplespertick = (20*mixingrate/modplayer.bpm)>>3; + } + /* Mix buffers from here + * Walk through all channels */ + left=0, right=0; + + /* If song has not stopped playing */ + if (modplayer.patterntableposition < 127) + /* Loop through all channels */ + for (c=0;c= mixer.channel[c].loopend) + { + if (mixer.channel[c].loopsample) + mixer.channel[c].samplepos -= + (mixer.channel[c].loopend- + mixer.channel[c].loopstart); + else mixer.channel[c].channelactive = false; + } + + /* If the sample has stopped playing don't mix it */ + if (!mixer.channel[c].channelactive) continue; + + /* Get the sample */ + s = (signed short)(modsong.sampledata[ + mixer.channel[c].samplepos]*mixer.channel[c].volume); + + /* 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].frequency < mixingrate) + { + /* Low precision linear interpolation + * (fast integer based) */ + qf_distance = mixer.channel[c].samplefractpos<<16 / + mixingrate; + qf_distance2 = (1<<16)-qf_distance; + s = (qf_distance*s + qf_distance2* + mixer.channel[c].lastsampledata)>>16; + } + + /* Save the last played sample for interpolation purposes */ + mixer.channel[c].lastsampledata = s; + + /* Pan the sample */ + left += s*(16-mixer.channel[c].panning)>>3; + right += s*mixer.channel[c].panning>>3; + + /* Advance sample */ + mixer.channel[c].samplefractpos += mixer.channel[c].frequency; + while (mixer.channel[c].samplefractpos > mixingrate) + { + mixer.channel[c].samplefractpos -= mixingrate; + mixer.channel[c].samplepos++; + } + } + /* If we have more than 4 channels + * we have to make sure that we apply clipping */ + if (modsong.noofchannels > 4) { + *p_left = clip(left)<<13; + *p_right = clip(right)<<13; + } + else { + *p_left = left<<13; + *p_right = right<<13; + } + p_left+=2; + p_right+=2; + } +} + + +enum codec_status codec_main(void) +{ + size_t n; + unsigned char *modfile; + int old_patterntableposition; + + 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 */ + /* + * This is the save way + + size_t bytesfree; + unsigned int filesize; + + p = modfile; + bytesfree=sizeof(modfile); + while ((n = ci->read_filebuf(p, bytesfree)) > 0) { + p += n; + bytesfree -= n; + if (bytesfree == 0) + return CODEC_ERROR; + } + filesize = p-modfile; + + if (filesize == 0) + return CODEC_ERROR; + */ + + /* Directly use mod in buffer */ + ci->seek_buffer(0); + modfile = ci->request_buffer(&n, ci->filesize); + if (!modfile || n < (size_t)ci->filesize) { + 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_patterntableposition = 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.patterntableposition = ci->seek_time/1000; + modplayer.currentline = 0; + ci->seek_complete(); + } + + if(old_patterntableposition != modplayer.patterntableposition) { + ci->set_elapsed(modplayer.patterntableposition*1000+500); + old_patterntableposition=modplayer.patterntableposition; + } + + 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; +} diff --git a/apps/filetypes.c b/apps/filetypes.c index ead2295f70..b884102ed7 100644 --- a/apps/filetypes.c +++ b/apps/filetypes.c @@ -68,6 +68,7 @@ const struct filetype inbuilt_filetypes[] = { { "m4a", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "m4b", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "mp4", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, + { "mod", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "shn", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "aif", FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, { "aiff",FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, diff --git a/apps/metadata.c b/apps/metadata.c index 7b3b314d74..a8821b29df 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -184,6 +184,14 @@ bool get_metadata(struct mp3entry* id3, int fd, const char* trackname) break; + case AFMT_MOD: + if (!get_mod_metadata(fd, id3)) + { + return false; + } + + break; + case AFMT_SHN: id3->vbr = true; id3->filesize = filesize(fd); diff --git a/apps/metadata/metadata_parsers.h b/apps/metadata/metadata_parsers.h index c3265f8a43..b34d09fe4c 100644 --- a/apps/metadata/metadata_parsers.h +++ b/apps/metadata/metadata_parsers.h @@ -25,6 +25,7 @@ bool get_mp4_metadata(int fd, struct mp3entry* id3); bool get_monkeys_metadata(int fd, struct mp3entry* id3); bool get_musepack_metadata(int fd, struct mp3entry *id3); bool get_sid_metadata(int fd, struct mp3entry* id3); +bool get_mod_metadata(int fd, struct mp3entry* id3); bool get_spc_metadata(int fd, struct mp3entry* id3); bool get_ogg_metadata(int fd, struct mp3entry* id3); bool get_wave_metadata(int fd, struct mp3entry* id3); diff --git a/apps/metadata/mod.c b/apps/metadata/mod.c new file mode 100644 index 0000000000..0205fd04bf --- /dev/null +++ b/apps/metadata/mod.c @@ -0,0 +1,62 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "id3.h" +#include "metadata_common.h" +#include "rbunicode.h" + +bool get_mod_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char buf[1084]; + int read_bytes; + char *p; + + + if ((lseek(fd, 0, SEEK_SET) < 0) + || ((read_bytes = read(fd, buf, sizeof(buf))) < 1084)) + { + return false; + } + + /* We don't do file format checking here + * There can be .mod files without any signatures out there */ + + p = id3->id3v2buf; + + /* Copy Title as artist */ + strcpy(p, &buf[0x00]); + id3->artist = p; + p += strlen(p)+1; + + id3->bitrate = filesize(fd)/1024; /* size in kb */ + id3->frequency = 44100; + id3->length = 120*1000; + id3->vbr = false; + id3->filesize = filesize(fd); + + return true; +} + diff --git a/firmware/export/id3.h b/firmware/export/id3.h index 8aaf76f73c..921e91cdd5 100644 --- a/firmware/export/id3.h +++ b/firmware/export/id3.h @@ -57,6 +57,7 @@ enum AFMT_SPC, /* SPC700 save state */ AFMT_APE, /* Monkey's Audio (APE) */ AFMT_WMA, /* WMAV1/V2 in ASF */ + AFMT_MOD, /* Amiga MOD File Format */ #endif /* add new formats at any index above this line to have a sensible order - diff --git a/firmware/id3.c b/firmware/id3.c index 0e5c05ca27..2604daf1f7 100644 --- a/firmware/id3.c +++ b/firmware/id3.c @@ -112,6 +112,9 @@ const struct afmt_entry audio_formats[AFMT_NUM_CODECS] = /* WMA (WMAV1/V2 in ASF) */ [AFMT_WMA] = AFMT_ENTRY("WMA", "wma", NULL, "wma\0wmv\0asf\0" ), + /* Amiga MOD File */ + [AFMT_MOD] = + AFMT_ENTRY("MOD", "mod", NULL, "mod\0" ), #endif };