ref: cf882314685613d8ede6f8c3ac27b1af7186871c
dir: /src/ft2_edit.c/
// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdint.h> #include "ft2_header.h" #include "ft2_config.h" #include "ft2_keyboard.h" #include "ft2_audio.h" #include "ft2_midi.h" #include "ft2_pattern_ed.h" #include "ft2_sysreqs.h" #include "ft2_textboxes.h" #include "ft2_tables.h" #include "ft2_structs.h" enum { KEYTYPE_NUM = 0, KEYTYPE_ALPHA = 1 }; static double dVolScaleFK1 = 1.0, dVolScaleFK2 = 1.0; // for block cut/copy/paste static bool blockCopied; static int16_t markXSize, markYSize; static uint16_t ptnBufLen, trkBufLen; // for transposing - these are set and tested accordingly static int8_t lastTranspVal; static uint8_t lastInsMode, lastTranspMode; static uint32_t transpDelNotes; // count of under-/overflowing notes for warning message static tonTyp clearNote; static tonTyp blkCopyBuff[MAX_PATT_LEN * MAX_VOICES]; static tonTyp ptnCopyBuff[MAX_PATT_LEN * MAX_VOICES]; static tonTyp trackCopyBuff[MAX_PATT_LEN]; static const int8_t tickArr[16] = { 16, 8, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1 }; void recordNote(uint8_t note, int8_t vol); // when the cursor is at the note slot static bool testNoteKeys(SDL_Scancode scancode) { const int8_t noteNum = scancodeKeyToNote(scancode); if (noteNum == 97) { // inserts "note off" if editing song if (playMode == PLAYMODE_EDIT || playMode == PLAYMODE_RECPATT || playMode == PLAYMODE_RECSONG) { if (!allocatePattern(editor.editPattern)) return true; // key pressed patt[editor.editPattern][(editor.pattPos * MAX_VOICES) + cursor.ch].ton = 97; const uint16_t pattLen = pattLens[editor.editPattern]; if (playMode == PLAYMODE_EDIT && pattLen >= 1) setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true); ui.updatePatternEditor = true; setSongModifiedFlag(); } return true; // key pressed } if (noteNum > 0 && noteNum <= 96) { recordNote(noteNum, -1); return true; // note key pressed (and note triggered) } return false; // no note key pressed } // when the cursor is at the note slot void testNoteKeysRelease(SDL_Scancode scancode) { const int8_t noteNum = scancodeKeyToNote(scancode); // convert key scancode to note number if (noteNum > 0 && noteNum <= 96) recordNote(noteNum, 0); // release note } static bool testEditKeys(SDL_Scancode scancode, SDL_Keycode keycode) { int8_t i; uint8_t oldVal; if (cursor.object == CURSOR_NOTE) { // the edit cursor is at the note slot if (testNoteKeys(scancode)) { keyb.keyRepeat = (playMode == PLAYMODE_EDIT); // repeat keys only if in edit mode return true; // we jammed an instrument } return false; // no note key pressed, test other keys } if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT) return false; // we're not editing, test other keys // convert key to slot data if (cursor.object == CURSOR_VOL1) { // volume column effect type (mixed keys) for (i = 0; i < KEY2VOL_ENTRIES; i++) { if (keycode == key2VolTab[i]) break; } if (i == KEY2VOL_ENTRIES) i = -1; // invalid key for slot } else if (cursor.object == CURSOR_EFX0) { // effect type (mixed keys) for (i = 0; i < KEY2EFX_ENTRIES; i++) { if (keycode == key2EfxTab[i]) break; } if (i == KEY2EFX_ENTRIES) i = -1; // invalid key for slot } else { // all other slots (hex keys) for (i = 0; i < KEY2HEX_ENTRIES; i++) { if (keycode == key2HexTab[i]) break; } if (i == KEY2HEX_ENTRIES) i = -1; // invalid key for slot } if (i == -1 || !allocatePattern(editor.editPattern)) return false; // no edit to be done // insert slot data tonTyp *ton = &patt[editor.editPattern][(editor.pattPos * MAX_VOICES) + cursor.ch]; switch (cursor.object) { case CURSOR_INST1: { oldVal = ton->instr; ton->instr = (ton->instr & 0x0F) | (i << 4); if (ton->instr > MAX_INST) ton->instr = MAX_INST; if (ton->instr != oldVal) setSongModifiedFlag(); } break; case CURSOR_INST2: { oldVal = ton->instr; ton->instr = (ton->instr & 0xF0) | i; if (ton->instr != oldVal) setSongModifiedFlag(); } break; case CURSOR_VOL1: { oldVal = ton->vol; ton->vol = (ton->vol & 0x0F) | ((i + 1) << 4); if (ton->vol >= 0x51 && ton->vol <= 0x5F) ton->vol = 0x50; if (ton->vol != oldVal) setSongModifiedFlag(); } break; case CURSOR_VOL2: { oldVal = ton->vol; if (ton->vol < 0x10) ton->vol = 0x10 + i; else ton->vol = (ton->vol & 0xF0) | i; if (ton->vol >= 0x51 && ton->vol <= 0x5F) ton->vol = 0x50; if (ton->vol != oldVal) setSongModifiedFlag(); } break; case CURSOR_EFX0: { oldVal = ton->effTyp; ton->effTyp = i; if (ton->effTyp != oldVal) setSongModifiedFlag(); } break; case CURSOR_EFX1: { oldVal = ton->eff; ton->eff = (ton->eff & 0x0F) | (i << 4); if (ton->eff != oldVal) setSongModifiedFlag(); } break; case CURSOR_EFX2: { oldVal = ton->eff; ton->eff = (ton->eff & 0xF0) | i; if (ton->eff != oldVal) setSongModifiedFlag(); } break; default: break; } // increase row (only in edit mode) const int16_t pattLen = pattLens[editor.editPattern]; if (playMode == PLAYMODE_EDIT && pattLen >= 1) setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true); if (i == 0) // if we inserted a zero, check if pattern is empty, for killing killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; return true; } // directly ported from the original FT2 code (fun fact: named EvulateTimeStamp() in the FT2 code) static void evaluateTimeStamp(int16_t *songPos, int16_t *pattNr, int16_t *pattPos, int16_t *tick) { int16_t sp = editor.songPos; int16_t nr = editor.editPattern; int16_t row = editor.pattPos; int16_t t = editor.tempo - editor.timer; t = CLAMP(t, 0, editor.tempo - 1); // this is needed, but also breaks quantization on speed>15 if (t > 15) t = 15; const int16_t pattLen = pattLens[nr]; if (config.recQuant > 0) { int16_t r; if (config.recQuantRes >= 16) { t += (editor.tempo >> 1) + 1; } else { r = tickArr[config.recQuantRes-1]; int16_t p = row & (r - 1); if (p < (r >> 1)) row -= p; else row = (row + r) - p; t = 0; } } if (t > editor.tempo) { t -= editor.tempo; row++; } if (row >= pattLen) { if (playMode == PLAYMODE_RECSONG) sp++; row = 0; if (sp >= song.len) sp = song.repS; nr = song.songTab[sp]; } *songPos = sp; *pattNr = nr; *pattPos = row; *tick = t; } // directly ported from the original FT2 code - what a mess, but it works... void recordNote(uint8_t note, int8_t vol) { int8_t i; int16_t nr, sp, pattpos, tick; int32_t time; tonTyp *noteData; const int16_t oldpattpos = editor.pattPos; if (songPlaying) { // row quantization evaluateTimeStamp(&sp, &nr, &pattpos, &tick); } else { sp = editor.songPos; nr = editor.editPattern; pattpos = editor.pattPos; tick = 0; } bool editmode = (playMode == PLAYMODE_EDIT); bool recmode = (playMode == PLAYMODE_RECSONG) || (playMode == PLAYMODE_RECPATT); if (note == 97) vol = 0; int8_t c = -1; int8_t k = -1; if (editmode || recmode) { // find out what channel is the most suitable in edit/record mode if ((config.multiEdit && editmode) || (config.multiRec && recmode)) { time = 0x7FFFFFFF; for (i = 0; i < song.antChn; i++) { if (editor.chnMode[i] && config.multiRecChn[i] && editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0) { c = i; time = editor.keyOffTime[i]; } } } else { c = cursor.ch; } for (i = 0; i < song.antChn; i++) { if (note == editor.keyOnTab[i] && config.multiRecChn[i]) k = i; } } else { // find out what channel is the most suitable in idle/play mode (jamming) if (config.multiKeyJazz) { time = 0x7FFFFFFF; c = 0; if (songPlaying) { for (i = 0; i < song.antChn; i++) { if (editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0 && config.multiRecChn[i]) { c = i; time = editor.keyOffTime[i]; } } } if (time == 0x7FFFFFFF) { for (i = 0; i < song.antChn; i++) { if (editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0) { c = i; time = editor.keyOffTime[i]; } } } } else { c = cursor.ch; } for (i = 0; i < song.antChn; i++) { if (note == editor.keyOnTab[i]) k = i; } } if (vol != 0) { if (c < 0 || (k >= 0 && (config.multiEdit || (recmode || !editmode)))) return; // play note editor.keyOnTab[c] = note; if (pattpos >= oldpattpos) // non-FT2 fix: only do this if we didn't quantize to next row { #ifdef HAS_MIDI playTone(c, editor.curInstr, note, vol, midi.currMIDIVibDepth, midi.currMIDIPitch); #else playTone(c, editor.curInstr, note, vol, 0, 0); #endif } if (editmode || recmode) { if (allocatePattern(nr)) { const int16_t pattLen = pattLens[nr]; noteData = &patt[nr][(pattpos * MAX_VOICES) + c]; // insert data noteData->ton = note; if (editor.curInstr > 0) noteData->instr = editor.curInstr; if (vol >= 0) noteData->vol = 0x10 + vol; if (!recmode) { // increase row (only in edit mode) if (pattLen >= 1) setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true); } else { // apply tick delay for note if quantization is disabled if (!config.recQuant && tick > 0) { noteData->effTyp = 0x0E; noteData->eff = 0xD0 + (tick & 0x0F); } } ui.updatePatternEditor = true; setSongModifiedFlag(); } } } else { // note off if (k != -1) c = k; if (c < 0) return; editor.keyOnTab[c] = 0; editor.keyOffTime[c] = ++editor.keyOffNr; if (pattpos >= oldpattpos) // non-FT2 fix: only do this if we didn't quantize to next row { #ifdef HAS_MIDI playTone(c, editor.curInstr, 97, vol, midi.currMIDIVibDepth, midi.currMIDIPitch); #else playTone(c, editor.curInstr, 97, vol, 0, 0); #endif } if (config.recRelease && recmode) { if (allocatePattern(nr)) { // insert data int16_t pattLen = pattLens[nr]; noteData = &patt[nr][(pattpos * MAX_VOICES) + c]; if (noteData->ton != 0) pattpos++; if (pattpos >= pattLen) { if (songPlaying) sp++; if (sp >= song.len) sp = song.repS; nr = song.songTab[sp]; pattpos = 0; pattLen = pattLens[nr]; } noteData = &patt[nr][(pattpos * MAX_VOICES) + c]; noteData->ton = 97; if (!recmode) { // increase row (only in edit mode) if (pattLen >= 1) setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true); } else { // apply tick delay for note if quantization is disabled if (!config.recQuant && tick > 0) { noteData->effTyp = 0x0E; noteData->eff = 0xD0 + (tick & 0x0F); } } ui.updatePatternEditor = true; setSongModifiedFlag(); } } } } bool handleEditKeys(SDL_Keycode keycode, SDL_Scancode scancode) { // special case for delete - manipulate note data if (keycode == SDLK_DELETE) { if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT) return false; // we're not editing, test other keys if (patt[editor.editPattern] == NULL) return true; tonTyp *note = &patt[editor.editPattern][(editor.pattPos * MAX_VOICES) + cursor.ch]; if (keyb.leftShiftPressed) { // delete all memset(note, 0, sizeof (tonTyp)); } else if (keyb.leftCtrlPressed) { // delete volume column + effect note->vol = 0; note->effTyp = 0; note->eff = 0; } else if (keyb.leftAltPressed) { // delete effect note->effTyp = 0; note->eff = 0; } else { if (cursor.object == CURSOR_VOL1 || cursor.object == CURSOR_VOL2) { // delete volume column note->vol = 0; } else { // delete note + instrument note->ton = 0; note->instr = 0; } } killPatternIfUnused(editor.editPattern); // increase row (only in edit mode) const int16_t pattLen = pattLens[editor.editPattern]; if (playMode == PLAYMODE_EDIT && pattLen >= 1) setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true); ui.updatePatternEditor = true; setSongModifiedFlag(); return true; } // a kludge for french keyb. layouts to allow writing numbers in the pattern data with left SHIFT const bool frKeybHack = keyb.leftShiftPressed && !keyb.leftAltPressed && !keyb.leftCtrlPressed && (scancode >= SDL_SCANCODE_1) && (scancode <= SDL_SCANCODE_0); if (frKeybHack || !keyb.keyModifierDown) return (testEditKeys(scancode, keycode)); return false; } void writeToMacroSlot(uint8_t slot) { uint16_t writeVol = 0; uint16_t writeEff = 0; if (patt[editor.editPattern] != NULL) { tonTyp *note = &patt[editor.editPattern][(editor.pattPos * MAX_VOICES) + cursor.ch]; writeVol = note->vol; writeEff = (note->effTyp << 8) | note->eff; } if (cursor.object == CURSOR_VOL1 || cursor.object == CURSOR_VOL2) config.volMacro[slot] = writeVol; else config.comMacro[slot] = writeEff; } void writeFromMacroSlot(uint8_t slot) { if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT) return; if (!allocatePattern(editor.editPattern)) return; const int16_t pattLen = pattLens[editor.editPattern]; tonTyp *note = &patt[editor.editPattern][(editor.pattPos * MAX_VOICES) + cursor.ch]; if (cursor.object == CURSOR_VOL1 || cursor.object == CURSOR_VOL2) { note->vol = (uint8_t)config.volMacro[slot]; } else { uint8_t effTyp = (uint8_t)(config.comMacro[slot] >> 8); if (effTyp > 35) { // illegal effect note->effTyp = 0; note->eff = 0; } else { note->effTyp = effTyp; note->eff = config.comMacro[slot] & 0xFF; } } if (playMode == PLAYMODE_EDIT && pattLen >= 1) setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true); killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } void insertPatternNote(void) { if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG) return; const int16_t nr = editor.editPattern; tonTyp *pattPtr = patt[nr]; if (pattPtr == NULL) return; const int16_t pattPos = editor.pattPos; const int16_t pattLen = pattLens[nr]; if (pattLen > 1) { for (int32_t i = pattLen-2; i >= pattPos; i--) pattPtr[((i + 1) * MAX_VOICES) + cursor.ch] = pattPtr[(i * MAX_VOICES) + cursor.ch]; } memset(&pattPtr[(pattPos * MAX_VOICES) + cursor.ch], 0, sizeof (tonTyp)); killPatternIfUnused(nr); ui.updatePatternEditor = true; setSongModifiedFlag(); } void insertPatternLine(void) { if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG) return; const int16_t nr = editor.editPattern; setPatternLen(nr, pattLens[nr] + config.recTrueInsert); // config.recTrueInsert is 0 or 1 tonTyp *pattPtr = patt[nr]; if (pattPtr != NULL) { const int16_t pattPos = editor.pattPos; const int16_t pattLen = pattLens[nr]; if (pattLen > 1) { for (int32_t i = pattLen-2; i >= pattPos; i--) { for (int32_t j = 0; j < MAX_VOICES; j++) pattPtr[((i + 1) * MAX_VOICES) + j] = pattPtr[(i * MAX_VOICES) + j]; } } memset(&pattPtr[pattPos * MAX_VOICES], 0, TRACK_WIDTH); killPatternIfUnused(nr); } ui.updatePatternEditor = true; setSongModifiedFlag(); } void deletePatternNote(void) { if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG) return; const int16_t nr = editor.editPattern; int16_t pattPos = editor.pattPos; const int16_t pattLen = pattLens[nr]; tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr != NULL) { if (pattPos > 0) { pattPos--; editor.pattPos = song.pattPos = pattPos; for (int32_t i = pattPos; i < pattLen-1; i++) pattPtr[(i * MAX_VOICES) + cursor.ch] = pattPtr[((i + 1) * MAX_VOICES) + cursor.ch]; memset(&pattPtr[((pattLen - 1) * MAX_VOICES) + cursor.ch], 0, sizeof (tonTyp)); } } else { if (pattPos > 0) { pattPos--; editor.pattPos = song.pattPos = pattPos; } } killPatternIfUnused(nr); ui.updatePatternEditor = true; setSongModifiedFlag(); } void deletePatternLine(void) { if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG) return; const int16_t nr = editor.editPattern; int16_t pattPos = editor.pattPos; const int16_t pattLen = pattLens[nr]; tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr != NULL) { if (pattPos > 0) { pattPos--; editor.pattPos = song.pattPos = pattPos; for (int32_t i = pattPos; i < pattLen-1; i++) { for (int32_t j = 0; j < MAX_VOICES; j++) pattPtr[(i * MAX_VOICES) + j] = pattPtr[((i + 1) * MAX_VOICES) + j]; } memset(&pattPtr[(pattLen - 1) * MAX_VOICES], 0, TRACK_WIDTH); } } else { if (pattPos > 0) { pattPos--; editor.pattPos = song.pattPos = pattPos; } } if (config.recTrueInsert && pattLen > 1) setPatternLen(nr, pattLen - 1); killPatternIfUnused(nr); ui.updatePatternEditor = true; setSongModifiedFlag(); } // ----- TRANSPOSE FUNCTIONS ----- static void countOverflowingNotes(uint8_t currInsOnly, uint8_t transpMode, int8_t addVal) { uint8_t ton; uint16_t p, pattLen, ch, row; tonTyp *pattPtr; transpDelNotes = 0; switch (transpMode) { case TRANSP_TRACK: { pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; // empty pattern pattPtr += cursor.ch; pattLen = pattLens[editor.editPattern]; for (row = 0; row < pattLen; row++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!currInsOnly || pattPtr->instr == editor.curInstr)) { if ((int8_t)ton+addVal > 96 || (int8_t)ton+addVal <= 0) transpDelNotes++; } pattPtr += MAX_VOICES; } } break; case TRANSP_PATT: { pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; // empty pattern pattLen = pattLens[editor.editPattern]; for (row = 0; row < pattLen; row++) { for (ch = 0; ch < song.antChn; ch++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!currInsOnly || pattPtr->instr == editor.curInstr)) { if ((int8_t)ton+addVal > 96 || (int8_t)ton+addVal <= 0) transpDelNotes++; } pattPtr++; } pattPtr += MAX_VOICES - song.antChn; } } break; case TRANSP_SONG: { for (p = 0; p < MAX_PATTERNS; p++) { pattPtr = patt[p]; if (pattPtr == NULL) continue; // empty pattern pattLen = pattLens[p]; for (row = 0; row < pattLen; row++) { for (ch = 0; ch < song.antChn; ch++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!currInsOnly || pattPtr->instr == editor.curInstr)) { if ((int8_t)ton+addVal > 96 || (int8_t)ton+addVal <= 0) transpDelNotes++; } pattPtr++; } pattPtr += MAX_VOICES - song.antChn; } } } break; case TRANSP_BLOCK: { if (pattMark.markY1 == pattMark.markY2) return; // no pattern marking pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; // empty pattern pattPtr += (pattMark.markY1 * MAX_VOICES) + pattMark.markX1; pattLen = pattLens[editor.editPattern]; for (row = pattMark.markY1; row < pattMark.markY2; row++) { for (ch = pattMark.markX1; ch <= pattMark.markX2; ch++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!currInsOnly || pattPtr->instr == editor.curInstr)) { if ((int8_t)ton+addVal > 96 || (int8_t)ton+addVal <= 0) transpDelNotes++; } pattPtr++; } pattPtr += MAX_VOICES - ((pattMark.markX2 + 1) - pattMark.markX1); } } break; default: break; } } void doTranspose(void) { char text[48]; uint8_t ton; uint16_t p, pattLen, ch, row; tonTyp *pattPtr; countOverflowingNotes(lastInsMode, lastTranspMode, lastTranspVal); if (transpDelNotes > 0) { sprintf(text, "%d note(s) will be erased! Proceed?", (int32_t)transpDelNotes); if (okBox(2, "System request", text) != 1) return; } // lastTranspVal is never <-12 or >12, so unsigned testing for >96 is safe switch (lastTranspMode) { case TRANSP_TRACK: { pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; // empty pattern pattPtr += cursor.ch; pattLen = pattLens[editor.editPattern]; for (row = 0; row < pattLen; row++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!lastInsMode || pattPtr->instr == editor.curInstr)) { ton += lastTranspVal; if (ton > 96) ton = 0; // also handles underflow pattPtr->ton = ton; } pattPtr += MAX_VOICES; } } break; case TRANSP_PATT: { pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; // empty pattern pattLen = pattLens[editor.editPattern]; for (row = 0; row < pattLen; row++) { for (ch = 0; ch < song.antChn; ch++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!lastInsMode || pattPtr->instr == editor.curInstr)) { ton += lastTranspVal; if (ton > 96) ton = 0; // also handles underflow pattPtr->ton = ton; } pattPtr++; } pattPtr += MAX_VOICES - song.antChn; } } break; case TRANSP_SONG: { for (p = 0; p < MAX_PATTERNS; p++) { pattPtr = patt[p]; if (pattPtr == NULL) continue; // empty pattern pattLen = pattLens[p]; for (row = 0; row < pattLen; row++) { for (ch = 0; ch < song.antChn; ch++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!lastInsMode || pattPtr->instr == editor.curInstr)) { ton += lastTranspVal; if (ton > 96) ton = 0; // also handles underflow pattPtr->ton = ton; } pattPtr++; } pattPtr += MAX_VOICES - song.antChn; } } } break; case TRANSP_BLOCK: { if (pattMark.markY1 == pattMark.markY2) return; // no pattern marking pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; // empty pattern pattPtr += (pattMark.markY1 * MAX_VOICES) + pattMark.markX1; pattLen = pattLens[editor.editPattern]; for (row = pattMark.markY1; row < pattMark.markY2; row++) { for (ch = pattMark.markX1; ch <= pattMark.markX2; ch++) { ton = pattPtr->ton; if ((ton >= 1 && ton <= 96) && (!lastInsMode || pattPtr->instr == editor.curInstr)) { ton += lastTranspVal; if (ton > 96) ton = 0; // also handles underflow pattPtr->ton = ton; } pattPtr++; } pattPtr += MAX_VOICES - ((pattMark.markX2 + 1) - pattMark.markX1); } } break; default: break; } ui.updatePatternEditor = true; setSongModifiedFlag(); } void trackTranspCurInsUp(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = 1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void trackTranspCurInsDn(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = -1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void trackTranspCurIns12Up(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = 12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void trackTranspCurIns12Dn(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = -12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void trackTranspAllInsUp(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = 1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void trackTranspAllInsDn(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = -1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void trackTranspAllIns12Up(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = 12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void trackTranspAllIns12Dn(void) { lastTranspMode = TRANSP_TRACK; lastTranspVal = -12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void pattTranspCurInsUp(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = 1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void pattTranspCurInsDn(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = -1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void pattTranspCurIns12Up(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = 12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void pattTranspCurIns12Dn(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = -12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void pattTranspAllInsUp(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = 1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void pattTranspAllInsDn(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = -1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void pattTranspAllIns12Up(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = 12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void pattTranspAllIns12Dn(void) { lastTranspMode = TRANSP_PATT; lastTranspVal = -12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void songTranspCurInsUp(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = 1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void songTranspCurInsDn(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = -1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void songTranspCurIns12Up(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = 12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void songTranspCurIns12Dn(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = -12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void songTranspAllInsUp(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = 1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void songTranspAllInsDn(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = -1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void songTranspAllIns12Up(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = 12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void songTranspAllIns12Dn(void) { lastTranspMode = TRANSP_SONG; lastTranspVal = -12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void blockTranspCurInsUp(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = 1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void blockTranspCurInsDn(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = -1; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void blockTranspCurIns12Up(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = 12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void blockTranspCurIns12Dn(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = -12; lastInsMode = TRANSP_CUR_INST; doTranspose(); } void blockTranspAllInsUp(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = 1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void blockTranspAllInsDn(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = -1; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void blockTranspAllIns12Up(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = 12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void blockTranspAllIns12Dn(void) { lastTranspMode = TRANSP_BLOCK; lastTranspVal = -12; lastInsMode = TRANSP_ALL_INST; doTranspose(); } void copyNote(tonTyp *src, tonTyp *dst) { if (editor.copyMaskEnable) { if (editor.copyMask[0]) dst->ton = src->ton; if (editor.copyMask[1]) dst->instr = src->instr; if (editor.copyMask[2]) dst->vol = src->vol; if (editor.copyMask[3]) dst->effTyp = src->effTyp; if (editor.copyMask[4]) dst->eff = src->eff; } else { *dst = *src; } } void pasteNote(tonTyp *src, tonTyp *dst) { if (editor.copyMaskEnable) { if (editor.copyMask[0] && (src->ton != 0 || !editor.transpMask[0])) dst->ton = src->ton; if (editor.copyMask[1] && (src->instr != 0 || !editor.transpMask[1])) dst->instr = src->instr; if (editor.copyMask[2] && (src->vol != 0 || !editor.transpMask[2])) dst->vol = src->vol; if (editor.copyMask[3] && (src->effTyp != 0 || !editor.transpMask[3])) dst->effTyp = src->effTyp; if (editor.copyMask[4] && (src->eff != 0 || !editor.transpMask[4])) dst->eff = src->eff; } else { *dst = *src; } } void cutTrack(void) { tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; const int16_t pattLen = pattLens[editor.editPattern]; if (config.ptnCutToBuffer) { memset(trackCopyBuff, 0, MAX_PATT_LEN * sizeof (tonTyp)); for (int16_t i = 0; i < pattLen; i++) copyNote(&pattPtr[(i * MAX_VOICES) + cursor.ch], &trackCopyBuff[i]); trkBufLen = pattLen; } pauseMusic(); for (int16_t i = 0; i < pattLen; i++) pasteNote(&clearNote, &pattPtr[(i * MAX_VOICES) + cursor.ch]); resumeMusic(); killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } void copyTrack(void) { tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; const int16_t pattLen = pattLens[editor.editPattern]; memset(trackCopyBuff, 0, MAX_PATT_LEN * sizeof (tonTyp)); for (int16_t i = 0; i < pattLen; i++) copyNote(&pattPtr[(i * MAX_VOICES) + cursor.ch], &trackCopyBuff[i]); trkBufLen = pattLen; } void pasteTrack(void) { if (trkBufLen == 0 || !allocatePattern(editor.editPattern)) return; tonTyp *pattPtr = patt[editor.editPattern]; const int16_t pattLen = pattLens[editor.editPattern]; pauseMusic(); for (int16_t i = 0; i < pattLen; i++) pasteNote(&trackCopyBuff[i], &pattPtr[(i * MAX_VOICES) + cursor.ch]); resumeMusic(); killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } void cutPattern(void) { tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; const int16_t pattLen = pattLens[editor.editPattern]; if (config.ptnCutToBuffer) { memset(ptnCopyBuff, 0, (MAX_PATT_LEN * MAX_VOICES) * sizeof (tonTyp)); for (int16_t x = 0; x < song.antChn; x++) { for (int16_t i = 0; i < pattLen; i++) copyNote(&pattPtr[(i * MAX_VOICES) + x], &ptnCopyBuff[(i * MAX_VOICES) + x]); } ptnBufLen = pattLen; } pauseMusic(); for (int16_t x = 0; x < song.antChn; x++) { for (int16_t i = 0; i < pattLen; i++) pasteNote(&clearNote, &pattPtr[(i * MAX_VOICES) + x]); } resumeMusic(); killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } void copyPattern(void) { tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; const int16_t pattLen = pattLens[editor.editPattern]; memset(ptnCopyBuff, 0, (MAX_PATT_LEN * MAX_VOICES) * sizeof (tonTyp)); for (int16_t x = 0; x < song.antChn; x++) { for (int16_t i = 0; i < pattLen; i++) copyNote(&pattPtr[(i * MAX_VOICES) + x], &ptnCopyBuff[(i * MAX_VOICES) + x]); } ptnBufLen = pattLen; ui.updatePatternEditor = true; } void pastePattern(void) { if (ptnBufLen == 0) return; if (pattLens[editor.editPattern] != ptnBufLen) { if (okBox(1, "System request", "Change pattern length to copybuffer's length?") == 1) setPatternLen(editor.editPattern, ptnBufLen); } if (!allocatePattern(editor.editPattern)) return; tonTyp *pattPtr = patt[editor.editPattern]; const int16_t pattLen = pattLens[editor.editPattern]; pauseMusic(); for (int16_t x = 0; x < song.antChn; x++) { for (int16_t i = 0; i < pattLen; i++) pasteNote(&ptnCopyBuff[(i * MAX_VOICES) + x], &pattPtr[(i * MAX_VOICES) + x]); } resumeMusic(); killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } void cutBlock(void) { if (pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2) return; tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; if (config.ptnCutToBuffer) { for (int16_t x = pattMark.markX1; x <= pattMark.markX2; x++) { for (int16_t y = pattMark.markY1; y < pattMark.markY2; y++) { assert(x < song.antChn && y < pattLens[editor.editPattern]); copyNote(&pattPtr[(y * MAX_VOICES) + x], &blkCopyBuff[((y - pattMark.markY1) * MAX_VOICES) + (x - pattMark.markX1)]); } } } pauseMusic(); for (int16_t x = pattMark.markX1; x <= pattMark.markX2; x++) { for (int16_t y = pattMark.markY1; y < pattMark.markY2; y++) pasteNote(&clearNote, &pattPtr[(y * MAX_VOICES) + x]); } resumeMusic(); markXSize = pattMark.markX2 - pattMark.markX1; markYSize = pattMark.markY2 - pattMark.markY1; blockCopied = true; killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } void copyBlock(void) { if (pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2) return; tonTyp *pattPtr = patt[editor.editPattern]; if (pattPtr == NULL) return; for (int16_t x = pattMark.markX1; x <= pattMark.markX2; x++) { for (int16_t y = pattMark.markY1; y < pattMark.markY2; y++) { assert(x < song.antChn && y < pattLens[editor.editPattern]); copyNote(&pattPtr[(y * MAX_VOICES) + x], &blkCopyBuff[((y - pattMark.markY1) * MAX_VOICES) + (x - pattMark.markX1)]); } } markXSize = pattMark.markX2 - pattMark.markX1; markYSize = pattMark.markY2 - pattMark.markY1; blockCopied = true; } void pasteBlock(void) { if (!blockCopied || !allocatePattern(editor.editPattern)) return; const int16_t pattLen = pattLens[editor.editPattern]; const int32_t xpos = cursor.ch; const int32_t ypos = editor.pattPos; int32_t j = markXSize; if (j+xpos >= song.antChn) j = song.antChn - xpos - 1; int32_t k = markYSize; if (k+ypos >= pattLen) k = pattLen - ypos; tonTyp *pattPtr = patt[editor.editPattern]; pauseMusic(); for (int32_t x = xpos; x <= xpos+j; x++) { for (int32_t y = ypos; y < ypos+k; y++) { assert(x < song.antChn && y < pattLen); pasteNote(&blkCopyBuff[((y - ypos) * MAX_VOICES) + (x - xpos)], &pattPtr[(y * MAX_VOICES) + x]); } } resumeMusic(); killPatternIfUnused(editor.editPattern); ui.updatePatternEditor = true; setSongModifiedFlag(); } static void remapInstrXY(uint16_t nr, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t src, uint8_t dst) { // this routine is only used sanely, so no need to check input tonTyp *pattPtr = patt[nr]; if (pattPtr == NULL) return; const int32_t noteSkipLen = MAX_VOICES - ((x2 + 1) - x1); tonTyp *note = &pattPtr[(y1 * MAX_VOICES) + x1]; for (uint16_t y = y1; y <= y2; y++) { for (uint16_t x = x1; x <= x2; x++) { assert(x < song.antChn && y < pattLens[nr]); if (note->instr == src) note->instr = dst; note++; } note += noteSkipLen; } } void remapBlock(void) { if (editor.srcInstr == editor.curInstr || pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2) return; pauseMusic(); remapInstrXY(editor.editPattern, pattMark.markX1, pattMark.markY1, pattMark.markX2, pattMark.markY2 - 1, editor.srcInstr, editor.curInstr); resumeMusic(); ui.updatePatternEditor = true; setSongModifiedFlag(); } void remapTrack(void) { if (editor.srcInstr == editor.curInstr) return; pauseMusic(); remapInstrXY(editor.editPattern, cursor.ch, 0, cursor.ch, pattLens[editor.editPattern] - 1, editor.srcInstr, editor.curInstr); resumeMusic(); ui.updatePatternEditor = true; setSongModifiedFlag(); } void remapPattern(void) { if (editor.srcInstr == editor.curInstr) return; pauseMusic(); remapInstrXY(editor.editPattern, 0, 0, (uint16_t)(song.antChn - 1), pattLens[editor.editPattern] - 1, editor.srcInstr, editor.curInstr); resumeMusic(); ui.updatePatternEditor = true; setSongModifiedFlag(); } void remapSong(void) { if (editor.srcInstr == editor.curInstr) return; pauseMusic(); for (int32_t i = 0; i < MAX_PATTERNS; i++) { const uint8_t pattNr = (uint8_t)i; remapInstrXY(pattNr, 0, 0, (uint16_t)(song.antChn - 1), pattLens[pattNr] - 1, editor.srcInstr, editor.curInstr); } resumeMusic(); ui.updatePatternEditor = true; setSongModifiedFlag(); } // "scale-fade volume" routines static int8_t getNoteVolume(tonTyp *note) { int8_t nv, vv, ev; if (note->vol >= 0x10 && note->vol <= 0x50) vv = note->vol - 0x10; else vv = -1; if (note->effTyp == 0xC) ev = MIN(note->eff, 64); else ev = -1; if (note->instr != 0 && instr[note->instr] != NULL) nv = (int8_t)instr[note->instr]->samp[0].vol; else nv = -1; int8_t finalv = -1; if (nv >= 0) finalv = nv; if (vv >= 0) finalv = vv; if (ev >= 0) finalv = ev; return finalv; } static void setNoteVolume(tonTyp *note, int8_t newVol) { if (newVol < 0) return; const int8_t oldv = getNoteVolume(note); if (note->vol == oldv) return; // volume is the same if (note->effTyp == 0x0C) note->eff = newVol; // Cxx effect else note->vol = 0x10 + newVol; // volume column } static void scaleNote(uint16_t ptn, int8_t ch, int16_t row, double dScale) { if (patt[ptn] == NULL) return; const int16_t pattLen = pattLens[ptn]; if (row < 0 || row >= pattLen || ch < 0 || ch >= song.antChn) return; tonTyp *note = &patt[ptn][(row * MAX_VOICES) + ch]; int32_t vol = getNoteVolume(note); if (vol >= 0) { vol = (int32_t)((vol * dScale) + 0.5); // rounded vol = MIN(MAX(0, vol), 64); setNoteVolume(note, (int8_t)vol); } } static bool askForScaleFade(char *msg) { char volstr[32+1]; sprintf(volstr, "%0.2f,%0.2f", dVolScaleFK1, dVolScaleFK2); if (inputBox(1, msg, volstr, sizeof (volstr) - 1) != 1) return false; bool err = false; char *val1 = volstr; if (strlen(val1) < 3) err = true; char *val2 = strchr(volstr, ','); if (val2 == NULL || strlen(val2) < 3) err = true; if (err) { okBox(0, "System message", "Invalid constant expressions."); return false; } dVolScaleFK1 = atof(val1); dVolScaleFK2 = atof(val2+1); return true; } void scaleFadeVolumeTrack(void) { if (!askForScaleFade("Volume scale-fade track (start-, end scale)")) return; if (patt[editor.editPattern] == NULL) return; const int32_t pattLen = pattLens[editor.editPattern]; double dIPy = 0.0; if (pattLen > 0) dIPy = (dVolScaleFK2 - dVolScaleFK1) / pattLen; double dVol = dVolScaleFK1; pauseMusic(); for (int16_t row = 0; row < pattLen; row++) { scaleNote(editor.editPattern, cursor.ch, row, dVol); dVol += dIPy; } resumeMusic(); } void scaleFadeVolumePattern(void) { if (!askForScaleFade("Volume scale-fade pattern (start-, end scale)")) return; if (patt[editor.editPattern] == NULL) return; const int32_t pattLen = pattLens[editor.editPattern]; double dIPy = 0.0; if (pattLen > 0) dIPy = (dVolScaleFK2 - dVolScaleFK1) / pattLen; double dVol = dVolScaleFK1; pauseMusic(); for (int16_t row = 0; row < pattLen; row++) { for (int8_t ch = 0; ch < song.antChn; ch++) scaleNote(editor.editPattern, ch, row, dVol); dVol += dIPy; } resumeMusic(); } void scaleFadeVolumeBlock(void) { if (!askForScaleFade("Volume scale-fade block (start-, end scale)")) return; if (patt[editor.editPattern] == NULL || pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2) return; const int32_t dy = pattMark.markY2 - pattMark.markY1; double dIPy = 0.0; if (dy > 0) dIPy = (dVolScaleFK2 - dVolScaleFK1) / dy; double dVol = dVolScaleFK1; pauseMusic(); for (int16_t row = pattMark.markY1; row < pattMark.markY2; row++) { for (int16_t ch = pattMark.markX1; ch <= pattMark.markX2; ch++) scaleNote(editor.editPattern, (uint8_t)ch, row, dVol); dVol += dIPy; } resumeMusic(); } void toggleCopyMaskEnable(void) { editor.copyMaskEnable ^= 1; } void toggleCopyMask0(void) { editor.copyMask[0] ^= 1; }; void toggleCopyMask1(void) { editor.copyMask[1] ^= 1; }; void toggleCopyMask2(void) { editor.copyMask[2] ^= 1; }; void toggleCopyMask3(void) { editor.copyMask[3] ^= 1; }; void toggleCopyMask4(void) { editor.copyMask[4] ^= 1; }; void togglePasteMask0(void) { editor.pasteMask[0] ^= 1; }; void togglePasteMask1(void) { editor.pasteMask[1] ^= 1; }; void togglePasteMask2(void) { editor.pasteMask[2] ^= 1; }; void togglePasteMask3(void) { editor.pasteMask[3] ^= 1; }; void togglePasteMask4(void) { editor.pasteMask[4] ^= 1; }; void toggleTranspMask0(void) { editor.transpMask[0] ^= 1; }; void toggleTranspMask1(void) { editor.transpMask[1] ^= 1; }; void toggleTranspMask2(void) { editor.transpMask[2] ^= 1; }; void toggleTranspMask3(void) { editor.transpMask[3] ^= 1; }; void toggleTranspMask4(void) { editor.transpMask[4] ^= 1; };