ref: 7d886dbab7dfae712d0e0cea9d77cd404af5cad4
dir: /src/pt2_edit.c/
// 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 <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #ifdef _WIN32 #include <direct.h> #else #include <unistd.h> #endif #include "pt2_helpers.h" #include "pt2_textout.h" #include "pt2_tables.h" #include "pt2_diskop.h" #include "pt2_sampler.h" #include "pt2_visuals.h" #include "pt2_keyboard.h" #include "pt2_config.h" #include "pt2_audio.h" #include "pt2_chordmaker.h" #include "pt2_edit.h" #include "pt2_replayer.h" #include "pt2_visuals_sync.h" static const int8_t scancode2NoteLo[52] = // "USB usage page standard" order { 7, 4, 3, 16, -1, 6, 8, 24, 10, -1, 13, 11, 9, 26, 28, 12, 17, 1, 19, 23, 5, 14, 2, 21, 0, -1, 13, 15, -1, 18, 20, 22, -1, 25, 27, -1, -1, -1, -1, -1, -1, 30, 29, 31, 30, -1, 15, -1, -1, 12, 14, 16 }; static const int8_t scancode2NoteHi[52] = // "USB usage page standard" order { 19, 16, 15, 28, -1, 18, 20, -2, 22, -1, 25, 23, 21, -2, -2, 24, 29, 13, 31, 35, 17, 26, 14, 33, 12, -1, 25, 27, -1, 30, 32, 34, -1, -2, -2, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -1, 27, -1, -1, 24, 26, 28 }; // used for re-rendering text object while editing it void updateTextObject(int16_t editObject) { switch (editObject) { default: break; case PTB_SONGNAME: ui.updateSongName = true; break; case PTB_SAMPLENAME: ui.updateCurrSampleName = true; break; case PTB_PE_PATT: ui.updatePosEd = true; break; case PTB_EO_QUANTIZE: ui.updateQuantizeText = true; break; case PTB_EO_METRO_1: ui.updateMetro1Text = true; break; case PTB_EO_METRO_2: ui.updateMetro2Text = true; break; case PTB_EO_FROM_NUM: ui.updateFromText = true; break; case PTB_EO_TO_NUM: ui.updateToText = true; break; case PTB_EO_MIX: ui.updateMixText = true; break; case PTB_EO_POS_NUM: ui.updatePosText = true; break; case PTB_EO_MOD_NUM: ui.updateModText = true; break; case PTB_EO_VOL_NUM: ui.updateVolText = true; break; case PTB_DO_DATAPATH: ui.updateDiskOpPathText = true; break; case PTB_POSS: ui.updateSongPos = true; break; case PTB_PATTERNS: ui.updateSongPattern = true; break; case PTB_LENGTHS: ui.updateSongLength = true; break; case PTB_SAMPLES: ui.updateCurrSampleNum = true; break; case PTB_SVOLUMES: ui.updateCurrSampleVolume = true; break; case PTB_SLENGTHS: ui.updateCurrSampleLength = true; break; case PTB_SREPEATS: ui.updateCurrSampleRepeat = true; break; case PTB_SREPLENS: ui.updateCurrSampleReplen = true; break; case PTB_PATTDATA: ui.updateCurrPattText = true; break; case PTB_SA_VOL_FROM_NUM: ui.updateVolFromText = true; break; case PTB_SA_VOL_TO_NUM: ui.updateVolToText = true; break; case PTB_SA_FIL_LP_CUTOFF: ui.updateLPText = true; break; case PTB_SA_FIL_HP_CUTOFF: ui.updateHPText = true; break; } } void exitGetTextLine(bool updateValue) { int8_t tmp8; int16_t tmp16; int32_t tmp32; SDL_StopTextInput(); // if user updated the disk op path text if (ui.diskOpScreenShown && ui.editObject == PTB_DO_DATAPATH) { UNICHAR *pathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR)); if (pathU != NULL) { #ifdef _WIN32 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, editor.currPath, -1, pathU, PATH_MAX); #else strcpy(pathU, editor.currPath); #endif diskOpSetPath(pathU, DISKOP_CACHE); free(pathU); } } if (ui.editTextType != TEXT_EDIT_STRING) { if (ui.dstPos != ui.numLen) removeTextEditMarker(); updateTextObject(ui.editObject); } else { removeTextEditMarker(); // yet another kludge... if (ui.editObject == PTB_PE_PATT) ui.updatePosEd = true; } ui.editTextFlag = false; ui.lineCurX = 0; ui.lineCurY = 0; ui.editPos = NULL; ui.dstPos = 0; if (ui.editTextType == TEXT_EDIT_STRING) { if (ui.dstOffset != NULL) *ui.dstOffset = '\0'; pointerSetPreviousMode(); if (!editor.mixFlag) updateWindowTitle(MOD_IS_MODIFIED); } else { // set back GUI text pointers and update values (if requested) moduleSample_t *s = &song->samples[editor.currSample]; switch (ui.editObject) { case PTB_SA_FIL_LP_CUTOFF: { editor.lpCutOffDisp = &editor.lpCutOff; if (updateValue) { editor.lpCutOff = ui.tmpDisp16; if (editor.lpCutOff > (uint16_t)(FILTERS_BASE_FREQ/2)) editor.lpCutOff = (uint16_t)(FILTERS_BASE_FREQ/2); ui.updateLPText = true; } } break; case PTB_SA_FIL_HP_CUTOFF: { editor.hpCutOffDisp = &editor.hpCutOff; if (updateValue) { editor.hpCutOff = ui.tmpDisp16; if (editor.hpCutOff > (uint16_t)(FILTERS_BASE_FREQ/2)) editor.hpCutOff = (uint16_t)(FILTERS_BASE_FREQ/2); ui.updateHPText = true; } } break; case PTB_SA_VOL_FROM_NUM: { editor.vol1Disp = &editor.vol1; if (updateValue) { editor.vol1 = ui.tmpDisp16; if (editor.vol1 > 200) editor.vol1 = 200; ui.updateVolFromText = true; showVolFromSlider(); } } break; case PTB_SA_VOL_TO_NUM: { editor.vol2Disp = &editor.vol2; if (updateValue) { editor.vol2 = ui.tmpDisp16; if (editor.vol2 > 200) editor.vol2 = 200; ui.updateVolToText = true; showVolToSlider(); } } break; case PTB_EO_VOL_NUM: { editor.sampleVolDisp = &editor.sampleVol; if (updateValue) { editor.sampleVol = ui.tmpDisp16; ui.updateVolText = true; } } break; case PTB_EO_POS_NUM: { editor.samplePosDisp = &editor.samplePos; if (updateValue) { editor.samplePos = ui.tmpDisp32; if (editor.samplePos > config.maxSampleLength) editor.samplePos = config.maxSampleLength; if (editor.samplePos > song->samples[editor.currSample].length) editor.samplePos = song->samples[editor.currSample].length; ui.updatePosText = true; } } break; case PTB_EO_QUANTIZE: { editor.quantizeValueDisp = &config.quantizeValue; if (updateValue) { if (ui.tmpDisp16 > 63) ui.tmpDisp16 = 63; config.quantizeValue = ui.tmpDisp16; ui.updateQuantizeText = true; } } break; case PTB_EO_METRO_1: // metronome speed { editor.metroSpeedDisp = &editor.metroSpeed; if (updateValue) { if (ui.tmpDisp16 > 64) ui.tmpDisp16 = 64; editor.metroSpeed = ui.tmpDisp16; ui.updateMetro1Text = true; } } break; case PTB_EO_METRO_2: // metronome channel { editor.metroChannelDisp = &editor.metroChannel; if (updateValue) { if (ui.tmpDisp16 > 4) ui.tmpDisp16 = 4; editor.metroChannel = ui.tmpDisp16; ui.updateMetro2Text = true; } } break; case PTB_EO_FROM_NUM: { editor.sampleFromDisp = &editor.sampleFrom; if (updateValue) { editor.sampleFrom = ui.tmpDisp8; // signed check + normal check if (editor.sampleFrom < 0x00 || editor.sampleFrom > 0x1F) editor.sampleFrom = 0x1F; ui.updateFromText = true; } } break; case PTB_EO_TO_NUM: { editor.sampleToDisp = &editor.sampleTo; if (updateValue) { editor.sampleTo = ui.tmpDisp8; // signed check + normal check if (editor.sampleTo < 0x00 || editor.sampleTo > 0x1F) editor.sampleTo = 0x1F; ui.updateToText = true; } } break; case PTB_PE_PATT: { int16_t posEdPos = song->currOrder; if (posEdPos > song->header.numOrders-1) posEdPos = song->header.numOrders-1; editor.currPosEdPattDisp = &song->header.order[posEdPos]; if (updateValue) { if (ui.tmpDisp16 > MAX_PATTERNS-1) ui.tmpDisp16 = MAX_PATTERNS-1; song->header.order[posEdPos] = ui.tmpDisp16; updateWindowTitle(MOD_IS_MODIFIED); if (ui.posEdScreenShown) ui.updatePosEd = true; ui.updateSongPattern = true; ui.updateSongSize = true; } } break; case PTB_POSS: { editor.currPosDisp = &song->currOrder; if (updateValue) { tmp16 = ui.tmpDisp16; if (tmp16 > 126) tmp16 = 126; if (song->currOrder != tmp16) { song->currOrder = tmp16; editor.currPatternDisp = &song->header.order[song->currOrder]; if (ui.posEdScreenShown) ui.updatePosEd = true; ui.updateSongPos = true; ui.updatePatternData = true; } } } break; case PTB_PATTERNS: { editor.currPatternDisp = &song->header.order[song->currOrder]; if (updateValue) { tmp16 = ui.tmpDisp16; if (tmp16 > MAX_PATTERNS-1) tmp16 = MAX_PATTERNS-1; if (song->header.order[song->currOrder] != tmp16) { song->header.order[song->currOrder] = tmp16; updateWindowTitle(MOD_IS_MODIFIED); if (ui.posEdScreenShown) ui.updatePosEd = true; ui.updateSongPattern = true; ui.updateSongSize = true; } } } break; case PTB_LENGTHS: { editor.currLengthDisp = &song->header.numOrders; if (updateValue) { tmp16 = CLAMP(ui.tmpDisp16, 1, 127); if (song->header.numOrders != tmp16) { song->header.numOrders = tmp16; int16_t posEdPos = song->currOrder; if (posEdPos > song->header.numOrders-1) posEdPos = song->header.numOrders-1; editor.currPosEdPattDisp = &song->header.order[posEdPos]; if (ui.posEdScreenShown) ui.updatePosEd = true; ui.updateSongLength = true; ui.updateSongSize = true; updateWindowTitle(MOD_IS_MODIFIED); } } } break; case PTB_PATTDATA: { editor.currEditPatternDisp = &song->currPattern; if (updateValue) { if (song->currPattern != ui.tmpDisp16) { setPattern(ui.tmpDisp16); ui.updatePatternData = true; ui.updateCurrPattText = true; } } } break; case PTB_SAMPLES: { editor.currSampleDisp = &editor.currSample; if (updateValue) { tmp8 = ui.tmpDisp8; if (tmp8 < 0x00) // (signed) if >0x7F was entered, clamp to 0x1F tmp8 = 0x1F; tmp8 = CLAMP(tmp8, 0x01, 0x1F) - 1; if (tmp8 != editor.currSample) { editor.currSample = tmp8; updateCurrSample(); } } } break; case PTB_SVOLUMES: { s->volumeDisp = &s->volume; if (updateValue) { tmp8 = ui.tmpDisp8; // signed check + normal check if (tmp8 < 0x00 || tmp8 > 0x40) tmp8 = 0x40; if (s->volume != tmp8) { s->volume = tmp8; ui.updateCurrSampleVolume = true; updateWindowTitle(MOD_IS_MODIFIED); } } } break; case PTB_SLENGTHS: { s->lengthDisp = &s->length; if (updateValue) { tmp32 = ui.tmpDisp32 & ~1; // even'ify if (tmp32 > config.maxSampleLength) tmp32 = config.maxSampleLength; if (s->loopStart+s->loopLength > 2) { if (tmp32 < s->loopStart+s->loopLength) tmp32 = s->loopStart+s->loopLength; } tmp32 &= ~1; if (s->length != tmp32) { turnOffVoices(); s->length = tmp32; ui.updateCurrSampleLength = true; ui.updateSongSize = true; updateSamplePos(); if (ui.samplerScreenShown) redrawSample(); recalcChordLength(); updateWindowTitle(MOD_IS_MODIFIED); } } } break; case PTB_SREPEATS: { s->loopStartDisp = &s->loopStart; if (updateValue) { tmp32 = ui.tmpDisp32 & ~1; // even'ify if (tmp32 > config.maxSampleLength) tmp32 = config.maxSampleLength; if (s->length >= s->loopLength) { if (tmp32+s->loopLength > s->length) tmp32 = s->length - s->loopLength; } else { tmp32 = 0; } tmp32 &= ~1; if (s->loopStart != tmp32) { turnOffVoices(); s->loopStart = tmp32; updatePaulaLoops(); ui.updateCurrSampleRepeat = true; if (ui.editOpScreenShown && ui.editOpScreen == 3) ui.updateChordLengthText = true; if (ui.samplerScreenShown) setLoopSprites(); updateWindowTitle(MOD_IS_MODIFIED); } } } break; case PTB_SREPLENS: { s->loopLengthDisp = &s->loopLength; if (updateValue) { tmp32 = ui.tmpDisp32 & ~1; // even'ify if (tmp32 > config.maxSampleLength) tmp32 = config.maxSampleLength; if (s->length >= s->loopStart) { if (s->loopStart+tmp32 > s->length) tmp32 = s->length - s->loopStart; } else { tmp32 = 2; } tmp32 &= ~1; if (tmp32 < 2) tmp32 = 2; if (s->loopLength != tmp32) { turnOffVoices(); s->loopLength = tmp32; updatePaulaLoops(); ui.updateCurrSampleReplen = true; if (ui.editOpScreenShown && ui.editOpScreen == 3) ui.updateChordLengthText = true; if (ui.samplerScreenShown) setLoopSprites(); updateWindowTitle(MOD_IS_MODIFIED); } } } break; default: break; } pointerSetPreviousMode(); } ui.editTextType = 0; } void getTextLine(int16_t editObject) { pointerSetMode(POINTER_MODE_MSG1, NO_CARRY); ui.lineCurY = (ui.editTextPos / 40) + 5; ui.lineCurX = ((ui.editTextPos % 40) * FONT_CHAR_W) + 4; ui.dstPtr = ui.showTextPtr; ui.editPos = ui.showTextPtr; ui.dstPos = 0; ui.editTextFlag = true; ui.editTextType = TEXT_EDIT_STRING; ui.editObject = editObject; if (ui.dstOffset != NULL) ui.dstOffset[0] = '\0'; // kludge if (editor.mixFlag) { textCharNext(); textCharNext(); textCharNext(); textCharNext(); } renderTextEditMarker(); SDL_StartTextInput(); } void getNumLine(uint8_t type, int16_t editObject) { pointerSetMode(POINTER_MODE_MSG1, NO_CARRY); ui.lineCurY = (ui.editTextPos / 40) + 5; ui.lineCurX = ((ui.editTextPos % 40) * FONT_CHAR_W) + 4; ui.dstPos = 0; ui.editTextFlag = true; ui.editTextType = type; ui.editObject = editObject; renderTextEditMarker(); SDL_StartTextInput(); } static uint8_t quantizeCheck(uint8_t row) { assert(song != NULL); if (song == NULL) return row; const uint8_t quantize = (uint8_t)config.quantizeValue; editor.didQuantize = false; if (editor.currMode == MODE_RECORD) { if (quantize == 0) { return row; } else if (quantize == 1) { if (song->tick > song->speed>>1) { row = (row + 1) & 63; editor.didQuantize = true; } } else { uint8_t tempRow = ((((quantize >> 1) + row) & 63) / quantize) * quantize; if (tempRow > row) editor.didQuantize = true; return tempRow; } } return row; } static void jamAndPlaceSample(SDL_Scancode scancode, bool normalMode) { uint8_t chNum = cursor.channel; assert(chNum < PAULA_VOICES); moduleChannel_t *ch = &song->channels[chNum]; note_t *note = &song->patterns[song->currPattern][(quantizeCheck(song->currRow) * PAULA_VOICES) + chNum]; int8_t noteVal = normalMode ? keyToNote(scancode) : pNoteTable[editor.currSample]; if (noteVal >= 0) { moduleSample_t *s = &song->samples[editor.currSample]; int16_t tempPeriod = periodTable[((s->fineTune & 0xF) * 37) + noteVal]; uint16_t cleanPeriod = periodTable[noteVal]; editor.currPlayNote = noteVal; // play current sample // don't play sample if we quantized to another row (will be played in modplayer instead) if (editor.currMode != MODE_RECORD || !editor.didQuantize) { lockAudio(); ch->n_samplenum = editor.currSample; ch->n_volume = s->volume; ch->n_period = tempPeriod; ch->n_start = &song->sampleData[s->offset]; ch->n_length = (uint16_t)((s->loopStart > 0) ? (s->loopStart + s->loopLength) >> 1 : s->length >> 1); ch->n_loopstart = &song->sampleData[s->offset + s->loopStart]; ch->n_replen = (uint16_t)(s->loopLength >> 1); if (ch->n_length == 0) ch->n_length = 1; const uint32_t voiceAddr = 0xDFF0A0 + (chNum * 16); paulaWriteWord(voiceAddr + 8, ch->n_volume); paulaWriteWord(voiceAddr + 6, ch->n_period); paulaWritePtr(voiceAddr + 0, ch->n_start); paulaWriteWord(voiceAddr + 4, ch->n_length); if (!editor.muted[chNum]) paulaWriteWord(0xDFF096, 0x8000 | ch->n_dmabit); // voice DMA on else paulaWriteWord(0xDFF096, ch->n_dmabit); // voice DMA off // 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 setVisualsVolume(chNum, ch->n_volume); setVisualsPeriod(chNum, ch->n_period); setVisualsDataPtr(chNum, ch->n_start); setVisualsLength(chNum, ch->n_length); if (!editor.muted[chNum]) setVisualsDMACON(0x8000 | ch->n_dmabit); else setVisualsDMACON(ch->n_dmabit); setVisualsDataPtr(chNum, ch->n_loopstart); setVisualsLength(chNum, ch->n_replen); unlockAudio(); } // normalMode = normal keys, or else keypad keys (in jam mode) if (normalMode || editor.pNoteFlag != 0) { if (normalMode || editor.pNoteFlag == 2) { // insert note and sample number if (!ui.samplerScreenShown && (editor.currMode == MODE_EDIT || editor.currMode == MODE_RECORD)) { note->sample = editor.sampleZero ? 0 : (editor.currSample + 1); note->period = cleanPeriod; if (editor.autoInsFlag) { note->command = editor.effectMacros[editor.autoInsSlot] >> 8; note->param = editor.effectMacros[editor.autoInsSlot] & 0xFF; } if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } if (editor.multiFlag) gotoNextMulti(); } updateSpectrumAnalyzer(s->volume, tempPeriod); } else if (noteVal == -2) { // delete note and sample if illegal note (= -2, -1 = ignore) key was entered if (normalMode || editor.pNoteFlag == 2) { if (!ui.samplerScreenShown && (editor.currMode == MODE_EDIT || editor.currMode == MODE_RECORD)) { note->period = 0; note->sample = 0; if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } } } void handleEditKeys(SDL_Scancode scancode, bool normalMode) { if (ui.editTextFlag) return; if (ui.samplerScreenShown || (editor.currMode == MODE_IDLE || editor.currMode == MODE_PLAY)) { // at this point it will only jam, not place it if (!keyb.leftAltPressed && !keyb.leftAmigaPressed && !keyb.leftCtrlPressed && !keyb.shiftPressed) jamAndPlaceSample(scancode, normalMode); return; } // handle modified (ALT/CTRL/SHIFT etc) keys for editing if (editor.currMode == MODE_EDIT || editor.currMode == MODE_RECORD) { if (handleSpecialKeys(scancode)) { if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); return; } } // are we editing a note, or other stuff? if (cursor.mode != CURSOR_NOTE) { // if we held down any key modifier at this point, then do nothing if (keyb.leftAltPressed || keyb.leftAmigaPressed || keyb.leftCtrlPressed || keyb.shiftPressed) return; if (editor.currMode == MODE_EDIT || editor.currMode == MODE_RECORD) { int8_t hexKey, numberKey; if (scancode == SDL_SCANCODE_0) numberKey = 0; else if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_9) numberKey = (int8_t)scancode - (SDL_SCANCODE_1-1); else numberKey = -1; if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_F) hexKey = 10 + ((int8_t)scancode - SDL_SCANCODE_A); else hexKey = -1; int8_t key = -1; if (numberKey != -1) { if (key == -1) key = 0; key += numberKey; } if (hexKey != -1) { if (key == -1) key = 0; key += hexKey; } note_t *note = &song->patterns[song->currPattern][(song->currRow * PAULA_VOICES) + cursor.channel]; switch (cursor.mode) { case CURSOR_SAMPLE1: { if (key != -1 && key < 2) { note->sample = (uint8_t)((note->sample & 0x0F) | (key << 4)); if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } break; case CURSOR_SAMPLE2: { if (key != -1 && key < 16) { note->sample = (uint8_t)((note->sample & 0xF0) | key); if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } break; case CURSOR_CMD: { if (key != -1 && key < 16) { note->command = (uint8_t)key; if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } break; case CURSOR_PARAM1: { if (key != -1 && key < 16) { note->param = (uint8_t)((note->param & 0x0F) | (key << 4)); if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } break; case CURSOR_PARAM2: { if (key != -1 && key < 16) { note->param = (uint8_t)((note->param & 0xF0) | key); if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } break; default: break; } } } else { if (scancode == SDL_SCANCODE_DELETE) { if (editor.currMode == MODE_EDIT || editor.currMode == MODE_RECORD) { note_t *note = &song->patterns[song->currPattern][(song->currRow * PAULA_VOICES) + cursor.channel]; if (!keyb.leftAltPressed) { note->sample = 0; note->period = 0; } if (keyb.shiftPressed || keyb.leftAltPressed) { note->command = 0; note->param = 0; } if (editor.currMode != MODE_RECORD) modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 63); updateWindowTitle(MOD_IS_MODIFIED); } } else { // if we held down any key modifier at this point, then do nothing if (keyb.leftAltPressed || keyb.leftAmigaPressed || keyb.leftCtrlPressed || keyb.shiftPressed) return; jamAndPlaceSample(scancode, normalMode); } } } bool handleSpecialKeys(SDL_Scancode scancode) { if (!keyb.leftAltPressed) return false; note_t *patt = song->patterns[song->currPattern]; note_t *note = &patt[(song->currRow * PAULA_VOICES) + cursor.channel]; note_t *prevNote = &patt[(((song->currRow - 1) & 63) * PAULA_VOICES) + cursor.channel]; if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0) { // insert stored effect (buffer[0..8]) note->command = editor.effectMacros[scancode - SDL_SCANCODE_1] >> 8; note->param = editor.effectMacros[scancode - SDL_SCANCODE_1] & 0xFF; updateWindowTitle(MOD_IS_MODIFIED); return true; } // copy command+effect from above into current command+effect if (scancode == SDL_SCANCODE_BACKSLASH) { note->command = prevNote->command; note->param = prevNote->param; updateWindowTitle(MOD_IS_MODIFIED); return true; } // copy command+(effect + 1) from above into current command+effect if (scancode == SDL_SCANCODE_EQUALS) { note->command = prevNote->command; note->param = prevNote->param + 1; // wraps 0x00..0xFF updateWindowTitle(MOD_IS_MODIFIED); return true; } // copy command+(effect - 1) from above into current command+effect if (scancode == SDL_SCANCODE_MINUS) { note->command = prevNote->command; note->param = prevNote->param - 1; // wraps 0x00..0xFF updateWindowTitle(MOD_IS_MODIFIED); return true; } return false; } void handleSampleJamming(SDL_Scancode scancode) // used for the sampling feature (in SAMPLER) { const int32_t chNum = cursor.channel; if (scancode == SDL_SCANCODE_NONUSBACKSLASH) { turnOffVoices(); // magic "kill all voices" button return; } int8_t noteVal = keyToNote(scancode); if (noteVal < 0 || noteVal > 35) return; moduleSample_t *s = &song->samples[editor.currSample]; if (s->length <= 1) return; moduleChannel_t *ch = &song->channels[chNum]; int8_t *n_start = &song->sampleData[s->offset]; int8_t vol = 64; uint16_t n_length = (uint16_t)(s->length >> 1); uint16_t period = periodTable[((s->fineTune & 0xF) * 37) + noteVal]; lockAudio(); ch->n_samplenum = editor.currSample; // needed for sample playback/sampling line const uint32_t voiceAddr = 0xDFF0A0 + (chNum * 16); paulaWriteWord(voiceAddr + 8, vol); paulaWriteWord(voiceAddr + 6, period); paulaWritePtr(voiceAddr + 0, n_start); paulaWriteWord(voiceAddr + 4, n_length); if (!editor.muted[chNum]) paulaWriteWord(0xDFF096, 0x8000 | ch->n_dmabit); // voice DMA on else paulaWriteWord(0xDFF096, ch->n_dmabit); // voice DMA off // these take effect after the current DMA cycle is done paulaWritePtr(voiceAddr + 0, NULL); // data paulaWriteWord(voiceAddr + 4, 1); // length // update tracker visuals setVisualsVolume(chNum, vol); setVisualsPeriod(chNum, period); setVisualsDataPtr(chNum, n_start); setVisualsLength(chNum, n_length); if (!editor.muted[chNum]) setVisualsDMACON(0x8000 | ch->n_dmabit); else setVisualsDMACON(ch->n_dmabit); setVisualsDataPtr(chNum, NULL); setVisualsLength(chNum, 1); unlockAudio(); } void saveUndo(void) { memcpy(editor.undoBuffer, song->patterns[song->currPattern], sizeof (note_t) * (PAULA_VOICES * MOD_ROWS)); } void undoLastChange(void) { for (uint16_t i = 0; i < MOD_ROWS * PAULA_VOICES; i++) { note_t data = editor.undoBuffer[i]; editor.undoBuffer[i] = song->patterns[song->currPattern][i]; song->patterns[song->currPattern][i] = data; } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void copySampleTrack(void) { if (editor.trackPattFlag == 2) { // copy from one sample slot to another // never attempt to swap if from and/or to is 0 if (editor.sampleFrom == 0 || editor.sampleTo == 0) { displayErrorMsg("FROM/TO = 0 !"); return; } moduleSample_t *smpTo = &song->samples[editor.sampleTo - 1]; moduleSample_t *smpFrom = &song->samples[editor.sampleFrom - 1]; turnOffVoices(); // copy uint32_t tmpOffset = smpTo->offset; *smpTo = *smpFrom; smpTo->offset = tmpOffset; // update the copied sample's GUI text pointers smpTo->volumeDisp = &smpTo->volume; smpTo->lengthDisp = &smpTo->length; smpTo->loopStartDisp = &smpTo->loopStart; smpTo->loopLengthDisp = &smpTo->loopLength; // copy sample data memcpy(&song->sampleData[smpTo->offset], &song->sampleData[smpFrom->offset], config.maxSampleLength); updateCurrSample(); ui.updateSongSize = true; } else { // copy sample number in track/pattern if (editor.trackPattFlag == 0) { for (int32_t i = 0; i < MOD_ROWS; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (noteSrc->sample == editor.sampleFrom) noteSrc->sample = editor.sampleTo; } } else { for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (noteSrc->sample == editor.sampleFrom) noteSrc->sample = editor.sampleTo; } } } ui.updatePatternData = true; } editor.samplePos = 0; updateSamplePos(); updateWindowTitle(MOD_IS_MODIFIED); } void exchSampleTrack(void) { if (editor.trackPattFlag == 2) { // exchange sample slots // never attempt to swap if from and/or to is 0 if (editor.sampleFrom == 0 || editor.sampleTo == 0) { displayErrorMsg("FROM/TO = 0 !"); return; } moduleSample_t *smpTo = &song->samples[editor.sampleTo-1]; moduleSample_t *smpFrom = &song->samples[editor.sampleFrom-1]; turnOffVoices(); // swap offsets first so that the next swap will leave offsets intact uint32_t tmpOffset = smpFrom->offset; smpFrom->offset = smpTo->offset; smpTo->offset = tmpOffset; // swap sample (now offsets are left as before) moduleSample_t smpTmp = *smpFrom; *smpFrom = *smpTo; *smpTo = smpTmp; // update the swapped sample's GUI text pointers smpFrom->volumeDisp = &smpFrom->volume; smpFrom->lengthDisp = &smpFrom->length; smpFrom->loopStartDisp = &smpFrom->loopStart; smpFrom->loopLengthDisp = &smpFrom->loopLength; smpTo->volumeDisp = &smpTo->volume; smpTo->lengthDisp = &smpTo->length; smpTo->loopStartDisp = &smpTo->loopStart; smpTo->loopLengthDisp = &smpTo->loopLength; // swap sample data for (int32_t i = 0; i < config.maxSampleLength; i++) { int8_t smp = song->sampleData[smpFrom->offset+i]; song->sampleData[smpFrom->offset+i] = song->sampleData[smpTo->offset+i]; song->sampleData[smpTo->offset+i] = smp; } editor.sampleZero = false; updateCurrSample(); } else { // exchange sample number in track/pattern if (editor.trackPattFlag == 0) { for (int32_t i = 0; i < MOD_ROWS; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (noteSrc->sample == editor.sampleFrom) noteSrc->sample = editor.sampleTo; else if (noteSrc->sample == editor.sampleTo) noteSrc->sample = editor.sampleFrom; } } else { for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (noteSrc->sample == editor.sampleFrom) noteSrc->sample = editor.sampleTo; else if (noteSrc->sample == editor.sampleTo) noteSrc->sample = editor.sampleFrom; } } } ui.updatePatternData = true; } editor.samplePos = 0; updateSamplePos(); updateWindowTitle(MOD_IS_MODIFIED); } void delSampleTrack(void) { saveUndo(); if (editor.trackPattFlag == 0) { for (int32_t i = 0; i < MOD_ROWS; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (noteSrc->sample == editor.currSample+1) { noteSrc->period = 0; noteSrc->sample = 0; noteSrc->command = 0; noteSrc->param = 0; } } } else { for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (noteSrc->sample == editor.currSample+1) { noteSrc->period = 0; noteSrc->sample = 0; noteSrc->command = 0; noteSrc->param = 0; } } } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void trackNoteUp(bool sampleAllFlag, uint8_t from, uint8_t to) { if (from > to) { uint8_t old = from; from = to; to = old; } saveUndo(); for (int32_t i = from; i <= to; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t j; for (j = 0; j < 36; j++) { if (noteSrc->period >= periodTable[j]) break; } bool noteDeleted = false; if (++j > 35) { j = 35; if (config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } } if (!noteDeleted) noteSrc->period = periodTable[j]; } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void trackNoteDown(bool sampleAllFlag, uint8_t from, uint8_t to) { if (from > to) { uint8_t old = from; from = to; to = old; } saveUndo(); for (int32_t i = from; i <= to; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t j; for (j = 0; j < 36; j++) { if (noteSrc->period >= periodTable[j]) break; } bool noteDeleted = false; if (--j < 0) { j = 0; if (config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } } if (!noteDeleted) noteSrc->period = periodTable[j]; } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void trackOctaUp(bool sampleAllFlag, uint8_t from, uint8_t to) { if (from > to) { uint8_t old = from; from = to; to = old; } bool noteChanged = false; saveUndo(); for (int32_t i = from; i <= to; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { uint16_t oldPeriod = noteSrc->period; // period -> note int32_t j; for (j = 0; j < 36; j++) { if (noteSrc->period >= periodTable[j]) break; } bool noteDeleted = false; if (j+12 > 35 && config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } if (j <= 23) j += 12; if (!noteDeleted) noteSrc->period = periodTable[j]; if (noteSrc->period != oldPeriod) noteChanged = true; } } if (noteChanged) { updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } } void trackOctaDown(bool sampleAllFlag, uint8_t from, uint8_t to) { if (from > to) { uint8_t old = from; from = to; to = old; } saveUndo(); for (int32_t i = from; i <= to; i++) { note_t *noteSrc = &song->patterns[song->currPattern][(i * PAULA_VOICES) + cursor.channel]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t j; for (j = 0; j < 36; j++) { if (noteSrc->period >= periodTable[j]) break; } bool noteDeleted = false; if (j-12 < 0 && config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } if (j >= 12) j -= 12; if (!noteDeleted) noteSrc->period = periodTable[j]; } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void pattNoteUp(bool sampleAllFlag) { saveUndo(); for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t k; for (k = 0; k < 36; k++) { if (noteSrc->period >= periodTable[k]) break; } bool noteDeleted = false; if (++k > 35) { k = 35; if (config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } } if (!noteDeleted) noteSrc->period = periodTable[k]; } } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void pattNoteDown(bool sampleAllFlag) { saveUndo(); for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t k; for (k = 0; k < 36; k++) { if (noteSrc->period >= periodTable[k]) break; } bool noteDeleted = false; if (--k < 0) { k = 0; if (config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } } if (!noteDeleted) noteSrc->period = periodTable[k]; } } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void pattOctaUp(bool sampleAllFlag) { saveUndo(); for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t k; for (k = 0; k < 36; k++) { if (noteSrc->period >= periodTable[k]) break; } bool noteDeleted = false; if (k+12 > 35 && config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } if (k <= 23) k += 12; if (!noteDeleted) noteSrc->period = periodTable[k]; } } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } void pattOctaDown(bool sampleAllFlag) { saveUndo(); for (int32_t i = 0; i < PAULA_VOICES; i++) { for (int32_t j = 0; j < MOD_ROWS; j++) { note_t *noteSrc = &song->patterns[song->currPattern][(j * PAULA_VOICES) + i]; if (!sampleAllFlag && noteSrc->sample != editor.currSample+1) continue; if (noteSrc->period) { // period -> note int32_t k; for (k = 0; k < 36; k++) { if (noteSrc->period >= periodTable[k]) break; } bool noteDeleted = false; if (k-12 < 0 && config.transDel) { noteSrc->period = 0; noteSrc->sample = 0; noteDeleted = true; } if (k >= 12) k -= 12; if (!noteDeleted) noteSrc->period = periodTable[k]; } } } updateWindowTitle(MOD_IS_MODIFIED); ui.updatePatternData = true; } int8_t keyToNote(SDL_Scancode scancode) { int8_t note; if (scancode < SDL_SCANCODE_B || scancode > SDL_SCANCODE_SLASH) return -1; // not a note key int32_t lookUpKey = (int32_t)scancode - SDL_SCANCODE_B; if (lookUpKey < 0 || lookUpKey >= 52) return -1; // just in case if (editor.keyOctave == OCTAVE_LOW) note = scancode2NoteLo[lookUpKey]; else note = scancode2NoteHi[lookUpKey]; return note; }