diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jssplr.c	Fri Sep 28 01:54:23 2012 +0300
@@ -0,0 +1,1593 @@
+/*
+ * 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);
+}