// C port of ProTracker 2.3D's replayer (with some modifications, but still accurate) // for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER#include <crtdbg.h>#endif #include <stdint.h>#include <stdbool.h>#include "pt2_audio.h"#include "pt2_helpers.h"#include "pt2_tables.h"#include "pt2_config.h"#include "pt2_visuals.h"#include "pt2_scopes.h"#include "pt2_paula.h"#include "pt2_visuals_sync.h"#include "pt2_posed.h" static bool posJumpAssert, pBreakFlag, modRenderDone;static bool doStopSong; // from F00 (Set Speed)static int8_t pBreakPosition, oldRow, modPattern;static uint8_t pattDelTime, lowMask = 0xFF, pattDelTime2;static int16_t modPos, oldPattern, oldPos;static uint16_t DMACONtemp;static int32_t modBPM, oldBPM, oldSpeed, ciaSetBPM; static const uint8_t funkTable[16] = // EFx (FunkRepeat/InvertLoop){ 0x00, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D, 0x10, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80}; void gotoNextMulti(void){ cursor.channel = (editor.multiModeNext[cursor.channel] - 1) & 3; cursor.pos = cursor.channel * 6; updateCursorPos();} double ciaBpm2Hz(int32_t bpm){ if (bpm == 0) return 0.0; const uint32_t ciaPeriod = 1773447 / bpm; // yes, PT truncates here return (double)CIA_PAL_CLK / (ciaPeriod+1); // +1, CIA triggers on underflow} void updatePaulaLoops(void) // used after manipulating sample loop points while Paula is live{ const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); for (int32_t i = 0; i < PAULA_VOICES; i++) { const moduleChannel_t *ch = &song->channels[i]; if (ch->n_samplenum == editor.currSample) // selected sample matches channel's sample? { const moduleSample_t *s = &song->samples[editor.currSample]; const uint32_t voiceAddr = 0xDFF0A0 + (i * 16); paulaWritePtr(voiceAddr + 0, ch->n_start + s->loopStart); paulaWriteWord(voiceAddr + 4, (uint16_t)(s->loopLength >> 1)); setVisualsDataPtr(i, ch->n_start + s->loopStart); setVisualsLength(i, (uint16_t)(s->loopLength >> 1)); } } if (audioWasntLocked) unlockAudio();} void turnOffVoices(void){ const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); paulaWriteWord(0xDFF096, 0x000F); // turn off all voice DMAs setVisualsDMACON(0x000F); // clear all volumes for (int32_t i = 0; i < PAULA_VOICES; i++) { const uint32_t voiceAddr = 0xDFF0A0 + (i * 16); paulaWriteWord(voiceAddr + 8, 0); setVisualsVolume(i, 0); } // reset dithering/filter states (so that every playback session is identical) resetAudioDithering(); if (audioWasntLocked) unlockAudio(); editor.tuningToneFlag = false;} void initializeModuleChannels(module_t *m){ assert(m != NULL); memset(m->channels, 0, sizeof (m->channels)); moduleChannel_t *ch = m->channels; for (uint8_t i = 0; i < PAULA_VOICES; i++, ch++) { ch->n_chanindex = i; ch->n_dmabit = 1 << i; }} void setReplayerPosToTrackerPos(void){ if (song == NULL) return; modPattern = (int8_t)song->currPattern; modPos = song->currPos; song->row = song->currRow; song->tick = 0;} module_t *createEmptyMod(void){ module_t *m = (module_t *)calloc(1, sizeof (module_t)); if (m == NULL) goto error; // allocate memory for all sample data blocks (+ 2 extra, for quirk + safety) const size_t allocLen = (MOD_SAMPLES + 2) * config.maxSampleLength; m->sampleData = (int8_t *)calloc(allocLen, 1); if (m->sampleData == NULL) goto error; for (int32_t i = 0; i < MAX_PATTERNS; i++) { m->patterns[i] = (note_t *)calloc(1, MOD_ROWS * sizeof (note_t) * PAULA_VOICES); if (m->patterns[i] == NULL) goto error; } m->header.songLength = 1; moduleSample_t *s = m->samples; for (int32_t i = 0; i < MOD_SAMPLES; i++, s++) { // setup GUI text pointers s->volumeDisp = &s->volume; s->lengthDisp = &s->length; s->loopStartDisp = &s->loopStart; s->loopLengthDisp = &s->loopLength; s->loopLength = 2; // sample data offsets (sample data = one huge buffer to rule them all) s->offset = config.maxSampleLength * i; } initializeModuleChannels(m); return m; error: if (m != NULL) { for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (m->patterns[i] != NULL) free(m->patterns[i]); } if (m->sampleData != NULL) free(m->sampleData); free(m); } return NULL;} void modSetSpeed(int32_t speed){ song->currSpeed = song->speed = speed; song->tick = 0;} void doStopIt(bool resetPlayMode){ editor.songPlaying = false; pattDelTime = pattDelTime2 = 0; if (resetPlayMode) { editor.playMode = PLAY_MODE_NORMAL; editor.currMode = MODE_IDLE; pointerSetModeThreadSafe(POINTER_MODE_IDLE, true); } if (song != NULL) { moduleChannel_t *ch = song->channels; for (int32_t i = 0; i < PAULA_VOICES; i++, ch++) { ch->n_wavecontrol = 0; ch->n_glissfunk = 0; ch->n_finetune = 0; ch->n_loopcount = 0; } } doStopSong = false; // just in case this flag was stuck from command F00 (stop song)} void setPattern(int16_t pattern){ if (pattern > MAX_PATTERNS-1) pattern = MAX_PATTERNS-1; song->currPattern = modPattern = (int8_t)pattern;} void storeTempVariables(void) // this one is accessed in other files, so non-static{ oldBPM = song->currBPM; oldRow = song->currRow; oldPos = song->currPos; oldSpeed = song->currSpeed; oldPattern = song->currPattern;} static void setVUMeterHeight(moduleChannel_t *ch){ if (editor.muted[ch->n_chanindex]) return; uint8_t vol = ch->n_volume; if ((ch->n_cmd & 0xF00) == 0xC00) // handle Cxx effect vol = ch->n_cmd & 0xFF; if (vol > 64) vol = 64; if (!editor.songPlaying) { editor.vuMeterVolumes[ch->n_chanindex] = vuMeterHeights[vol]; } else { ch->syncVuVolume = vol; ch->syncFlags |= UPDATE_VUMETER; }} static void updateFunk(moduleChannel_t *ch){ const int8_t funkSpeed = ch->n_glissfunk >> 4; if (funkSpeed == 0) return; ch->n_funkoffset += funkTable[funkSpeed]; if (ch->n_funkoffset >= 128) { ch->n_funkoffset = 0; if (ch->n_loopstart != NULL && ch->n_wavestart != NULL) // ProTracker bugfix { if (++ch->n_wavestart >= ch->n_loopstart + (ch->n_replen << 1)) ch->n_wavestart = ch->n_loopstart; *ch->n_wavestart = -1 - *ch->n_wavestart; } }} static void setGlissControl(moduleChannel_t *ch){ ch->n_glissfunk = (ch->n_glissfunk & 0xF0) | (ch->n_cmd & 0x0F);} static void setVibratoControl(moduleChannel_t *ch){ ch->n_wavecontrol = (ch->n_wavecontrol & 0xF0) | (ch->n_cmd & 0x0F);} static void setFineTune(moduleChannel_t *ch){ ch->n_finetune = ch->n_cmd & 0xF;} static void jumpLoop(moduleChannel_t *ch){ if (song->tick != 0) return; if ((ch->n_cmd & 0xF) == 0) { ch->n_pattpos = song->row; } else { if (ch->n_loopcount == 0) ch->n_loopcount = ch->n_cmd & 0xF; else if (--ch->n_loopcount == 0) return; pBreakPosition = ch->n_pattpos; pBreakFlag = true; // stuff used for MOD2WAV to determine if the song has reached its end if (editor.mod2WavOngoing) { for (int32_t tempParam = pBreakPosition; tempParam <= song->row; tempParam++) editor.rowVisitTable[(modPos * MOD_ROWS) + tempParam] = false; } }} static void setTremoloControl(moduleChannel_t *ch){ ch->n_wavecontrol = ((ch->n_cmd & 0xF) << 4) | (ch->n_wavecontrol & 0xF);} /* This is a little used effect, despite being present in original ProTracker.** E8x was sometimes entirely replaced with code used for demo fx syncing in** demo mod players, so it can be turned off by looking at DISABLE_E8X in** protracker.ini if you so desire.*/static void karplusStrong(moduleChannel_t *ch){ int8_t a, b; if (config.disableE8xEffect) return; if (ch->n_loopstart == NULL) return; // ProTracker bugfix int8_t *ptr8 = ch->n_loopstart; int16_t end = ((ch->n_replen * 2) & 0xFFFF) - 2; do { a = ptr8[0]; b = ptr8[1]; *ptr8++ = (a + b) >> 1; } while (--end >= 0); a = ptr8[0]; b = ch->n_loopstart[0]; *ptr8 = (a + b) >> 1;} static void doRetrg(moduleChannel_t *ch){ const uint32_t voiceAddr = 0xDFF0A0 + (ch->n_chanindex * 16); paulaWriteWord(0xDFF096, ch->n_dmabit); // voice DMA off paulaWritePtr(voiceAddr + 0, ch->n_start); // n_start is increased on 9xx paulaWriteWord(voiceAddr + 4, ch->n_length); paulaWriteWord(voiceAddr + 6, ch->n_period); paulaWriteWord(0xDFF096, 0x8000 | ch->n_dmabit); // voice DMA on // these take effect after the current DMA cycle is done paulaWritePtr(voiceAddr + 0, ch->n_loopstart); paulaWriteWord(voiceAddr + 4, ch->n_replen); // update tracker visuals setVisualsDMACON(ch->n_dmabit); setVisualsDataPtr(ch->n_chanindex, ch->n_start); setVisualsLength(ch->n_chanindex, ch->n_length); setVisualsPeriod(ch->n_chanindex, ch->n_period); setVisualsDMACON(0x8000 | ch->n_dmabit); setVisualsDataPtr(ch->n_chanindex, ch->n_loopstart); setVisualsLength(ch->n_chanindex, ch->n_replen); // set spectrum analyzer state for this channel ch->syncAnalyzerVolume = ch->n_volume; ch->syncAnalyzerPeriod = ch->n_period; ch->syncFlags |= UPDATE_SPECTRUM_ANALYZER; setVUMeterHeight(ch);} static void retrigNote(moduleChannel_t *ch){ if ((ch->n_cmd & 0xF) > 0) { if (song->tick == 0 && (ch->n_note & 0xFFF) > 0) return; if (song->tick % (ch->n_cmd & 0xF) == 0) doRetrg(ch); }} static void volumeSlide(moduleChannel_t *ch){ uint8_t param = ch->n_cmd & 0xFF; if ((param & 0xF0) == 0) { ch->n_volume -= param & 0x0F; if (ch->n_volume < 0) ch->n_volume = 0; } else { ch->n_volume += param >> 4; if (ch->n_volume > 64) ch->n_volume = 64; }} static void volumeFineUp(moduleChannel_t *ch){ if (song->tick == 0) { ch->n_volume += ch->n_cmd & 0xF; if (ch->n_volume > 64) ch->n_volume = 64; }} static void volumeFineDown(moduleChannel_t *ch){ if (song->tick == 0) { ch->n_volume -= ch->n_cmd & 0xF; if (ch->n_volume < 0) ch->n_volume = 0; }} static void noteCut(moduleChannel_t *ch){ if (song->tick == (ch->n_cmd & 0xF)) ch->n_volume = 0;} static void noteDelay(moduleChannel_t *ch){ if (song->tick == (ch->n_cmd & 0xF) && (ch->n_note & 0xFFF) > 0) doRetrg(ch);} static void patternDelay(moduleChannel_t *ch){ if (song->tick == 0 && pattDelTime2 == 0) pattDelTime = (ch->n_cmd & 0xF) + 1;} static void funkIt(moduleChannel_t *ch){ if (song->tick == 0) { ch->n_glissfunk = ((ch->n_cmd & 0xF) << 4) | (ch->n_glissfunk & 0xF); if ((ch->n_glissfunk & 0xF0) > 0) updateFunk(ch); }} static void positionJump(moduleChannel_t *ch){ // original PT doesn't do this check, but we have to if (editor.playMode != PLAY_MODE_PATTERN || (editor.currMode == MODE_RECORD && editor.recordMode != RECORD_PATT)) modPos = (ch->n_cmd & 0xFF) - 1; // B00 results in -1, but it safely jumps to order 0 pBreakPosition = 0; posJumpAssert = true;} static void volumeChange(moduleChannel_t *ch){ ch->n_volume = ch->n_cmd & 0xFF; if ((uint8_t)ch->n_volume > 64) ch->n_volume = 64;} static void patternBreak(moduleChannel_t *ch){ pBreakPosition = (((ch->n_cmd & 0xF0) >> 4) * 10) + (ch->n_cmd & 0x0F); if ((uint8_t)pBreakPosition > 63) pBreakPosition = 0; posJumpAssert = true;} static void setSpeed(moduleChannel_t *ch){ if ((ch->n_cmd & 0xFF) > 0) {Lines 1–500 of 1857. Load more
// C port of ProTracker 2.3D's replayer (with some modifications, but still accurate) // for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdint.h> #include <stdbool.h> #include "pt2_audio.h" #include "pt2_helpers.h" #include "pt2_tables.h" #include "pt2_config.h" #include "pt2_visuals.h" #include "pt2_scopes.h" #include "pt2_paula.h" #include "pt2_visuals_sync.h" #include "pt2_posed.h" static bool posJumpAssert, pBreakFlag, modRenderDone; static bool doStopSong; // from F00 (Set Speed) static int8_t pBreakPosition, oldRow, modPattern; static uint8_t pattDelTime, lowMask = 0xFF, pattDelTime2; static int16_t modPos, oldPattern, oldPos; static uint16_t DMACONtemp; static int32_t modBPM, oldBPM, oldSpeed, ciaSetBPM; static const uint8_t funkTable[16] = // EFx (FunkRepeat/InvertLoop) { 0x00, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D, 0x10, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80 }; void gotoNextMulti(void) { cursor.channel = (editor.multiModeNext[cursor.channel] - 1) & 3; cursor.pos = cursor.channel * 6; updateCursorPos(); } double ciaBpm2Hz(int32_t bpm) { if (bpm == 0) return 0.0; const uint32_t ciaPeriod = 1773447 / bpm; // yes, PT truncates here return (double)CIA_PAL_CLK / (ciaPeriod+1); // +1, CIA triggers on underflow } void updatePaulaLoops(void) // used after manipulating sample loop points while Paula is live { const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); for (int32_t i = 0; i < PAULA_VOICES; i++) { const moduleChannel_t *ch = &song->channels[i]; if (ch->n_samplenum == editor.currSample) // selected sample matches channel's sample? { const moduleSample_t *s = &song->samples[editor.currSample]; const uint32_t voiceAddr = 0xDFF0A0 + (i * 16); paulaWritePtr(voiceAddr + 0, ch->n_start + s->loopStart); paulaWriteWord(voiceAddr + 4, (uint16_t)(s->loopLength >> 1)); setVisualsDataPtr(i, ch->n_start + s->loopStart); setVisualsLength(i, (uint16_t)(s->loopLength >> 1)); } } if (audioWasntLocked) unlockAudio(); } void turnOffVoices(void) { const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); paulaWriteWord(0xDFF096, 0x000F); // turn off all voice DMAs setVisualsDMACON(0x000F); // clear all volumes for (int32_t i = 0; i < PAULA_VOICES; i++) { const uint32_t voiceAddr = 0xDFF0A0 + (i * 16); paulaWriteWord(voiceAddr + 8, 0); setVisualsVolume(i, 0); } // reset dithering/filter states (so that every playback session is identical) resetAudioDithering(); if (audioWasntLocked) unlockAudio(); editor.tuningToneFlag = false; } void initializeModuleChannels(module_t *m) { assert(m != NULL); memset(m->channels, 0, sizeof (m->channels)); moduleChannel_t *ch = m->channels; for (uint8_t i = 0; i < PAULA_VOICES; i++, ch++) { ch->n_chanindex = i; ch->n_dmabit = 1 << i; } } void setReplayerPosToTrackerPos(void) { if (song == NULL) return; modPattern = (int8_t)song->currPattern; modPos = song->currPos; song->row = song->currRow; song->tick = 0; } module_t *createEmptyMod(void) { module_t *m = (module_t *)calloc(1, sizeof (module_t)); if (m == NULL) goto error; // allocate memory for all sample data blocks (+ 2 extra, for quirk + safety) const size_t allocLen = (MOD_SAMPLES + 2) * config.maxSampleLength; m->sampleData = (int8_t *)calloc(allocLen, 1); if (m->sampleData == NULL) goto error; for (int32_t i = 0; i < MAX_PATTERNS; i++) { m->patterns[i] = (note_t *)calloc(1, MOD_ROWS * sizeof (note_t) * PAULA_VOICES); if (m->patterns[i] == NULL) goto error; } m->header.songLength = 1; moduleSample_t *s = m->samples; for (int32_t i = 0; i < MOD_SAMPLES; i++, s++) { // setup GUI text pointers s->volumeDisp = &s->volume; s->lengthDisp = &s->length; s->loopStartDisp = &s->loopStart; s->loopLengthDisp = &s->loopLength; s->loopLength = 2; // sample data offsets (sample data = one huge buffer to rule them all) s->offset = config.maxSampleLength * i; } initializeModuleChannels(m); return m; error: if (m != NULL) { for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (m->patterns[i] != NULL) free(m->patterns[i]); } if (m->sampleData != NULL) free(m->sampleData); free(m); } return NULL; } void modSetSpeed(int32_t speed) { song->currSpeed = song->speed = speed; song->tick = 0; } void doStopIt(bool resetPlayMode) { editor.songPlaying = false; pattDelTime = pattDelTime2 = 0; if (resetPlayMode) { editor.playMode = PLAY_MODE_NORMAL; editor.currMode = MODE_IDLE; pointerSetModeThreadSafe(POINTER_MODE_IDLE, true); } if (song != NULL) { moduleChannel_t *ch = song->channels; for (int32_t i = 0; i < PAULA_VOICES; i++, ch++) { ch->n_wavecontrol = 0; ch->n_glissfunk = 0; ch->n_finetune = 0; ch->n_loopcount = 0; } } doStopSong = false; // just in case this flag was stuck from command F00 (stop song) } void setPattern(int16_t pattern) { if (pattern > MAX_PATTERNS-1) pattern = MAX_PATTERNS-1; song->currPattern = modPattern = (int8_t)pattern; } void storeTempVariables(void) // this one is accessed in other files, so non-static { oldBPM = song->currBPM; oldRow = song->currRow; oldPos = song->currPos; oldSpeed = song->currSpeed; oldPattern = song->currPattern; } static void setVUMeterHeight(moduleChannel_t *ch) { if (editor.muted[ch->n_chanindex]) return; uint8_t vol = ch->n_volume; if ((ch->n_cmd & 0xF00) == 0xC00) // handle Cxx effect vol = ch->n_cmd & 0xFF; if (vol > 64) vol = 64; if (!editor.songPlaying) { editor.vuMeterVolumes[ch->n_chanindex] = vuMeterHeights[vol]; } else { ch->syncVuVolume = vol; ch->syncFlags |= UPDATE_VUMETER; } } static void updateFunk(moduleChannel_t *ch) { const int8_t funkSpeed = ch->n_glissfunk >> 4; if (funkSpeed == 0) return; ch->n_funkoffset += funkTable[funkSpeed]; if (ch->n_funkoffset >= 128) { ch->n_funkoffset = 0; if (ch->n_loopstart != NULL && ch->n_wavestart != NULL) // ProTracker bugfix { if (++ch->n_wavestart >= ch->n_loopstart + (ch->n_replen << 1)) ch->n_wavestart = ch->n_loopstart; *ch->n_wavestart = -1 - *ch->n_wavestart; } } } static void setGlissControl(moduleChannel_t *ch) { ch->n_glissfunk = (ch->n_glissfunk & 0xF0) | (ch->n_cmd & 0x0F); } static void setVibratoControl(moduleChannel_t *ch) { ch->n_wavecontrol = (ch->n_wavecontrol & 0xF0) | (ch->n_cmd & 0x0F); } static void setFineTune(moduleChannel_t *ch) { ch->n_finetune = ch->n_cmd & 0xF; } static void jumpLoop(moduleChannel_t *ch) { if (song->tick != 0) return; if ((ch->n_cmd & 0xF) == 0) { ch->n_pattpos = song->row; } else { if (ch->n_loopcount == 0) ch->n_loopcount = ch->n_cmd & 0xF; else if (--ch->n_loopcount == 0) return; pBreakPosition = ch->n_pattpos; pBreakFlag = true; // stuff used for MOD2WAV to determine if the song has reached its end if (editor.mod2WavOngoing) { for (int32_t tempParam = pBreakPosition; tempParam <= song->row; tempParam++) editor.rowVisitTable[(modPos * MOD_ROWS) + tempParam] = false; } } } static void setTremoloControl(moduleChannel_t *ch) { ch->n_wavecontrol = ((ch->n_cmd & 0xF) << 4) | (ch->n_wavecontrol & 0xF); } /* This is a little used effect, despite being present in original ProTracker. ** E8x was sometimes entirely replaced with code used for demo fx syncing in ** demo mod players, so it can be turned off by looking at DISABLE_E8X in ** protracker.ini if you so desire. */ static void karplusStrong(moduleChannel_t *ch) { int8_t a, b; if (config.disableE8xEffect) return; if (ch->n_loopstart == NULL) return; // ProTracker bugfix int8_t *ptr8 = ch->n_loopstart; int16_t end = ((ch->n_replen * 2) & 0xFFFF) - 2; do { a = ptr8[0]; b = ptr8[1]; *ptr8++ = (a + b) >> 1; } while (--end >= 0); a = ptr8[0]; b = ch->n_loopstart[0]; *ptr8 = (a + b) >> 1; } static void doRetrg(moduleChannel_t *ch) { const uint32_t voiceAddr = 0xDFF0A0 + (ch->n_chanindex * 16); paulaWriteWord(0xDFF096, ch->n_dmabit); // voice DMA off paulaWritePtr(voiceAddr + 0, ch->n_start); // n_start is increased on 9xx paulaWriteWord(voiceAddr + 4, ch->n_length); paulaWriteWord(voiceAddr + 6, ch->n_period); paulaWriteWord(0xDFF096, 0x8000 | ch->n_dmabit); // voice DMA on // these take effect after the current DMA cycle is done paulaWritePtr(voiceAddr + 0, ch->n_loopstart); paulaWriteWord(voiceAddr + 4, ch->n_replen); // update tracker visuals setVisualsDMACON(ch->n_dmabit); setVisualsDataPtr(ch->n_chanindex, ch->n_start); setVisualsLength(ch->n_chanindex, ch->n_length); setVisualsPeriod(ch->n_chanindex, ch->n_period); setVisualsDMACON(0x8000 | ch->n_dmabit); setVisualsDataPtr(ch->n_chanindex, ch->n_loopstart); setVisualsLength(ch->n_chanindex, ch->n_replen); // set spectrum analyzer state for this channel ch->syncAnalyzerVolume = ch->n_volume; ch->syncAnalyzerPeriod = ch->n_period; ch->syncFlags |= UPDATE_SPECTRUM_ANALYZER; setVUMeterHeight(ch); } static void retrigNote(moduleChannel_t *ch) { if ((ch->n_cmd & 0xF) > 0) { if (song->tick == 0 && (ch->n_note & 0xFFF) > 0) return; if (song->tick % (ch->n_cmd & 0xF) == 0) doRetrg(ch); } } static void volumeSlide(moduleChannel_t *ch) { uint8_t param = ch->n_cmd & 0xFF; if ((param & 0xF0) == 0) { ch->n_volume -= param & 0x0F; if (ch->n_volume < 0) ch->n_volume = 0; } else { ch->n_volume += param >> 4; if (ch->n_volume > 64) ch->n_volume = 64; } } static void volumeFineUp(moduleChannel_t *ch) { if (song->tick == 0) { ch->n_volume += ch->n_cmd & 0xF; if (ch->n_volume > 64) ch->n_volume = 64; } } static void volumeFineDown(moduleChannel_t *ch) { if (song->tick == 0) { ch->n_volume -= ch->n_cmd & 0xF; if (ch->n_volume < 0) ch->n_volume = 0; } } static void noteCut(moduleChannel_t *ch) { if (song->tick == (ch->n_cmd & 0xF)) ch->n_volume = 0; } static void noteDelay(moduleChannel_t *ch) { if (song->tick == (ch->n_cmd & 0xF) && (ch->n_note & 0xFFF) > 0) doRetrg(ch); } static void patternDelay(moduleChannel_t *ch) { if (song->tick == 0 && pattDelTime2 == 0) pattDelTime = (ch->n_cmd & 0xF) + 1; } static void funkIt(moduleChannel_t *ch) { if (song->tick == 0) { ch->n_glissfunk = ((ch->n_cmd & 0xF) << 4) | (ch->n_glissfunk & 0xF); if ((ch->n_glissfunk & 0xF0) > 0) updateFunk(ch); } } static void positionJump(moduleChannel_t *ch) { // original PT doesn't do this check, but we have to if (editor.playMode != PLAY_MODE_PATTERN || (editor.currMode == MODE_RECORD && editor.recordMode != RECORD_PATT)) modPos = (ch->n_cmd & 0xFF) - 1; // B00 results in -1, but it safely jumps to order 0 pBreakPosition = 0; posJumpAssert = true; } static void volumeChange(moduleChannel_t *ch) { ch->n_volume = ch->n_cmd & 0xFF; if ((uint8_t)ch->n_volume > 64) ch->n_volume = 64; } static void patternBreak(moduleChannel_t *ch) { pBreakPosition = (((ch->n_cmd & 0xF0) >> 4) * 10) + (ch->n_cmd & 0x0F); if ((uint8_t)pBreakPosition > 63) pBreakPosition = 0; posJumpAssert = true; } static void setSpeed(moduleChannel_t *ch) { if ((ch->n_cmd & 0xFF) > 0) {