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);
}