Mercurial > hg > dmlib
view minijss/jssplr.c @ 2576:812b16ee49db
I had been living under apparent false impression that "realfft.c"
on which the FFT implementation in DMLIB was basically copied from
was released in public domain at some point, but it could very well
be that it never was. Correct license is (or seems to be) GNU GPL.
Thus I removing the code from DMLIB, and profusely apologize to the
author, Philip Van Baren.
It was never my intention to distribute code based on his original
work under a more liberal license than originally intended.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 11 Mar 2022 16:32:50 +0200 |
parents | 69a5af2eb1ea |
children | 9807ae37ad69 |
line wrap: on
line source
/* * miniJSS - Module playing routines * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2006-2015 Tecnic Software productions (TNSP) */ #include "jssplr.h" #include <math.h> /* Miscellaneous tables */ static const Uint8 jmpSineTab[32] = { 0, 24, 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253, 255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24 }; static const Sint16 jmpXMAmigaPeriodTab[13 * 8] = { 907, 900, 894, 887, 881, 875, 868, 862, 856, 850, 844, 838, 832, 826, 820, 814, 808, 802, 796, 791, 785, 779, 774, 768, 762, 757, 752, 746, 741, 736, 730, 725, 720, 715, 709, 704, 699, 694, 689, 684, 678, 675, 670, 665, 660, 655, 651, 646, 640, 636, 632, 628, 623, 619, 614, 610, 604, 601, 597, 592, 588, 584, 580, 575, 570, 567, 563, 559, 555, 551, 547, 543, 538, 535, 532, 528, 524, 520, 516, 513, 508, 505, 502, 498, 494, 491, 487, 484, 480, 477, 474, 470, 467, 463, 460, 457, 453, 450, 447, 443, 440, 437, 434, 431 }; #define jmpNMODEffectTable (36) static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /* Helper functions */ static int jmpNoteToAmigaPeriod(int note, int finetune) { int tmp = dmClamp(note + finetune + 8, 0, 103); return jmpXMAmigaPeriodTab[tmp]; } static int jmpGetPeriodFromNote(JSSPlayer *mp, int note, int finetune) { int res; if (JMPGETMODFLAGS(mp, jmdfAmigaPeriods)) { int mfinetune = finetune / 16, mnote = (note % 12) * 8, moctave = note / 12, period1, period2; period1 = jmpNoteToAmigaPeriod(mnote, mfinetune); if (finetune < 0) { mfinetune--; finetune = -finetune; } else mfinetune++; period2 = jmpNoteToAmigaPeriod(mnote, mfinetune); mfinetune = finetune & 15; period1 *= (16 - mfinetune); period2 *= mfinetune; res = ((period1 + period2) * 2) >> moctave; //fprintf(stderr, "jmpGetAmigaPeriod(%d, %d) = %d\n", note, finetune, res); } else { //fprintf(stderr, "jmpGetLinearPeriod(%d, %d) = %d\n", note, finetune, res); //res = ((120 - note) << 6) - (finetune / 2); res = 7680 - (note * 64) - (finetune / 2); if (res < 1) res = 1; } return res; } static void jmpCSetPitch(JSSPlayer *mp, JSSPlayerChannel *chn, int value) { if (value > 0) { if (JMPGETMODFLAGS(mp, jmdfAmigaPeriods)) { // Frequency = 8363*1712 / Period chn->cfreq = chn->freq = 14317456 / value; } else { // Frequency = Frequency = 8363*2^((6*12*16*4 - Period) / (12*16*4)) chn->cfreq = chn->freq = 8363.0f * pow(2.0f, (4608.0f - (double) value) / 768.0f); //chn->cfreq = chn->freq = 8363 * (1 << ((4608 - value) / 768)); } JMPSETNDFLAGS(cdfNewFreq); } } static void jmpCSetVolume(JSSPlayerChannel *chn, int channel, int volume) { (void) channel; chn->volume = dmClamp(volume, mpMinVol, mpMaxVol); JMPSETNDFLAGS(cdfNewVolume); } static BOOL jmpExecEnvelope(JSSEnvelope *env, JSSPlayerEnvelope *pe, BOOL keyOff) { int point; if (!pe->exec) return FALSE; // Find current point, if not last point for (point = 0; point < env->npoints - 1; point++) { if (pe->frame < env->points[point + 1].frame) break; } if (env->flags & jenvfLooped && pe->frame >= env->points[env->loopE].frame) { point = env->loopS; pe->frame = env->points[env->loopS].frame; pe->value = env->points[point].value; } // Check for last point if (pe->frame >= env->points[env->npoints - 1].frame) { point = env->npoints - 1; pe->exec = FALSE; pe->value = env->points[point].value; } else { // Linearly interpolate the value between current and next point JSSEnvelopePoint *ep1 = &env->points[point], *ep2 = &env->points[point + 1]; int delta = ep2->frame - ep1->frame; if (delta > 0) pe->value = ep1->value + ((ep2->value - ep1->value) * (pe->frame - ep1->frame)) / delta; else pe->value = ep1->value; } if (pe->exec) { // The frame counter IS processed even if the envelope is not! if ((env->flags & jenvfSustain) && point == env->sustain && env->points[point].frame == env->points[env->sustain].frame) { if (keyOff) pe->frame++; } else pe->frame++; } return TRUE; } static void jmpProcessExtInstrument(JSSPlayerChannel *chn, int channel) { JSSExtInstrument *inst = chn->extInstrument; (void) channel; // Get the instrument for envelope data if (!inst) return; // Process the autovibrato /* FIXME fix me FIX me!!! todo. */ if (inst->volumeEnv.flags & jenvfUsed) { // Process the instrument volume fadeout if (chn->keyOff && chn->fadeOutVol > 0 && inst->fadeOut > 0) { int tmp = chn->fadeOutVol - inst->fadeOut; if (tmp < 0) tmp = 0; chn->fadeOutVol = tmp; JMPSETNDFLAGS(cdfNewVolume); } // Execute the volume envelope if (jmpExecEnvelope(&inst->volumeEnv, &chn->volumeEnv, chn->keyOff)) JMPSETNDFLAGS(cdfNewVolume); } else { // If the envelope is not used, set max volume chn->volumeEnv.value = mpMaxVol; chn->fadeOutVol = chn->keyOff ? 0 : mpMaxFadeoutVol; JMPSETNDFLAGS(cdfNewVolume); } if (inst->panningEnv.flags & jenvfUsed) { // Process the panning envelope if (jmpExecEnvelope(&inst->panningEnv, &chn->panningEnv, chn->keyOff)) JMPSETNDFLAGS(cdfNewPanPos); } else { // If the envelope is not used, set center panning if (chn->panningEnv.value != mpPanCenter) { chn->panningEnv.value = mpPanCenter; JMPSETNDFLAGS(cdfNewPanPos); } } } /* * The player */ JSSPlayer *jmpInit(JSSMixer *pDevice) { JSSPlayer *mp; // Allocate a player structure mp = dmMalloc0(sizeof(JSSPlayer)); if (mp == NULL) JSSERROR(DMERR_MALLOC, NULL, "Could not allocate memory for player structure.\n"); // Set variables mp->device = pDevice; #ifdef JSS_SUP_THREADS mp->mutex = dmCreateMutex(); #endif return mp; } int jmpClose(JSSPlayer * mp) { if (mp == NULL) return DMERR_NULLPTR; // Stop player jmpStop(mp); // Deallocate resources #ifdef JSS_SUP_THREADS dmDestroyMutex(mp->mutex); #endif // Clear structure memset(mp, 0, sizeof(JSSPlayer)); dmFree(mp); return DMERR_OK; } /* Reset the envelopes for given channel. */ static void jmpResetEnvelope(JSSPlayerEnvelope *env) { env->frame = env->value = 0; env->exec = TRUE; } /* Clear module player structure */ void jmpClearChannel(JSSPlayerChannel *chn) { memset(chn, 0, sizeof(JSSPlayerChannel)); chn->note = jsetNotSet; chn->ninstrument = jsetNotSet; chn->nextInstrument = jsetNotSet; chn->panning = mpPanCenter; chn->panningEnv.value = mpPanCenter; } void jmpClearPlayer(JSSPlayer * mp) { assert(mp != NULL); JSS_LOCK(mp); // Initialize general variables mp->patternDelay = 0; mp->newRowSet = FALSE; mp->newOrderSet = FALSE; mp->tick = jsetNotSet; mp->row = 0; mp->lastPatLoopRow = 0; // Initialize channel data for (int i = 0; i < jsetNChannels; i++) jmpClearChannel(&mp->channels[i]); JSS_UNLOCK(mp); } /* Set module */ void jmpSetModule(JSSPlayer * mp, JSSModule * module) { assert(mp != NULL); JSS_LOCK(mp); jmpStop(mp); jmpClearPlayer(mp); mp->module = module; JSS_UNLOCK(mp); } /* Stop playing */ void jmpStop(JSSPlayer * mp) { assert(mp != NULL); JSS_LOCK(mp); if (mp->isPlaying) { jvmRemoveCallback(mp->device); mp->isPlaying = FALSE; } JSS_UNLOCK(mp); } /* Resume playing */ void jmpResume(JSSPlayer * mp) { assert(mp != NULL); JSS_LOCK(mp); if (!mp->isPlaying) { int result = jvmSetCallback(mp->device, jmpExec, (void *) mp); if (result != DMERR_OK) JSSERROR(result,, "Could not initialize callback for player.\n"); mp->isPlaying = TRUE; } JSS_UNLOCK(mp); } /* Sets new order using given value as reference. * Jumps over skip-points and invalid values, loops * to first order if enabled. */ static void jmpSetNewOrder(JSSPlayer * mp, int order) { BOOL orderOK; mp->order = jsetNotSet; orderOK = FALSE; while (!orderOK) { if (order < 0 || order >= mp->module->norders) { jmpStop(mp); orderOK = TRUE; } else { int pattern = mp->module->orderList[order]; if (pattern == jsetOrderSkip) { order++; } else if (pattern >= mp->module->npatterns || pattern == jsetOrderEnd) { jmpStop(mp); orderOK = TRUE; } else { // All OK orderOK = TRUE; mp->pattern = mp->module->patterns[pattern]; mp->npattern = pattern; mp->order = order; } } } } /* Set new tempo-value of the player. */ static void jmpSetTempo(JSSPlayer * mp, int tempo) { assert(mp != NULL); JSS_LOCK(mp); assert(mp->device != NULL); mp->tempo = tempo; jvmSetCallbackFreq(mp->device, (mp->device->outFreq * 5) / (tempo * 2)); JSS_UNLOCK(mp); } static void jmpClearChannels(JSSPlayer * mp) { assert(mp != NULL); JSS_LOCK(mp); assert(mp->device != NULL); assert(mp->module != NULL); for (int i = 0; i < mp->module->nchannels; i++) jvmStop(mp->device, i); // Initialize channel data for (int i = 0; i < jsetNChannels; i++) jmpClearChannel(&mp->channels[i]); JSS_UNLOCK(mp); } /* Starts playing module from a given ORDER. */ static int jmpPlayStart(JSSPlayer *mp) { int result; mp->speed = mp->module->defSpeed; jmpSetTempo(mp, mp->module->defTempo); result = jvmSetCallback(mp->device, jmpExec, (void *) mp); if (result != DMERR_OK) { JSSERROR(result, result, "Could not initialize callback for player.\n"); } mp->isPlaying = TRUE; return DMERR_OK; } int jmpChangeOrder(JSSPlayer *mp, int order) { assert(mp != NULL); JSS_LOCK(mp); assert(mp->module != NULL); jmpClearChannels(mp); jmpClearPlayer(mp); jmpSetNewOrder(mp, order); if (mp->order == jsetNotSet) { JSS_UNLOCK(mp); JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Could not start playing from given order #%i\n", order); } JSS_UNLOCK(mp); return DMERR_OK; } int jmpPlayOrder(JSSPlayer * mp, int order) { int result; assert(mp != NULL); JSS_LOCK(mp); assert(mp->module != NULL); // Stop if already playing jmpStop(mp); jmpClearChannels(mp); jmpClearPlayer(mp); // Check starting order jmpSetNewOrder(mp, order); if (mp->order == jsetNotSet) { JSS_UNLOCK(mp); JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Could not start playing from given order #%i\n", order); } if ((result = jmpPlayStart(mp)) != DMERR_OK) { JSS_UNLOCK(mp); return result; } JSS_UNLOCK(mp); return DMERR_OK; } /* Play given pattern */ int jmpPlayPattern(JSSPlayer * mp, int pattern) { int result; assert(mp != NULL); JSS_LOCK(mp); assert(mp->module != NULL); // Stop if already playing jmpStop(mp); jmpClearPlayer(mp); mp->npattern = pattern; if ((result = jmpPlayStart(mp)) != DMERR_OK) { JSS_UNLOCK(mp); return result; } JSS_UNLOCK(mp); return DMERR_OK; } /* Set volume for given module channel. */ static void jmpSetVolume(JSSPlayerChannel * chn, int channel, int volume) { (void) channel; chn->volume = chn->cvolume = dmClamp(volume, mpMinVol, mpMaxVol); JMPSETNDFLAGS(cdfNewVolume); } #define jmpChangeVolume(Q, Z, X) jmpSetVolume(Q, Z, chn->volume + (X)) /* Change the pitch of given channel by ADelta. */ static void jmpChangePitch(JSSPlayerChannel *chn, int channel, int delta) { int value; (void) channel; // Calculate new pitch and check it value = chn->pitch + delta; if (value < 0) value = 0; chn->pitch = value; JMPSETNDFLAGS(cdfNewPitch); } /* Do a note portamento (pitch slide) effect for given module channel. */ static void jmpDoPortamento(JSSPlayerChannel * chn, int channel) { (void) channel; // Check for zero parameter if (chn->iLastPortaToNoteParam == 0) { JMPSETNDFLAGS(cdfNewPitch); return; } /* Slide the pitch of channel to the destination value * with speed of iLastPortaToNoteParam[] * 4 and stop when it equals. */ if (chn->pitch < chn->iLastPortaToNotePitch) { // Increase pitch UP jmpChangePitch(chn, channel, chn->iLastPortaToNoteParam * 4); if (chn->pitch > chn->iLastPortaToNotePitch) chn->pitch = chn->iLastPortaToNotePitch; } else if (chn->pitch > chn->iLastPortaToNotePitch) { // Decrease pitch DOWN jmpChangePitch(chn, channel, -(chn->iLastPortaToNoteParam * 4)); if (chn->pitch < chn->iLastPortaToNotePitch) chn->pitch = chn->iLastPortaToNotePitch; } } /* Do a tremolo effect for given module channel. */ static void jmpDoTremolo(JSSPlayerChannel *chn, int channel) { (void) channel; if (chn->tremolo.depth != 0 && chn->tremolo.speed != 0) { int delta, tmp = chn->tremolo.pos & 31; switch (chn->tremolo.wc & 3) { case 0: delta = jmpSineTab[tmp]; break; case 1: tmp <<= 3; delta = (chn->tremolo.pos < 0) ? 255 - tmp : tmp; break; case 2: delta = 255; break; case 3: default: delta = jmpSineTab[tmp]; break; } delta = (delta * chn->tremolo.depth) >> 6; jmpCSetVolume(chn, channel, chn->cvolume + (chn->tremolo.pos >= 0 ? delta : -delta)); chn->tremolo.pos += chn->tremolo.speed; if (chn->tremolo.pos > 31) chn->tremolo.pos -= 64; } } /* Do a vibrato effect for given module channel. */ static void jmpDoVibrato(JSSPlayerChannel *chn, int channel) { (void) channel; if (chn->vibrato.depth != 0 && chn->vibrato.speed != 0) { int delta, tmp = chn->vibrato.pos & 31; switch (chn->vibrato.wc & 3) { case 0: delta = jmpSineTab[tmp]; break; case 1: tmp <<= 3; delta = (chn->vibrato.pos < 0) ? 255 - tmp : tmp; break; case 2: delta = 255; break; case 3: default: delta = jmpSineTab[tmp]; break; } delta = ((delta * chn->vibrato.depth) >> 7) << 2; chn->freq = chn->cfreq + (chn->vibrato.pos >= 0 ? delta : -delta); JMPSETNDFLAGS(cdfNewFreq); chn->vibrato.pos += chn->vibrato.speed; if (chn->vibrato.pos > 31) chn->vibrato.pos -= 64; } } /* Do a volume slide effect for given module channel. */ static void jmpDoVolumeSlide(JSSPlayerChannel * chn, int channel, int param) { int paramX, paramY; JMPMAKEPARAM(param, paramX, paramY) if (paramY == 0) jmpChangeVolume(chn, channel, paramX); if (paramX == 0) jmpChangeVolume(chn, channel, -paramY); } static void jmpTriggerNote(JSSPlayer * mp, JSSPlayerChannel *chn, BOOL newExtInstrument); static void jmpDoMultiRetrigNote(JSSPlayer *mp, JSSPlayerChannel *chn, int channel) { if (chn->lastMultiRetrigParamY != 0 && (mp->tick % chn->lastMultiRetrigParamY) == 0) { BOOL change = TRUE; int volume = chn->volume; switch (chn->lastMultiRetrigParamX) { case 0x1: volume -= 1; break; case 0x2: volume -= 2; break; case 0x3: volume -= 4; break; case 0x4: volume -= 8; break; case 0x5: volume -= 16; break; case 0x6: volume = (volume * 2) / 3; break; case 0x7: volume /= 2; break; case 0x9: volume += 1; break; case 0xA: volume += 2; break; case 0xB: volume += 4; break; case 0xC: volume += 8; break; case 0xD: volume += 16; break; case 0xE: volume = (volume * 3) / 2; break; case 0xF: volume *= 2; break; default: change = FALSE; } jmpTriggerNote(mp, chn, FALSE); if (change) jmpSetVolume(chn, channel, volume); } } /* Execute a pattern loop effect/command for given module channel. * * This routine works for most of the supported formats, as they * use the 'standard' implementation ascending from MOD. However, * here is included also a slightly kludgy implementation of the * FT2 patloop bug. */ static void jmpDoPatternLoop(JSSPlayer * mp, JSSPlayerChannel *chn, int channel, int paramY) { (void) channel; // Check what we need to do if (paramY > 0) { // SBx/E6x loops 'x' times if (chn->iPatLoopCount == 1) chn->iPatLoopCount = 0; else { // Check if we need to set the count if (chn->iPatLoopCount == 0) chn->iPatLoopCount = paramY + 1; // Loop to specified row chn->iPatLoopCount--; mp->newRow = chn->iPatLoopRow; mp->newRowSet = TRUE; } } else { // SB0/E60 sets the loop start point chn->iPatLoopRow = mp->row; // This is here because of the infamous FT2 patloop bug mp->lastPatLoopRow = mp->row; } } /* Do arpeggio effect */ static void jmpDoArpeggio(JSSPlayer * mp, JSSPlayerChannel *chn, int channel, int paramY, int paramX) { JSSInstrument *inst = chn->instrument; (void) channel; if (inst != NULL) { int tmp = chn->note; if (tmp == jsetNotSet || tmp == jsetNoteOff) return; switch (mp->tick & 3) { case 1: tmp += paramX; break; case 2: tmp += paramY; break; } tmp = dmClamp(tmp + inst->ERelNote, 0, 119); jmpCSetPitch(mp, chn, jmpGetPeriodFromNote(mp, tmp, inst->EFineTune)); } } /* Trigger a new note on the given channel. * Separate function used from various places where note * triggering is needed (retrig, multi-retrig, etc.) */ static void jmpTriggerNote(JSSPlayer * mp, JSSPlayerChannel *chn, BOOL newExtInstrument) { if (chn->nextInstrument >= 0 && chn->nextInstrument < mp->module->nextInstruments && mp->module->extInstruments[chn->nextInstrument] != NULL) { chn->extInstrument = mp->module->extInstruments[chn->nextInstrument]; } else { chn->extInstrument = NULL; chn->instrument = NULL; chn->ninstrument = jsetNotSet; } if (chn->extInstrument != NULL) { int tmp = chn->extInstrument->sNumForNotes[chn->note]; if (tmp >= 0 && tmp < mp->module->ninstruments && mp->module->instruments[tmp] != NULL) { if (chn->ninstrument != tmp) JMPSETNDFLAGS(cdfNewInstr); else JMPSETNDFLAGS(cdfPlay); chn->ninstrument = tmp; chn->instrument = mp->module->instruments[tmp]; if (newExtInstrument) { chn->volume = chn->instrument->volume; chn->panning = chn->instrument->EPanning; JMPSETNDFLAGS(cdfNewPanPos | cdfNewVolume); } } } if (chn->instrument != NULL) { int tmp; JSSInstrument *inst = chn->instrument; // Save old pitch for later use chn->oldPitch = chn->pitch; chn->position = 0; // Compute new pitch tmp = dmClamp(chn->note + inst->ERelNote, 0, 119); chn->pitch = jmpGetPeriodFromNote(mp, tmp, inst->EFineTune); JMPSETNDFLAGS(cdfNewPitch | cdfPlay | cdfNewPos); } else { chn->volume = 0; chn->panning = jchPanMiddle; JMPSETNDFLAGS(cdfStop | cdfNewPanPos | cdfNewVolume); } } /* * Process a new pattern row */ static void jmpProcessNewRow(JSSPlayer * mp, int channel) { JSSNote *currNote; BOOL newNote = FALSE, newExtInstrument = FALSE, volumePortaSet = FALSE; char effect; int param, paramX, paramY; JSSPlayerChannel *chn = &(mp->channels[channel]); JMPGETNOTE(currNote, mp->row, channel); // Check for a new note/keyoff here if (currNote->note == jsetNoteOff) chn->keyOff = TRUE; else if (currNote->note >= 0 && currNote->note <= 96) { newNote = TRUE; chn->oldNote = chn->note; chn->note = currNote->note; chn->keyOff = FALSE; } // Check for new instrument if (currNote->instrument != jsetNotSet) { /* Envelopes and ext.instrument fadeout are initialized always if * new instrument is set, even if the instrument does not exist. */ jmpResetEnvelope(&chn->volumeEnv); jmpResetEnvelope(&chn->panningEnv); chn->keyOff = FALSE; chn->fadeOutVol = mpMaxFadeoutVol; JMPSETNDFLAGS(cdfNewPanPos | cdfPlay | cdfNewVolume); // We save the instrument number here for later use chn->nextInstrument = currNote->instrument; newExtInstrument = TRUE; } if (newNote) { jmpTriggerNote(mp, chn, newExtInstrument); } // Process the volume column JMPMAKEPARAM(currNote->volume, paramX, paramY); switch (paramX) { case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: jmpSetVolume(chn, channel, currNote->volume); break; case 0x7: // Dx = Fine Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(chn, channel, -paramY); break; case 0x8: // Ux = Fine Volumeslide Up : IMPL.VERIFIED jmpChangeVolume(chn, channel, paramY); break; case 0x9: // Sx = Set vibrato speed : IMPL.VERIFIED chn->vibrato.speed = paramY; break; case 0xa: // Vx = Vibrato : IMPL.VERIFIED if (paramY) chn->vibrato.depth = paramY; break; case 0xe: // Mx = Porta To Note : IMPL.VERIFIED if (paramY) chn->iLastPortaToNoteParam = paramY * 16; if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) { chn->lastPortaToNoteNote = chn->note; chn->iLastPortaToNotePitch = chn->pitch; chn->pitch = chn->oldPitch; chn->note = chn->oldNote; JMPUNSETNDFLAGS(cdfNewPitch | cdfPlay); volumePortaSet = TRUE; } break; } // ...And finally process the Normal effects if (currNote->effect == jsetNotSet) return; param = currNote->param; JMPMAKEPARAM(param, paramX, paramY); JMPGETEFFECT(effect, currNote->effect); switch (effect) { case '0': // 0xy = Arpeggio jmpDoArpeggio(mp, chn, channel, paramX, paramY); break; case 'W': // Used widely in demo-music as MIDAS Sound System sync-command case 'Q': // SoundTracker/OpenCP: Qxx = Set LP filter resonance case 'Z': // SoundTracker/OpenCP: Zxx = Set LP filter cutoff freq break; case '1': case '2': // 1xy = Portamento Up, 2xy = Portamento Down : IMPL.VERIFIED if (param) chn->iLastPortaParam = param; break; case '3': // 3xy = Porta To Note if (volumePortaSet) break; if (param) chn->iLastPortaToNoteParam = param; if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) { chn->lastPortaToNoteNote = chn->note; chn->iLastPortaToNotePitch = chn->pitch; chn->pitch = chn->oldPitch; chn->note = chn->oldNote; JMPUNSETNDFLAGS(cdfNewPitch | cdfPlay); } break; case '4': // 4xy = Vibrato : IMPL.VERIFIED if (paramX) chn->vibrato.speed = paramX; if (paramY) chn->vibrato.depth = paramY; if ((chn->vibrato.wc & 4) == 0) chn->vibrato.pos = 0; break; case '5': // 5xy = Portamento + Volume Slide case '6': // 6xy = Vibrato + Volume slide if (param) chn->iLastVolSlideParam = param; break; case '7': // 7xy = Tremolo if (paramX) chn->tremolo.speed = paramX; if (paramY) chn->tremolo.depth = paramY; if ((chn->tremolo.wc & 4) == 0) chn->tremolo.pos = 0; break; case '8': // 8xx = Set Panning chn->panning = param; JMPSETNDFLAGS(cdfNewPanPos); break; case '9': // 9xx = Set Sample Offset : IMPL.VERIFIED if (param != 0) chn->lastSampleOffsetParam = param; if (chn->newDataFlags & cdfNewPitch && chn->instrument != NULL) { int pos = chn->lastSampleOffsetParam * 0x100, end = (chn->instrument->flags & jsfLooped) ? chn->instrument->loopE : chn->instrument->size; if (pos <= end) { chn->position = pos; JMPSETNDFLAGS(cdfNewPos); } else { JMPSETNDFLAGS(cdfStop); } } break; case 'A': // Axy = Volume Slide : IMPL.VERIFIED if (param) chn->iLastVolSlideParam = param; break; case 'B': // Bxx = Pattern Jump : IMPL.VERIFIED mp->newOrder = param; mp->newOrderSet = TRUE; mp->jumpFlag = TRUE; mp->lastPatLoopRow = 0; break; case 'C': // Cxx = Set Volume : IMPL.VERIFIED jmpSetVolume(chn, channel, param); break; case 'D': // Dxx = Pattern Break : IMPL.VERIFIED // Compute the new row mp->newRow = (paramX * 10) + paramY; if (mp->newRow >= mp->pattern->nrows) mp->newRow = 0; mp->newRowSet = TRUE; // Now we do some tricky tests if (!mp->breakFlag && !mp->jumpFlag) { mp->newOrder = mp->order + 1; mp->newOrderSet = TRUE; } mp->breakFlag = TRUE; break; case 'E': // Exy = Special Effects switch (paramX) { case 0x00: // E0x - Set filter (NOT SUPPORTED) JMPDEBUG("Set Filter used, UNSUPPORTED"); break; case 0x01: // E1x - Fine Portamento Up if (paramY) chn->iCLastFinePortamentoUpParam = paramY; jmpChangePitch(chn, channel, -(chn->iCLastFinePortamentoUpParam * 4)); break; case 0x02: // E2x - Fine Portamento Down if (paramY) chn->iCLastFinePortamentoDownParam = paramY; jmpChangePitch(chn, channel, (chn->iCLastFinePortamentoDownParam * 4)); break; case 0x03: // E3x - Glissando Control (NOT SUPPORTED) break; case 0x04: // E4x - Set Vibrato waveform chn->vibrato.wc = paramY; break; case 0x05: // E5x - Set Finetune JMPDEBUG("Set Finetune used, UNIMPLEMENTED"); break; case 0x06: // E6x - Set Pattern Loop jmpDoPatternLoop(mp, chn, channel, paramY); break; case 0x07: // E7x - Set Tremolo waveform chn->tremolo.wc = paramY; break; case 0x08: // E8x - Set Pan Position chn->panning = (paramY * 16); JMPSETNDFLAGS(cdfNewPanPos); break; case 0x09: // E9x - Retrig note if (mp->tick == paramY) jmpTriggerNote(mp, chn, FALSE); break; case 0x0a: // EAx - Fine Volumeslide Up if (paramY) chn->iCLastFineVolumeslideUpParam = paramY; jmpChangeVolume(chn, channel, chn->iCLastFineVolumeslideUpParam); break; case 0x0b: // EBx - Fine Volumeslide Down if (paramY) chn->iCLastFineVolumeslideDownParam = paramY; jmpChangeVolume(chn, channel, -(chn->iCLastFineVolumeslideDownParam)); break; case 0x0c: // ECx - Set Note Cut (NOT PROCESSED IN TICK0) break; case 0x0d: // EDx - Set Note Delay : IMPL.VERIFIED if (paramY > 0) { // Save the ND-flags, then clear chn->iSaveNDFlags = chn->newDataFlags; chn->newDataFlags = 0; // TODO .. does this only affect NOTE or also instrument? } break; case 0x0e: // EEx - Set Pattern Delay : IMPL.VERIFIED mp->patternDelay = paramY; break; case 0x0f: // EFx - Invert Loop (NOT SUPPORTED) JMPDEBUG("Invert Loop used, UNSUPPORTED"); break; default: JMPDEBUG("Unsupported special command used"); } break; case 'F': // Fxy = Set Speed / Tempo : IMPL.VERIFIED if (param > 0) { if (param < 0x20) mp->speed = param; else jmpSetTempo(mp, param); } break; case 'G': // Gxx = Global Volume mp->globalVol = param; JMPSETNDFLAGS(cdfNewGlobalVol); break; case 'H': // Hxx = Global Volume Slide JMPDEBUG("Global Volume Slide used, UNIMPLEMENTED"); break; case 'K': // Kxx = Key-off (Same as key-off note) chn->keyOff = TRUE; break; case 'L': // Lxx = Set Envelope Position JMPDEBUG("Set Envelope Position used, NOT verified with FT2"); chn->panningEnv.frame = param; chn->volumeEnv.frame = param; chn->panningEnv.exec = TRUE; chn->volumeEnv.exec = TRUE; break; case 'R': // Rxy = Multi Retrig note if (paramX != 0) chn->lastMultiRetrigParamX = paramX; if (paramY != 0) chn->lastMultiRetrigParamY = paramY; break; case 'T': // Txy = Tremor if (param) chn->iLastTremorParam = param; break; case 'X': // Xxy = Extra Fine Portamento switch (paramX) { case 0x01: // X1y - Extra Fine Portamento Up if (paramY) chn->iCLastExtraFinePortamentoUpParam = paramY; jmpChangePitch(chn, channel, - chn->iCLastExtraFinePortamentoUpParam); break; case 0x02: // X2y - Extra Fine Portamento Down if (paramY) chn->iCLastExtraFinePortamentoDownParam = paramY; jmpChangePitch(chn, channel, chn->iCLastExtraFinePortamentoUpParam); break; default: JMPDEBUG("Unsupported value in Extra Fine Portamento command!"); break; } break; default: JMPDEBUG("Unsupported effect"); break; } } static void jmpProcessEffects(JSSPlayer * mp, int channel) { JSSPlayerChannel *chn = &(mp->channels[channel]); JSSNote *currNote; int param, paramX, paramY, tmp; char effect; // Process the volume column effects JMPGETNOTE(currNote, mp->row, channel); JMPMAKEPARAM(currNote->volume, paramX, paramY); switch (paramX) { case 0x05: // -x = Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(chn, channel, -paramY); break; case 0x06: // +x = Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(chn, channel, paramY); break; case 0x0a: // Vx = Vibrato : IMPL.VERIFIED jmpDoVibrato(chn, channel); break; case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED jmpDoPortamento(chn, channel); break; } // ...And finally process the Normal effects if (currNote->effect == jsetNotSet) return; param = currNote->param; JMPMAKEPARAM(param, paramX, paramY); JMPGETEFFECT(effect, currNote->effect); switch (effect) { case '0': // 0xy = Arpeggio jmpDoArpeggio(mp, chn, channel, paramX, paramY); break; case '1': // 1xy = Portamento Up if (chn->iLastPortaParam > 0) jmpChangePitch(chn, channel, -(chn->iLastPortaParam * 4)); break; case '2': // 2xy = Portamento Down if (chn->iLastPortaParam > 0) jmpChangePitch(chn, channel, (chn->iLastPortaParam * 4)); break; case '3': // 3xy = Porta To Note jmpDoPortamento(chn, channel); break; case '4': // 4xy = Vibrato jmpDoVibrato(chn, channel); break; case '5': // 5xy = Portamento + Volume Slide jmpDoPortamento(chn, channel); jmpDoVolumeSlide(chn, channel, chn->iLastVolSlideParam); break; case '6': // 6xy = Vibrato + Volume Slide jmpDoVibrato(chn, channel); jmpDoVolumeSlide(chn, channel, chn->iLastVolSlideParam); break; case '7': // 7xy = Tremolo jmpDoTremolo(chn, channel); break; case 'A': // Axy = Volume slide jmpDoVolumeSlide(chn, channel, chn->iLastVolSlideParam); break; case 'E': // Exy = Special Effects switch (paramX) { case 0x09: // E9x - Retrig note if (mp->tick == paramY) jmpTriggerNote(mp, chn, FALSE); break; case 0x0c: // ECx - Set Note Cut if (mp->tick == paramY) jmpSetVolume(chn, channel, jsetMinVol); break; case 0x0d: // EDx - Set Note Delay if (mp->tick == paramY) chn->newDataFlags = chn->iSaveNDFlags; break; } break; case 'R': // Rxy = Multi Retrig note jmpDoMultiRetrigNote(mp, chn, channel); break; case 'T': // Txy = Tremor JMPMAKEPARAM(chn->iLastTremorParam, paramX, paramY) paramX++; paramY++; tmp = chn->iTremorCount % (paramX + paramY); if (tmp < paramX) jmpCSetVolume(chn, channel, chn->cvolume); else jmpCSetVolume(chn, channel, jsetMinVol); chn->iTremorCount = tmp + 1; break; } } /* This is the main processing callback-loop of a module player. * It processes the ticks, calling the needed jmpProcessNewRow() * and jmpProcessEffects() methods for processing the module playing. */ void jmpExec(void *pDEV, void *pMP) { JSSPlayer *mp; // Check some things via assert() mp = (JSSPlayer *) pMP; JSS_LOCK(mp); (void) pDEV; // JSSMixer *dev = (JSSMixer *) pDEV; // Check if we are playing if (!mp->isPlaying) goto out; // Clear channel new data flags mp->jumpFlag = FALSE; mp->breakFlag = FALSE; for (int channel = 0; channel < jsetNChannels; channel++) mp->channels[channel].newDataFlags = 0; //fprintf(stderr, "1: tick=%d, order=%d, iPattern=%d, row=%d\n", mp->tick, mp->order, mp->npattern, mp->row); // Check for init-tick if (mp->tick == jsetNotSet) { // Initialize pattern mp->newRow = 0; mp->newRowSet = TRUE; mp->tick = mp->speed; mp->patternDelay = 0; } //fprintf(stderr, "2: tick=%d, order=%d, iPattern=%d, row=%d\n", mp->tick, mp->order, mp->npattern, mp->row); // Check if we are playing if (!mp->isPlaying) goto out; assert(mp->pattern); // Update the tick mp->tick++; if (mp->tick >= mp->speed) { // Re-init tick counter mp->tick = 0; // Check pattern delay if (mp->patternDelay > 0) mp->patternDelay--; else { // New pattern row if (mp->newRowSet) { mp->row = mp->newRow; mp->newRowSet = FALSE; } else mp->row++; // Check for end of pattern if (mp->row >= mp->pattern->nrows) { // Go to next order if (mp->order != jsetNotSet) jmpSetNewOrder(mp, mp->order + 1); else mp->isPlaying = FALSE; // Check for FT2 quirks if (JMPGETMODFLAGS(mp, jmdfFT2Replay)) mp->row = mp->lastPatLoopRow; else mp->row = 0; } if (!mp->isPlaying) goto out; // Check current order if (mp->newOrderSet) { jmpSetNewOrder(mp, mp->newOrder); mp->newOrderSet = FALSE; } //fprintf(stderr, "3: tick=%d, order=%d, iPattern=%d, row=%d\n", mp->tick, mp->order, mp->npattern, mp->row); if (!mp->isPlaying) goto out; // TICK #0: Process new row for (int channel = 0; channel < mp->module->nchannels; channel++) jmpProcessNewRow(mp, channel); } // patternDelay } // tick else { // Implement FT2's pattern delay-effect: don't update effects while on patdelay if (!JMPGETMODFLAGS(mp, jmdfFT2Replay) || (JMPGETMODFLAGS(mp, jmdfFT2Replay) && mp->patternDelay <= 0)) { // TICK n: Process the effects for (int channel = 0; channel < mp->module->nchannels; channel++) jmpProcessEffects(mp, channel); } } // Check if playing has stopped if (!mp->isPlaying) goto out; // Update player data to audio device/mixer for (int channel = 0; channel < mp->module->nchannels; channel++) { JSSPlayerChannel *chn = &mp->channels[channel]; // Process extended instruments jmpProcessExtInstrument(chn, channel); // Check NDFlags and update channel data int flags = chn->newDataFlags; if (!flags) continue; // Check if we stop? if (flags & cdfStop) { jvmStop(mp->device, channel); } else { // No, handle other flags if (flags & cdfNewInstr) { JSSInstrument *instr = chn->instrument; if (instr != NULL) { jvmSetSample(mp->device, channel, instr->data, instr->size, instr->loopS, instr->loopE, instr->flags); } } if (flags & cdfPlay) { jvmReset(mp->device, channel); jvmPlay(mp->device, channel); } if (flags & cdfNewPitch) jmpCSetPitch(mp, chn, chn->pitch); if (flags & (cdfNewFreq | cdfNewPitch)) jvmSetFreq(mp->device, channel, chn->freq); if (flags & cdfNewPos) jvmSetPos(mp->device, channel, chn->position); if (flags & cdfNewVolume) { BOOL init = flags & (cdfNewInstr | cdfPlay); jvmSetVolumeRamp(mp->device, channel, init ? 0 : jvmGetVolume(mp->device, channel), (chn->fadeOutVol * chn->volumeEnv.value * chn->volume) / (16 * 65536), init ? 5 : 0); } if (flags & cdfNewPanPos) { jvmSetPanRamp(mp->device, channel, jvmGetPan(mp->device, channel), chn->panning + (((chn->panningEnv.value - 32) * (128 - abs(chn->panning - 128))) / 32), 0); } if (flags & cdfNewGlobalVol) jvmSetGlobalVol(mp->device, mp->globalVol); } } out: JSS_UNLOCK(mp); }