Mercurial > hg > dmlib
view jssplr.c @ 65:03375aa0ef2b
Implement some new functions for bitmapped font handling.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 01 Oct 2012 11:20:56 +0300 |
parents | a33e47232161 |
children | ff0fe1d1ab3d |
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->device != NULL); if (value > 0) { if (JMPGETMODFLAGS(mp, jmdfAmigaPeriods)) { // Frequency = 8363*1712 / Period //fprintf(stderr, "jmpCSetPitch::AMIGA(%d, %d)\n", channel, value); jvmSetFreq(mp->device, 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->device, channel, 8363.0f * pow(2.0f, (4608.0f - (double) value) / 768.0f)); } } } static void jmpCSetVolume(JSSPlayer * mp, JSSPlayerChannel *chn, int channel, int volume) { assert(mp != NULL); assert(mp->device != NULL); if (volume < mpMinVol) volume = mpMinVol; else if (volume > mpMaxVol) volume = mpMaxVol; //fprintf(stderr, "chn %d: vol=%d, fad=%d, env=%d\n", channel, volume, chn->iCFadeOutVol, chn->iCVolEnv); jvmSetVolume(mp->device, channel, (chn->iCFadeOutVol * chn->iCVolEnv * volume) / (16 * 65536)); } static void jmpCSetPanning(JSSPlayer * mp, JSSPlayerChannel *chn, int channel, int panning) { assert(mp != NULL); assert(mp->device != NULL); jvmSetPan(mp->device, channel, panning + (((chn->iCPanEnv - 32) * (128 - abs(panning - 128))) / 32)); } static void jmpCSetPosition(JSSPlayer * mp, int channel, int value) { assert(mp != NULL); assert(mp->device != NULL); jvmSetPos(mp->device, channel, value); } static int jmpFindEnvPoint(JSSEnvelope * env, const 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(JSSPlayerChannel *chn, int channel) { JSSExtInstrument *inst = chn->iCExtInstrument; (void) 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 (chn->iCKeyOff && chn->iCFadeOutVol > 0 && inst->fadeOut > 0) { int tmp = chn->iCFadeOutVol - inst->fadeOut; if (tmp < 0) tmp = 0; chn->iCFadeOutVol = tmp; JMPSETNDFLAGS(cdfNewVolume); } if (chn->iCVolEnv_Exec) { // Execute the volume envelope jmpExecEnvelope(&(inst->volumeEnv), chn->iCKeyOff, &(chn->iCVolEnv_Frames), &(chn->iCVolEnv_Exec), &(chn->iCVolEnv)); JMPSETNDFLAGS(cdfNewVolume); } } else { // If the envelope is not used, set max volume if (chn->iCVolEnv != mpMaxVol) { chn->iCVolEnv = mpMaxVol; chn->iCFadeOutVol = mpMaxFadeoutVol; JMPSETNDFLAGS(cdfNewVolume); } } // Process the panning envelope if (inst->panningEnv.flags & jenvfUsed) { if (chn->iCPanEnv_Exec) { // Execute the panning envelope jmpExecEnvelope(&(inst->panningEnv), chn->iCKeyOff, &(chn->iCPanEnv_Frames), &(chn->iCPanEnv_Exec), &(chn->iCPanEnv)); JMPSETNDFLAGS(cdfNewPanPos); } } else { // If the envelope is not used, set center panning if (chn->iCPanEnv != mpPanCenter) { chn->iCPanEnv = 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->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 jmpResetEnvelopes(JSSPlayerChannel *chn) { chn->iCPanEnv_Frames = chn->iCVolEnv_Frames = 0; chn->iCPanEnv_Exec = chn->iCVolEnv_Exec = TRUE; } /* Clear module player structure */ void jmpClearChannel(JSSPlayerChannel *chn) { memset(chn, 0, sizeof(JSSPlayerChannel)); chn->iCNote = jsetNotSet; chn->iCInstrumentN = jsetNotSet; chn->iCExtInstrumentN = jsetNotSet; chn->iCPanning = mpPanCenter; chn->iCPanEnv = mpPanCenter; } void jmpClearPlayer(JSSPlayer * mp) { int i; 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 (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; int pattern; pattern = jsetOrderEnd; mp->order = jsetNotSet; orderOK = FALSE; while (!orderOK) { if (order < 0 || order >= mp->module->norders) { jmpStop(mp); orderOK = TRUE; } else { 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. */ void jmpSetTempo(JSSPlayer * mp, int tempo) { assert(mp != NULL); JSS_LOCK(mp); assert(mp->device != NULL); mp->tempo = tempo; jvmSetCallbackFreq(mp->device, (tempo * 2) / 5); JSS_UNLOCK(mp); } void jmpClearChannels(JSSPlayer * mp) { int i; assert(mp != NULL); JSS_LOCK(mp); assert(mp->device != NULL); assert(mp->module != NULL); for (i = 0; i < mp->module->nchannels; i++) { jvmStop(mp->device, i); jvmClear(mp->device, i); } JSS_UNLOCK(mp); } /* Starts playing module from a given ORDER. */ 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); // Check starting order if (order < 0 || order >= mp->module->norders) { JSS_UNLOCK(mp); JSSERROR(DMERR_INVALID_ARGS, DMERR_INVALID_ARGS, "Invalid playing startorder given.\n"); } // Initialize playing 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); } mp->speed = mp->module->defSpeed; jmpSetTempo(mp, mp->module->defTempo); // Set callback result = jvmSetCallback(mp->device, 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->module != NULL); // Stop if already playing jmpStop(mp); jmpClearChannels(mp); // Initialize playing jmpClearPlayer(mp); mp->npattern = pattern; mp->speed = mp->module->defSpeed; jmpSetTempo(mp, mp->module->defTempo); // Set callback result = jvmSetCallback(mp->device, 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(JSSPlayerChannel * chn, int channel, int volume) { (void) channel; // Check values if (volume < mpMinVol) volume = mpMinVol; else if (volume > mpMaxVol) volume = mpMaxVol; // Set the volume chn->iCVolume = volume; JMPSETNDFLAGS(cdfNewVolume); } #define jmpChangeVolume(Q, Z, X) jmpSetVolume(Q, Z, chn->iCVolume + (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->iCPitch + delta; if (value < 0) value = 0; // Set the new pitch chn->iCPitch = 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->iCPitch != chn->iLastPortaToNotePitch) { if (chn->iCPitch < chn->iLastPortaToNotePitch) { // Increase pitch UP jmpChangePitch(chn, channel, chn->iLastPortaToNoteParam * 4); if (chn->iCPitch > chn->iLastPortaToNotePitch) chn->iCPitch = chn->iLastPortaToNotePitch; } else { // Decrease pitch DOWN jmpChangePitch(chn, channel, -(chn->iLastPortaToNoteParam * 4)); if (chn->iCPitch < chn->iLastPortaToNotePitch) chn->iCPitch = chn->iLastPortaToNotePitch; } } } /* Do a tremolo effect for given module channel. */ static void jmpDoTremolo(JSSPlayer * mp, JSSPlayerChannel *chn, int channel) { int delta, pos, depth; // Check settings if (chn->iTremoloDepth == 0 || chn->iTremoloSpeed == 0) return; // Get position of tremolo waveform pos = chn->iTremoloPos & 255; depth = chn->iTremoloDepth; switch (chn->iTremoloWC & 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, chn, channel, chn->iCVolume + delta); // Advance tremolo waveform position chn->iTremoloPos += chn->iTremoloSpeed; if (chn->iTremoloPos > 255) chn->iTremoloPos = 0; } /* Do a vibrato effect for given module channel. */ static void jmpDoVibrato(JSSPlayer * mp, JSSPlayerChannel *chn, int channel) { int delta, pos, depth; // Check settings if (chn->iVibratoDepth == 0 || chn->iVibratoSpeed == 0) return; // Get position of vibrato waveform pos = chn->iVibratoPos & 255; depth = chn->iVibratoDepth; switch (chn->iVibratoWC & 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, chn->iCPitch + delta); // Advance vibrato waveform position chn->iVibratoPos += chn->iVibratoSpeed; if (chn->iVibratoPos > 255) chn->iVibratoPos = 0; } /* 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); } /* 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 *tempInst = chn->iCInstrument; if (tempInst) { int tmp = chn->iCNote; if (tmp == jsetNotSet || tmp == jsetNoteOff) return; switch (mp->tick & 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) { JSSPlayerChannel *chn = &(mp->channels[channel]); 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, 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 (param) chn->iLastPortaToNoteParam = param; if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) { chn->iLastPortaToNotePitch = chn->iCPitch; chn->iCPitch = chn->iCOldPitch; JMPUNSETNDFLAGS(cdfNewPitch | cdfNewInstr | cdfNewPanPos); } break; case '4': // 4xy = Vibrato : IMPL.VERIFIED if (paramX) chn->iVibratoSpeed = paramX; if (paramY) chn->iVibratoDepth = paramY; if ((chn->iVibratoWC & 4) == 0) chn->iVibratoPos = 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->iTremoloSpeed = paramX; if (paramY) chn->iTremoloDepth = paramY; if ((chn->iTremoloWC & 4) == 0) chn->iTremoloPos = 0; break; case '8': // 8xx = Set Panning JMPDEBUG("Set Panning used, UNIMPLEMENTED"); break; case '9': // 9xx = Set Sample Offset : IMPL.VERIFIED if (chn->iCNewDataFlags & cdfNewPitch) { chn->iCPosition = param * 0x100; JMPSETNDFLAGS(cdfNewPos); } 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->iVibratoWC = 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->iTremoloWC = paramY; break; case 0x08: // E8x - Set Pan Position chn->iCPanning = (paramY * 16); JMPSETNDFLAGS(cdfNewPanPos); break; case 0x09: // E9x - Retrig note JMPDEBUG("Retrig Note used, UNIMPLEMENTED"); 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->iCNewDataFlags; chn->iCNewDataFlags = 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->iCKeyOff = TRUE; break; case 'L': // Lxx = Set Envelope Position JMPDEBUG("Set Envelope Position used, NOT verified with FT2"); chn->iCPanEnv_Frames = param; chn->iCVolEnv_Frames = param; chn->iCPanEnv_Exec = TRUE; chn->iCVolEnv_Exec = TRUE; break; case 'R': // Rxy = Multi Retrig note JMPDEBUG("Multi Retrig Note used, UNIMPLEMENTED"); 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 jmpProcessNewRow(JSSPlayer * mp, int channel) { JSSNote *currNote; JSSExtInstrument *extInst = NULL; JSSInstrument *inst = NULL; BOOL newNote = FALSE; int tmp, paramX, paramY; JSSPlayerChannel *chn = &(mp->channels[channel]); JMPGETNOTE(currNote, mp->row, channel); // Check for a new note/keyoff here if (currNote->note == jsetNoteOff) chn->iCKeyOff = TRUE; else if (currNote->note >= 0 && currNote->note <= 96) { // New note was set newNote = TRUE; chn->iCNote = 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(chn); chn->iCKeyOff = FALSE; chn->iCFadeOutVol = mpMaxFadeoutVol; // We save the instrument number here for later use if (currNote->instrument >= 0 && currNote->instrument < mp->module->nextInstruments) chn->iCExtInstrumentN = currNote->instrument; } /* ONLY if newNote was SET NOW and ExtInstrument HAS BEEN set, we can * set new pitches, and other things... */ if (newNote) { if (chn->iCExtInstrumentN != jsetNotSet) extInst = mp->module->extInstruments[chn->iCExtInstrumentN]; else extInst = NULL; if (extInst) { // Set instrument int note = chn->iCNote; chn->iCExtInstrument = 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->module->ninstruments) { // Set the new instrument inst = mp->module->instruments[tmp]; chn->iCInstrumentN = tmp; chn->iCInstrument = inst; chn->iCVolume = inst->volume; chn->iCPanning = inst->EPanning; chn->iCPosition = 0; // Set NDFlags JMPSETNDFLAGS(cdfNewInstr | cdfNewPos | cdfNewPanPos | cdfNewVolume); } } } } if (inst) { // Save old pitch for later use chn->iCOldPitch = chn->iCPitch; // Compute new pitch tmp = (chn->iCNote + inst->ERelNote); //fprintf(stderr, "HEH: %d + %d = %d\n", chn->iCNote, inst->ERelNote, tmp); if (tmp < 0) tmp = 0; else if (tmp > 119) tmp = 119; chn->iCPitch = 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(chn, channel, currNote->volume); break; case 0x07: // Dx = Fine Volumeslide Down : IMPL.VERIFIED jmpChangeVolume(chn, channel, -paramY); break; case 0x08: // Ux = Fine Volumeslide Up : IMPL.VERIFIED jmpChangeVolume(chn, channel, paramY); break; case 0x09: // Sx = Set vibrato speed : IMPL.VERIFIED chn->iVibratoSpeed = paramY; break; case 0x0a: // Vx = Vibrato : IMPL.VERIFIED if (paramY) chn->iVibratoDepth = paramY; break; case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED if (paramY) chn->iLastPortaToNoteParam = paramY; if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) { chn->iLastPortaToNotePitch = chn->iCPitch; chn->iCPitch = chn->iCOldPitch; 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) { 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(mp, 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(mp, 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(mp, chn, channel); jmpDoVolumeSlide(chn, channel, chn->iLastVolSlideParam); break; case '7': // 7xy = Tremolo jmpDoTremolo(mp, chn, channel); break; case 'A': // Axy = Volume slide jmpDoVolumeSlide(chn, channel, chn->iLastVolSlideParam); break; case 'E': // Exy = Special Effects switch (paramX) { 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->iCNewDataFlags = chn->iSaveNDFlags; break; } break; case 'T': // Txy = Tremor JMPMAKEPARAM(chn->iLastTremorParam, paramX, paramY) paramX++; paramY++; tmp = (chn->iTremorCount % (paramX + paramY)); if (tmp < paramX) jmpCSetVolume(mp, chn, channel, chn->iCVolume); else jmpCSetVolume(mp, 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; JSSMixer *dev; int channel, flags; // Check some things via assert() assert(pMP != NULL); mp = (JSSPlayer *) pMP; JSS_LOCK(mp); dev = (JSSMixer *) pDEV; assert(mp->device == dev); assert(mp->module != NULL); // Check if we are playing if (!mp->isPlaying) goto out; // Clear channel new data flags mp->jumpFlag = FALSE; mp->breakFlag = FALSE; for (channel = 0; channel < jsetNChannels; channel++) mp->channels[channel].iCNewDataFlags = 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 < 0) { // Initialize pattern if (mp->order != jsetNotSet) jmpSetNewOrder(mp, mp->order); mp->newRow = 0; mp->newRowSet = TRUE; mp->tick = mp->speed; } //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 (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 (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 (channel = 0; channel < mp->module->nchannels; channel++) { JSSPlayerChannel *chn = &mp->channels[channel]; // Process extended instruments jmpProcessExtInstrument(chn, channel); // Check NDFlags and update channel data flags = chn->iCNewDataFlags; if (flags) { // Check if we stop? if (flags & cdfStop) { jvmStop(mp->device, channel); } else { // No, handle other flags if (flags & cdfNewInstr) { JSSInstrument *instr = chn->iCInstrument; if (instr != NULL) { jvmSetSample(mp->device, channel, instr->data, instr->size, instr->loopS, instr->loopE, instr->flags); } jvmPlay(mp->device, channel); } if (flags & cdfNewPitch) jmpCSetPitch(mp, channel, chn->iCPitch); if (flags & cdfNewPos) jmpCSetPosition(mp, channel, chn->iCPosition); if (flags & cdfNewVolume) jmpCSetVolume(mp, chn, channel, chn->iCVolume); if (flags & cdfNewPanPos) jmpCSetPanning(mp, chn, channel, chn->iCPanning); if (flags & cdfNewGlobalVol) jvmSetGlobalVol(mp->device, mp->globalVol); } } } out: JSS_UNLOCK(mp); }