Mercurial > hg > dmlib
view jssplr.c @ 0:32250b436bca
Initial re-import.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 28 Sep 2012 01:54:23 +0300 |
parents | |
children | ca0e00facb7b |
line wrap: on
line source
/* * miniJSS - Module playing routines * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2006-2009 Tecnic Software productions (TNSP) */ #include "jssplr.h" #include <string.h> #include <stdlib.h> // FIXME!! FIX ME! #include <math.h> /* Miscellaneous tables */ #define jmpNSineTable (256) static int *jmpSineTable = NULL; 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 */ int jmpNoteToAmigaPeriod(int note, int finetune) { int tmp = (note + finetune + 8); if (tmp < 0) tmp = 0; else if (tmp > 103) tmp = 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, int channel, int value) { assert(mp != NULL); assert(mp->pDevice != NULL); if (value > 0) { if (JMPGETMODFLAGS(mp, jmdfAmigaPeriods)) { // Frequency = 8363*1712 / Period //fprintf(stderr, "jmpCSetPitch::AMIGA(%d, %d)\n", channel, value); jvmSetFreq(mp->pDevice, channel, 14317456.0f / (double) value); } else { // Frequency = Frequency = 8363*2^((6*12*16*4 - Period) / (12*16*4)) //fprintf(stderr, "jmpCSetPitch::Linear(%d, %d)\n", channel, value); jvmSetFreq(mp->pDevice, channel, 8363.0f * pow(2.0f, (4608.0f - (double) value) / 768.0f)); } } } static void jmpCSetInstrument(JSSPlayer * mp, int channel) { JSSInstrument *instr; assert(mp != NULL); assert(mp->pDevice != NULL); instr = mp->iCInstrument[channel]; if (instr) { jvmSetSample(mp->pDevice, channel, instr->data, instr->size, instr->loopS, instr->loopE, instr->flags); } } static void jmpCSetVolume(JSSPlayer * mp, int channel, int volume) { assert(mp != NULL); assert(mp->pDevice != NULL); if (volume < 0) volume = 0; else if (volume > 64) volume = 64; //fprintf(stderr, "chn %d: vol=%d, fad=%d, env=%d\n", channel, volume, mp->iCFadeOutVol[channel], mp->iCVolEnv[channel]); jvmSetVolume(mp->pDevice, channel, (mp->iCFadeOutVol[channel] * mp->iCVolEnv[channel] * volume) / (16 * 65536)); } static void jmpCSetPanning(JSSPlayer * mp, int channel, int panning) { assert(mp != NULL); assert(mp->pDevice != NULL); jvmSetPan(mp->pDevice, channel, panning + (((mp->iCPanEnv[channel] - 32) * (128 - abs(panning - 128))) / 32)); } static void jmpCSetPosition(JSSPlayer * mp, int channel, int value) { assert(mp != NULL); assert(mp->pDevice != NULL); jvmSetPos(mp->pDevice, channel, value); } static void jmpCPlay(JSSPlayer * mp, int channel) { assert(mp != NULL); assert(mp->pDevice != NULL); jvmPlay(mp->pDevice, channel); } static void jmpCStop(JSSPlayer * mp, int channel) { assert(mp != NULL); assert(mp->pDevice != NULL); jvmStop(mp->pDevice, channel); } static int jmpFindEnvPoint(JSSEnvelope * env, int pos) { int i; for (i = 0; i < env->npoints - 1; i++) { if (env->points[i].frame <= pos && env->points[i + 1].frame >= pos) return i; } return -1; } static void jmpExecEnvelope(JSSEnvelope * env, BOOL keyOff, int * frames, BOOL * doExec, int * result) { int currPoint, delta; JSSEnvelopePoint *ipf1, *ipf2; // OK, find the current point based on frame currPoint = jmpFindEnvPoint(env, *frames); // Check if the envelope has ended if (currPoint < 0 && (env->flags & jenvfLooped) == 0) { *doExec = FALSE; return; } // Do the envelope looping here, if needed if ((env->flags & jenvfLooped) && *frames >= env->points[env->loopE].frame) { currPoint = env->loopS; *frames = env->points[currPoint].frame; } // If the current point is OK, then process the envelope if (currPoint >= 0) { // Linearly interpolate the value for given frame ipf1 = &env->points[currPoint]; ipf2 = &env->points[currPoint + 1]; delta = (ipf2->frame - ipf1->frame); if (delta > 0) *result = ipf1->value + ((ipf2->value - ipf1->value) * (*frames - ipf1->frame)) / delta; else *result = ipf1->value; } // The frame counter IS processed even if the envelope is not! if ((env->flags & jenvfSustain) && currPoint == env->sustain && env->points[currPoint].frame == env->points[env->sustain].frame) { if (keyOff) (*frames)++; } else (*frames)++; } static void jmpProcessExtInstrument(JSSPlayer * mp, int channel) { JSSExtInstrument *inst = mp->iCExtInstrument[channel]; // Get the instrument for envelope data if (!inst) return; // Process the autovibrato /* FIXME fix me FIX me!!! todo. */ // Process the volume envelope if (inst->volumeEnv.flags & jenvfUsed) { // Process the instrument volume fadeout if (mp->iCKeyOff[channel] && (mp->iCFadeOutVol[channel] > 0) && (inst->fadeOut > 0)) { int tmp = (mp->iCFadeOutVol[channel] - inst->fadeOut); if (tmp < 0) tmp = 0; mp->iCFadeOutVol[channel] = tmp; JMPSETNDFLAGS(cdfNewVolume); } if (mp->iCVolEnv_Exec[channel]) { // Execute the volume envelope jmpExecEnvelope(&(inst->volumeEnv), mp->iCKeyOff[channel], &(mp->iCVolEnv_Frames[channel]), &(mp->iCVolEnv_Exec[channel]), &(mp->iCVolEnv[channel])); JMPSETNDFLAGS(cdfNewVolume); } } else { // If the envelope is not used, set max volume if (mp->iCVolEnv[channel] != mpMaxVol) { mp->iCVolEnv[channel] = mpMaxVol; mp->iCFadeOutVol[channel] = mpMaxFadeoutVol; JMPSETNDFLAGS(cdfNewVolume); } } // Process the panning envelope if (inst->panningEnv.flags & jenvfUsed) { if (mp->iCPanEnv_Exec[channel]) { // Execute the panning envelope jmpExecEnvelope(&(inst->panningEnv), mp->iCKeyOff[channel], &(mp->iCPanEnv_Frames[channel]), &(mp->iCPanEnv_Exec[channel]), &(mp->iCPanEnv[channel])); JMPSETNDFLAGS(cdfNewPanPos); } } else { // If the envelope is not used, set center panning if (mp->iCPanEnv[channel] != mpPanCenter) { mp->iCPanEnv[channel] = mpPanCenter; JMPSETNDFLAGS(cdfNewPanPos); } } } /* * The player */ JSSPlayer *jmpInit(JSSMixer *pDevice) { JSSPlayer *mp; // Initialize global tables if (jmpSineTable == NULL) { int i; if ((jmpSineTable = dmMalloc(jmpNSineTable * sizeof(int))) == NULL) JSSERROR(DMERR_MALLOC, NULL, "Could not allocate memory for sinus table.\n"); for (i = 0; i < 256; i++) { float f = ((float) i * M_PI * 2.0f) / 256.0f; jmpSineTable[i] = (int) (sin(f) * 2048.0f); } } // Allocate a player structure mp = dmMalloc0(sizeof(JSSPlayer)); if (!mp) JSSERROR(DMERR_MALLOC, NULL, "Could not allocate memory for player structure.\n"); // Set variables mp->pDevice = 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 jmpResetEnvelopes(JSSPlayer * mp, int channel) { assert(mp != NULL); mp->iCPanEnv_Frames[channel] = mp->iCVolEnv_Frames[channel] = 0; mp->iCPanEnv_Exec[channel] = mp->iCVolEnv_Exec[channel] = TRUE; } /* Clear module player structure */ void jmpClearPlayer(JSSPlayer * mp) { int i; assert(mp != NULL); JSS_LOCK(mp); // Initialize general variables mp->iPatternDelay = 0; mp->newRowSet = FALSE; mp->newOrderSet = FALSE; mp->iTick = jsetNotSet; mp->iRow = 0; mp->iLastPatLoopRow = 0; // Initialize channel data memset(&(mp->iPatLoopRow), 0, sizeof(mp->iPatLoopRow)); memset(&(mp->iPatLoopCount), 0, sizeof(mp->iPatLoopCount)); memset(&mp->iLastPortaParam, 0, sizeof(mp->iLastPortaParam)); memset(&mp->iLastPortaToNoteParam, 0, sizeof(mp->iLastPortaToNoteParam)); memset(&mp->iLastPortaToNotePitch, 0, sizeof(mp->iLastPortaToNotePitch)); memset(&mp->iVibratoPos, 0, sizeof(mp->iVibratoPos)); memset(&mp->iVibratoSpeed, 0, sizeof(mp->iVibratoSpeed)); memset(&mp->iVibratoDepth, 0, sizeof(mp->iVibratoDepth)); memset(&mp->iVibratoWC, 0, sizeof(mp->iVibratoWC)); memset(&mp->iTremoloPos, 0, sizeof(mp->iTremoloPos)); memset(&mp->iTremoloSpeed, 0, sizeof(mp->iTremoloSpeed)); memset(&mp->iTremoloDepth, 0, sizeof(mp->iTremoloDepth)); memset(&mp->iTremoloWC, 0, sizeof(mp->iTremoloWC)); memset(&mp->iLastTremorParam, 0, sizeof(mp->iLastTremorParam)); memset(&mp->iTremorCount, 0, sizeof(mp->iTremorCount)); memset(&mp->iLastSampleOffset, 0, sizeof(mp->iLastSampleOffset)); memset(&mp->iLastRetrigParam, 0, sizeof(mp->iLastRetrigParam)); memset(&mp->iLastVolSlideParam, 0, sizeof(mp->iLastVolSlideParam)); memset(&mp->iRetrigNDFlags, 0, sizeof(mp->iRetrigNDFlags)); memset(&mp->iSaveNDFlags, 0, sizeof(mp->iSaveNDFlags)); memset(&mp->iCNewDataFlags, 0, sizeof(mp->iCNewDataFlags)); memset(&mp->iCPitch, 0, sizeof(mp->iCPitch)); memset(&mp->iCPosition, 0, sizeof(mp->iCPosition)); memset(&mp->iCVolume, 0, sizeof(mp->iCVolume)); memset(&mp->iCFadeOutVol, 0, sizeof(mp->iCFadeOutVol)); memset(&mp->iCVolEnv, 0, sizeof(mp->iCVolEnv)); memset(&mp->iCAutoVib_Frames, 0, sizeof(mp->iCAutoVib_Frames)); memset(&mp->iCPanEnv_Frames, 0, sizeof(mp->iCPanEnv_Frames)); memset(&mp->iCVolEnv_Frames, 0, sizeof(mp->iCVolEnv_Frames)); memset(&mp->iCLastFineVolumeslideUpParam, 0, sizeof(mp->iCLastFineVolumeslideUpParam)); memset(&mp->iCLastFineVolumeslideDownParam, 0, sizeof(mp->iCLastFineVolumeslideDownParam)); memset(&mp->iCLastExtraFinePortamentoUpParam, 0, sizeof(mp->iCLastExtraFinePortamentoUpParam)); memset(&mp->iCLastExtraFinePortamentoDownParam, 0, sizeof(mp->iCLastExtraFinePortamentoDownParam)); memset(&mp->iCLastFinePortamentoUpParam, 0, sizeof(mp->iCLastFinePortamentoUpParam)); memset(&mp->iCLastFinePortamentoDownParam, 0, sizeof(mp->iCLastFinePortamentoDownParam)); memset(&mp->iCPanEnv_Exec, 0, sizeof(mp->iCPanEnv_Exec)); memset(&mp->iCVolEnv_Exec, 0, sizeof(mp->iCVolEnv_Exec)); memset(&mp->iCKeyOff, 0, sizeof(mp->iCKeyOff)); for (i = 0; i < jsetNChannels; i++) { mp->iCNote[i] = jsetNotSet; mp->iCInstrument[i] = NULL; mp->iCInstrumentN[i] = jsetNotSet; mp->iCExtInstrument[i] = NULL; mp->iCExtInstrumentN[i] = jsetNotSet; mp->iCPanning[i] = mpPanCenter; mp->iCPanEnv[i] = mpPanCenter; } JSS_UNLOCK(mp); } /* Set module */ void jmpSetModule(JSSPlayer * mp, JSSModule * pModule) { assert(mp != NULL); JSS_LOCK(mp); jmpStop(mp); jmpClearPlayer(mp); mp->pModule = pModule; JSS_UNLOCK(mp); } /* Stop playing */ void jmpStop(JSSPlayer * mp) { assert(mp != NULL); JSS_LOCK(mp); if (mp->isPlaying) { // Remove callback jvmRemoveCallback(mp->pDevice); 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->pDevice, 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; int pattern; pattern = jsetOrderEnd; mp->iOrder = jsetNotSet; orderOK = FALSE; while (!orderOK) { if (order < 0 || order >= mp->pModule->norders) { jmpStop(mp); orderOK = TRUE; } else { pattern = mp->pModule->orderList[order]; if (pattern == jsetOrderSkip) { order++; } else if (pattern >= mp->pModule->npatterns || pattern == jsetOrderEnd) { jmpStop(mp); orderOK = TRUE; } else { // All OK orderOK = TRUE; mp->pPattern = mp->pModule->patterns[pattern]; mp->iPattern = pattern; mp->iOrder = order; } } } } /* Set new tempo-value of the player. */ void jmpSetTempo(JSSPlayer * mp, int tempo) { assert(mp != NULL); JSS_LOCK(mp); assert(mp->pDevice != NULL); mp->iTempo = tempo; jvmSetCallbackFreq(mp->pDevice, (tempo * 2) / 5); JSS_UNLOCK(mp); } void jmpClearChannels(JSSPlayer * mp) { int i; assert(mp != NULL); JSS_LOCK(mp); assert(mp->pDevice != NULL); assert(mp->pModule != NULL); for (i = 0; i < mp->pModule->nchannels; i++) { jvmStop(mp->pDevice, i); jvmClear(mp->pDevice, i); } JSS_UNLOCK(mp); } /* Starts playing module from a given ORDER. */ int jmpPlayOrder(JSSPlayer * mp, int iStartOrder) { int result; assert(mp != NULL); JSS_LOCK(mp); assert(mp->pModule != NULL); // Stop if already playing jmpStop(mp); jmpClearChannels(mp); // Check starting order if (iStartOrder < 0 || iStartOrder >= mp->pModule->norders) { JSS_UNLOCK(mp); JSSERROR(DMERR_INVALID_ARGS, DMERR_INVALID_ARGS, "Invalid playing startorder given.\n"); } // Initialize playing jmpClearPlayer(mp); jmpSetNewOrder(mp, iStartOrder); if (mp->iOrder == jsetNotSet) { JSS_UNLOCK(mp); JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Could not start playing from given order #%i\n", iStartOrder); } mp->iSpeed = mp->pModule->defSpeed; jmpSetTempo(mp, mp->pModule->defTempo); // Set callback result = jvmSetCallback(mp->pDevice, jmpExec, (void *) mp); if (result != DMERR_OK) { JSS_UNLOCK(mp); JSSERROR(result, result, "Could not initialize callback for player.\n"); } mp->isPlaying = TRUE; JSS_UNLOCK(mp); return 0; } /* Play given pattern */ int jmpPlayPattern(JSSPlayer * mp, int pattern) { int result; assert(mp != NULL); JSS_LOCK(mp); assert(mp->pModule != NULL); // Stop if already playing jmpStop(mp); jmpClearChannels(mp); // Initialize playing jmpClearPlayer(mp); mp->iPattern = pattern; mp->iSpeed = mp->pModule->defSpeed; jmpSetTempo(mp, mp->pModule->defTempo); // Set callback result = jvmSetCallback(mp->pDevice, jmpExec, (void *) mp); if (result != DMERR_OK) { JSS_UNLOCK(mp); JSSERROR(result, result, "Could not initialize callback for player.\n"); } mp->isPlaying = TRUE; JSS_UNLOCK(mp); return 0; } /* Set volume for given module channel. */ static void jmpSetVolume(JSSPlayer * mp, int channel, int value) { // Check values if (value < mpMinVol) value = mpMinVol; else if (value > mpMaxVol) value = mpMaxVol; // Set the volume mp->iCVolume[channel] = value; JMPSETNDFLAGS(cdfNewVolume); } #define jmpChangeVolume(Q, Z, X) jmpSetVolume(Q, Z, mp->iCVolume[channel] + (X)) /* Change the pitch of given channel by ADelta. */ static void jmpChangePitch(JSSPlayer * mp, int channel, int delta) { int value; // Calculate new pitch and check it value = (mp->iCPitch[channel] + delta); if (value < 0) value = 0; // Set the new pitch mp->iCPitch[channel] = value; JMPSETNDFLAGS(cdfNewPitch); } /* Do a note portamento (pitch slide) effect for given module channel. */ static void jmpDoPortamento(JSSPlayer * mp, int channel) { // Check for zero parameter if (mp->iLastPortaToNoteParam[channel] == 0) { JMPSETNDFLAGS(cdfNewPitch); return; } /* Slide the pitch of channel to the destination value * with speed of iLastPortaToNoteParam[] * 4 and stop when it equals. */ if (mp->iCPitch[channel] != mp->iLastPortaToNotePitch[channel]) { if (mp->iCPitch[channel] < mp->iLastPortaToNotePitch[channel]) { // Increase pitch UP jmpChangePitch(mp, channel, mp->iLastPortaToNoteParam[channel] * 4); if (mp->iCPitch[channel] > mp->iLastPortaToNotePitch[channel]) mp->iCPitch[channel] = mp->iLastPortaToNotePitch[channel]; } else { // Decrease pitch DOWN jmpChangePitch(mp, channel, -(mp->iLastPortaToNoteParam[channel] * 4)); if (mp->iCPitch[channel] < mp->iLastPortaToNotePitch[channel]) mp->iCPitch[channel] = mp->iLastPortaToNotePitch[channel]; } } } /* Do a tremolo effect for given module channel. */ static void jmpDoTremolo(JSSPlayer * mp, int channel) { int delta, pos, depth; // Check settings if (mp->iTremoloDepth[channel] == 0 || mp->iTremoloSpeed[channel] == 0) return; // Get position of tremolo waveform pos = mp->iTremoloPos[channel] & 255; depth = mp->iTremoloDepth[channel]; switch (mp->iTremoloWC[channel] & 3) { case 0: // Sine-wave delta = (jmpSineTable[pos] * depth) / 2048; break; case 1: // Ramp down delta = ((pos - 128) * depth) / 128; break; case 2: // Square delta = (((pos & 128) - 64) * depth) / 64; break; default: return; } // Set the new volume jmpCSetVolume(mp, channel, mp->iCVolume[channel] + delta); // Advance tremolo waveform position mp->iTremoloPos[channel] += mp->iTremoloSpeed[channel]; if (mp->iTremoloPos[channel] > 255) mp->iTremoloPos[channel] = 0; } /* Do a vibrato effect for given module channel. */ static void jmpDoVibrato(JSSPlayer * mp, int channel) { int delta, pos, depth; // Check settings if (mp->iVibratoDepth[channel] == 0 || mp->iVibratoSpeed[channel] == 0) return; // Get position of vibrato waveform pos = mp->iVibratoPos[channel] & 255; depth = mp->iVibratoDepth[channel]; switch (mp->iVibratoWC[channel] & 3) { case 0: // Sine-wave delta = (jmpSineTable[pos] * depth) / 2048; break; case 1: // Ramp down delta = ((pos - 128) * depth) / 16; break; case 2: // Square delta = (((pos & 128) - 64) * depth) / 8; break; default: return; } // Set the new frequency jmpCSetPitch(mp, channel, mp->iCPitch[channel] + delta); // Advance vibrato waveform position mp->iVibratoPos[channel] += mp->iVibratoSpeed[channel]; if (mp->iVibratoPos[channel] > 255) mp->iVibratoPos[channel] = 0; } /* Do a volume slide effect for given module channel. */ static void jmpDoVolumeSlide(JSSPlayer * mp, int channel, int param) { int paramX, paramY; JMPMAKEPARAM(param, paramX, paramY) if (paramY == 0) jmpChangeVolume(mp, channel, paramX); if (paramX == 0) jmpChangeVolume(mp, channel, -paramY); } /* 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, int channel, int paramY) { // Check what we need to do if (paramY > 0) { // SBx/E6x loops 'x' times if (mp->iPatLoopCount[channel] == 1) mp->iPatLoopCount[channel] = 0; else { // Check if we need to set the count if (mp->iPatLoopCount[channel] == 0) mp->iPatLoopCount[channel] = (paramY + 1); // Loop to specified row mp->iPatLoopCount[channel]--; mp->iNewRow = mp->iPatLoopRow[channel]; mp->newRowSet = TRUE; } } else { // SB0/E60 sets the loop start point mp->iPatLoopRow[channel] = mp->iRow; // This is here because of the infamous FT2 patloop bug mp->iLastPatLoopRow = mp->iRow; } } /* Do arpeggio effect */ static void jmpDoArpeggio(JSSPlayer * mp, int channel, int paramY, int paramX) { JSSInstrument *tempInst = mp->iCInstrument[channel]; if (tempInst) { int tmp = mp->iCNote[channel]; if (tmp == jsetNotSet || tmp == jsetNoteOff) return; switch (mp->iTick & 3) { case 1: tmp += paramX; break; case 2: tmp += paramY; break; } jmpCSetPitch(mp, channel, jmpGetPeriodFromNote(mp, tmp + tempInst->ERelNote, tempInst->EFineTune)); } } /* * Process pattern effects */ static void jmpProcessRowEffect(JSSPlayer * mp, int channel, JSSNote * currNote) { int param, paramX, paramY; char effect; param = currNote->param; JMPMAKEPARAM(param, paramX, paramY); JMPGETEFFECT(effect, currNote->effect); switch (effect) { case '0': // 0xy = Arpeggio jmpDoArpeggio(mp, 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) mp->iLastPortaParam[channel] = param; break; case '3': // 3xy = Porta To Note if (param) mp->iLastPortaToNoteParam[channel] = param; if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) { mp->iLastPortaToNotePitch[channel] = mp->iCPitch[channel]; mp->iCPitch[channel] = mp->iCOldPitch[channel]; JMPUNSETNDFLAGS(cdfNewPitch | cdfNewInstr | cdfNewPanPos); } break; case '4': // 4xy = Vibrato : IMPL.VERIFIED if (paramX) mp->iVibratoSpeed[channel] = paramX; if (paramY) mp->iVibratoDepth[channel] = paramY; if ((mp->iVibratoWC[channel] & 4) == 0) mp->iVibratoPos[channel] = 0; break; case '5': // 5xy = Portamento + Volume Slide case '6': // 6xy = Vibrato + Volume slide if (param) mp->iLastVolSlideParam[channel] = param; break; case '7': // 7xy = Tremolo if (paramX) mp->iTremoloSpeed[channel] = paramX; if (paramY) mp->iTremoloDepth[channel] = paramY; if ((mp->iTremoloWC[channel] & 4) == 0) mp->iTremoloPos[channel] = 0; break; case '8': // 8xx = Set Panning JMPDEBUG("Set Panning used, UNIMPLEMENTED"); break; case '9': // 9xx = Set Sample Offset : IMPL.VERIFIED if (mp->iCNewDataFlags[channel] & cdfNewPitch) { mp->iCPosition[channel] = param * 0x100; JMPSETNDFLAGS(cdfNewPos); } break; case 'A': // Axy = Volume Slide : IMPL.VERIFIED if (param) mp->iLastVolSlideParam[channel] = param; break; case 'B': // Bxx = Pattern Jump : IMPL.VERIFIED mp->iNewOrder = param; mp->newOrderSet = TRUE; mp->jumpFlag = TRUE; mp->iLastPatLoopRow = 0; break; case 'C': // Cxx = Set Volume : IMPL.VERIFIED jmpSetVolume(mp, channel, param); break; case 'D': // Dxx = Pattern Break : IMPL.VERIFIED // Compute the new row mp->iNewRow = (paramX * 10) + paramY; if (mp->iNewRow >= mp->pPattern->nrows) mp->iNewRow = 0; mp->newRowSet = TRUE; // Now we do some tricky tests if (!mp->breakFlag && !mp->jumpFlag) { mp->iNewOrder = mp->iOrder + 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) mp->iCLastFinePortamentoUpParam[channel] = paramY; jmpChangePitch(mp, channel, -(mp->iCLastFinePortamentoUpParam[channel] * 4)); break; case 0x02: // E2x - Fine Portamento Down if (paramY) mp->iCLastFinePortamentoDownParam[channel] = paramY; jmpChangePitch(mp, channel, (mp->iCLastFinePortamentoDownParam[channel] * 4)); break; case 0x03: // E3x - Glissando Control (NOT SUPPORTED) break; case 0x04: // E4x - Set Vibrato waveform mp->iVibratoWC[channel] = paramY; break; case 0x05: // E5x - Set Finetune JMPDEBUG("Set Finetune used, UNIMPLEMENTED"); break; case 0x06: // E6x - Set Pattern Loop jmpDoPatternLoop(mp, channel, paramY); break; case 0x07: // E7x - Set Tremolo waveform mp->iTremoloWC[channel] = paramY; break; case 0x08: // E8x - Set Pan Position mp->iCPanning[channel] = (paramY * 16); JMPSETNDFLAGS(cdfNewPanPos); break; case 0x09: // E9x - Retrig note JMPDEBUG("Retrig Note used, UNIMPLEMENTED"); break; case 0x0a: // EAx - Fine Volumeslide Up if (paramY) mp->iCLastFineVolumeslideUpParam[channel] = paramY; jmpChangeVolume(mp, channel, mp->iCLastFineVolumeslideUpParam[channel]); break; case 0x0b: // EBx - Fine Volumeslide Down if (paramY) mp->iCLastFineVolumeslideDownParam[channel] = paramY; jmpChangeVolume(mp, channel, -(mp->iCLastFineVolumeslideDownParam[channel])); 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 mp->iSaveNDFlags[channel] = mp->iCNewDataFlags[channel]; mp->iCNewDataFlags[channel] = 0; // TODO .. does this only affect NOTE or also instrument? } break; case 0x0e: // EEx - Set Pattern Delay : IMPL.VERIFIED mp->iPatternDelay = 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->iSpeed = param; else jmpSetTempo(mp, param); } break; case 'G': // Gxx = Global Volume mp->iGlobalVol = 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) mp->iCKeyOff[channel] = TRUE; break; case 'L': // Lxx = Set Envelope Position JMPDEBUG("Set Envelope Position used, NOT verified with FT2"); mp->iCPanEnv_Frames[channel] = param; mp->iCVolEnv_Frames[channel] = param; mp->iCPanEnv_Exec[channel] = TRUE; mp->iCVolEnv_Exec[channel] = TRUE; break; case 'R': // Rxy = Multi Retrig note JMPDEBUG("Multi Retrig Note used, UNIMPLEMENTED"); break; case 'T': // Txy = Tremor if (param) mp->iLastTremorParam[channel] = param; break; case 'X': // Xxy = Extra Fine Portamento switch (paramX) { case 0x01: // X1y - Extra Fine Portamento Up if (paramY) mp->iCLastExtraFinePortamentoUpParam[param] = paramY; jmpChangePitch(mp, channel, -(mp->iCLastExtraFinePortamentoUpParam[param])); break; case 0x02: // X2y - Extra Fine Portamento Down if (paramY) mp->iCLastExtraFinePortamentoDownParam[param] = paramY; jmpChangePitch(mp, channel, mp->iCLastExtraFinePortamentoUpParam[param]); break; default: JMPDEBUG("Unsupported value in Extra Fine Portamento command!"); break; } break; default: JMPDEBUG("Unsupported effect"); break; } } static void jmpProcessNewRow(JSSPlayer * mp, int channel) { JSSNote *currNote; JSSExtInstrument *extInst = NULL; JSSInstrument *inst = NULL; BOOL newNote = FALSE; int tmp, paramX, paramY; JMPGETNOTE(currNote, mp->iRow, channel); // Check for a new note/keyoff here if (currNote->note == jsetNoteOff) mp->iCKeyOff[channel] = TRUE; else if (currNote->note >= 0 && currNote->note <= 96) { // New note was set newNote = TRUE; mp->iCNote[channel] = currNote->note; } // 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. */ jmpResetEnvelopes(mp, channel); mp->iCKeyOff[channel] = FALSE; mp->iCFadeOutVol[channel] = mpMaxFadeoutVol; // We save the instrument number here for later use if (currNote->instrument >= 0 && currNote->instrument < mp->pModule->nextInstruments) mp->iCExtInstrumentN[channel] = currNote->instrument; } /* ONLY if newNote was SET NOW and ExtInstrument HAS BEEN set, we can * set new pitches, and other things... */ if (newNote) { if (mp->iCExtInstrumentN[channel] != jsetNotSet) extInst = mp->pModule->extInstruments[mp->iCExtInstrumentN[channel]]; else extInst = NULL; if (extInst) { // Set instrument int note = mp->iCNote[channel]; mp->iCExtInstrument[channel] = extInst; // We set new Instrument ONLY if NEW NOTE has been set if (note != jsetNotSet) { // Get instrument number tmp = extInst->sNumForNotes[note]; if (tmp >= 0 && tmp < mp->pModule->ninstruments) { // Set the new instrument inst = mp->pModule->instruments[tmp]; mp->iCInstrumentN[channel] = tmp; mp->iCInstrument[channel] = inst; mp->iCVolume[channel] = inst->volume; mp->iCPanning[channel] = inst->EPanning; mp->iCPosition[channel] = 0; // Set NDFlags JMPSETNDFLAGS(cdfNewInstr | cdfNewPos | cdfNewPanPos | cdfNewVolume); } } } } if (inst) { // Save old pitch for later use mp->iCOldPitch[channel] = mp->iCPitch[channel]; // Compute new pitch tmp = (mp->iCNote[channel] + inst->ERelNote); //fprintf(stderr, "HEH: %d + %d = %d\n", mp->iCNote[channel], inst->ERelNote, tmp); if (tmp < 0) tmp = 0; else if (tmp > 119) tmp = 119; mp->iCPitch[channel] = jmpGetPeriodFromNote(mp, tmp, inst->EFineTune); JMPSETNDFLAGS(cdfNewPitch); } // Process the volume column JMPMAKEPARAM(currNote->volume, paramX, paramY); switch (paramX) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: jmpSetVolume(mp, channel, currNote->volume); break; case 0x07: // Dx = Fine Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(mp, channel, -paramY); break; case 0x08: // Ux = Fine Volumeslide Up : IMPL.VERIFIED jmpChangeVolume(mp, channel, paramY); break; case 0x09: // Sx = Set vibrato speed : IMPL.VERIFIED mp->iVibratoSpeed[channel] = paramY; break; case 0x0a: // Vx = Vibrato : IMPL.VERIFIED if (paramY) mp->iVibratoDepth[channel] = paramY; break; case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED if (paramY) mp->iLastPortaToNoteParam[channel] = paramY; if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) { mp->iLastPortaToNotePitch[channel] = mp->iCPitch[channel]; mp->iCPitch[channel] = mp->iCOldPitch[channel]; JMPUNSETNDFLAGS(cdfNewPitch | cdfNewInstr | cdfNewPanPos); } break; } // ...And finally process the Normal effects if (currNote->effect != jsetNotSet) jmpProcessRowEffect(mp, channel, currNote); } static void jmpProcessEffects(JSSPlayer * mp, int channel) { JSSNote *currNote; int param, paramX, paramY, tmp; char effect; // Process the volume column effects JMPGETNOTE(currNote, mp->iRow, channel); JMPMAKEPARAM(currNote->volume, paramX, paramY); switch (paramX) { case 0x05: // -x = Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(mp, channel, -paramY); break; case 0x06: // +x = Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(mp, channel, paramY); break; case 0x0a: // Vx = Vibrato : IMPL.VERIFIED jmpDoVibrato(mp, channel); break; case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED jmpDoPortamento(mp, 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, channel, paramX, paramY); break; case '1': // 1xy = Portamento Up if (mp->iLastPortaParam[channel] > 0) jmpChangePitch(mp, channel, -(mp->iLastPortaParam[channel] * 4)); break; case '2': // 2xy = Portamento Down if (mp->iLastPortaParam[channel] > 0) jmpChangePitch(mp, channel, (mp->iLastPortaParam[channel] * 4)); break; case '3': // 3xy = Porta To Note jmpDoPortamento(mp, channel); break; case '4': // 4xy = Vibrato jmpDoVibrato(mp, channel); break; case '5': // 5xy = Portamento + Volume Slide jmpDoPortamento(mp, channel); jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]); break; case '6': // 6xy = Vibrato + Volume Slide jmpDoVibrato(mp, channel); jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]); break; case '7': // 7xy = Tremolo jmpDoTremolo(mp, channel); break; case 'A': // Axy = Volume slide jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]); break; case 'E': // Exy = Special Effects switch (paramX) { case 0x0c: // ECx - Set Note Cut if (mp->iTick == paramY) jmpSetVolume(mp, channel, jsetMinVol); break; case 0x0d: // EDx - Set Note Delay if (mp->iTick == paramY) mp->iCNewDataFlags[channel] = mp->iSaveNDFlags[channel]; break; } break; case 'T': // Txy = Tremor JMPMAKEPARAM(mp->iLastTremorParam[channel], paramX, paramY) paramX++; paramY++; tmp = (mp->iTremorCount[channel] % (paramX + paramY)); if (tmp < paramX) jmpCSetVolume(mp, channel, mp->iCVolume[channel]); else jmpCSetVolume(mp, channel, jsetMinVol); mp->iTremorCount[channel] = (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; JSSMixer *dev; int channel, flags; // Check some things via assert() assert(pMP != NULL); mp = (JSSPlayer *) pMP; JSS_LOCK(mp); dev = (JSSMixer *) pDEV; assert(mp->pDevice == dev); assert(mp->pModule != NULL); // Check if we are playing if (!mp->isPlaying) goto out; // Clear channel new data flags mp->jumpFlag = FALSE; mp->breakFlag = FALSE; memset(mp->iCNewDataFlags, 0, sizeof(mp->iCNewDataFlags)); //fprintf(stderr, "1: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow); // Check for init-tick if (mp->iTick < 0) { // Initialize pattern if (mp->iOrder != jsetNotSet) jmpSetNewOrder(mp, mp->iOrder); mp->iNewRow = 0; mp->newRowSet = TRUE; mp->iTick = mp->iSpeed; } //fprintf(stderr, "2: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow); // Check if we are playing if (!mp->isPlaying) goto out; assert(mp->pPattern); // Update the tick mp->iTick++; if (mp->iTick >= mp->iSpeed) { // Re-init tick counter mp->iTick = 0; // Check pattern delay if (mp->iPatternDelay > 0) mp->iPatternDelay--; else { // New pattern row if (mp->newRowSet) { mp->iRow = mp->iNewRow; mp->newRowSet = FALSE; } else mp->iRow++; // Check for end of pattern if (mp->iRow >= mp->pPattern->nrows) { // Go to next order if (mp->iOrder != jsetNotSet) jmpSetNewOrder(mp, mp->iOrder + 1); else mp->isPlaying = FALSE; // Check for FT2 quirks if (JMPGETMODFLAGS(mp, jmdfFT2Replay)) mp->iRow = mp->iLastPatLoopRow; else mp->iRow = 0; } if (!mp->isPlaying) goto out; // Check current order if (mp->newOrderSet) { jmpSetNewOrder(mp, mp->iNewOrder); mp->newOrderSet = FALSE; } //fprintf(stderr, "3: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow); if (!mp->isPlaying) goto out; // TICK #0: Process new row for (channel = 0; channel < mp->pModule->nchannels; channel++) jmpProcessNewRow(mp, channel); } // iPatternDelay } // iTick else { // Implement FT2's pattern delay-effect: don't update effects while on patdelay if (!JMPGETMODFLAGS(mp, jmdfFT2Replay) || (JMPGETMODFLAGS(mp, jmdfFT2Replay) && mp->iPatternDelay <= 0)) { // TICK n: Process the effects for (channel = 0; channel < mp->pModule->nchannels; channel++) jmpProcessEffects(mp, channel); } } // Check if playing has stopped if (!mp->isPlaying) goto out; // Update player data to audio device/mixer for (channel = 0; channel < mp->pModule->nchannels; channel++) { // Process extended instruments jmpProcessExtInstrument(mp, channel); // Check NDFlags and update channel data flags = mp->iCNewDataFlags[channel]; if (flags) { // Check if we stop? if (flags & cdfStop) jmpCStop(mp, channel); else { // No, handle other flags if (flags & cdfNewInstr) { jmpCSetInstrument(mp, channel); jmpCPlay(mp, channel); } if (flags & cdfNewPitch) jmpCSetPitch(mp, channel, mp->iCPitch[channel]); if (flags & cdfNewPos) jmpCSetPosition(mp, channel, mp->iCPosition[channel]); if (flags & cdfNewVolume) jmpCSetVolume(mp, channel, mp->iCVolume[channel]); if (flags & cdfNewPanPos) jmpCSetPanning(mp, channel, mp->iCPanning[channel]); if (flags & cdfNewGlobalVol) jvmSetGlobalVol(mp->pDevice, mp->iGlobalVol); } } } out: JSS_UNLOCK(mp); }