ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

commit 380d39a7206417b80dc6468d44c8cc3f93c197d5
parent 4dd5fb98c4325b4fb674bce5dec18652fdd5b22a
Author: Olav Sørensen <olav.sorensen@live.no>
Date:   Tue,  4 Mar 2025 18:31:28 +0100

New smp. ed. "effects" screen, code cleanup + more

Diffstat:
Msrc/ft2_about.c | 44+++++++++++++++++---------------------------
Msrc/ft2_checkboxes.c | 5+++++
Msrc/ft2_checkboxes.h | 3+++
Msrc/ft2_edit.c | 2+-
Msrc/ft2_header.h | 2+-
Msrc/ft2_main.c | 2++
Msrc/ft2_midi.c | 2+-
Msrc/ft2_module_loader.c | 2++
Msrc/ft2_pattern_draw.c | 6+++---
Msrc/ft2_pattern_ed.c | 2++
Msrc/ft2_pushbuttons.c | 26++++++++++++++++++++++++--
Msrc/ft2_pushbuttons.h | 22+++++++++++++++++++++-
Asrc/ft2_random.c | 25+++++++++++++++++++++++++
Asrc/ft2_random.h | 7+++++++
Msrc/ft2_replayer.c | 40++++++++++++++++++++--------------------
Msrc/ft2_replayer.h | 6+++++-
Msrc/ft2_sample_ed.c | 51++++++++++++++++++++++++++++++---------------------
Msrc/ft2_sample_ed.h | 1-
Asrc/ft2_smpfx.c | 1389+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ft2_smpfx.h | 26++++++++++++++++++++++++++
Msrc/ft2_structs.h | 6+++---
Msrc/ft2_sysreqs.c | 42+++++++++++++++++++++++++++++++++++-------
Msrc/ft2_tables.c | 21++-------------------
Msrc/ft2_tables.h | 6++----
Msrc/ft2_textboxes.c | 3+++
Msrc/scopes/ft2_scopes.c | 30+++++++++++++++---------------
Mvs2019_project/ft2-clone/ft2-clone.vcxproj | 4++++
Mvs2019_project/ft2-clone/ft2-clone.vcxproj.filters | 8++++++++
28 files changed, 1656 insertions(+), 127 deletions(-)

diff --git a/src/ft2_about.c b/src/ft2_about.c @@ -8,6 +8,7 @@ #include "ft2_gfxdata.h" #include "ft2_pattern_ed.h" // exitPatternEditorExtended() #include "ft2_config.h" +#include "ft2_random.h" #define OLD_NUM_STARS 1000 #define NUM_STARS 1500 @@ -64,17 +65,6 @@ static oldMatrix_t oldStarMatrix; static vector_t starPoints[NUM_STARS], starRotation; static matrix_t starMatrix; -// exact Turbo Pascal Random() implementation -static int32_t Random(int32_t limit) -{ - static uint32_t randSeed; // seed is 0 in Turbo Pascal unless Randomize() is called - - randSeed *= 134775813; - randSeed++; - - return ((int64_t)randSeed * limit) >> 32; -} - static uint32_t blendPixels(uint32_t pixelA, uint32_t pixelB, uint16_t alpha) { const uint16_t invAlpha = alpha ^ 0xFFFF; @@ -333,7 +323,7 @@ void showAboutScreen(void) // called once when about screen is opened { oldVector_t *s = oldStarPoints; - const int32_t type = Random(4); + const int32_t type = randoml(4); switch (type) { // classic "space stars" @@ -342,9 +332,9 @@ void showAboutScreen(void) // called once when about screen is opened zSpeed = 309; for (int32_t i = 0; i < OLD_NUM_STARS; i++, s++) { - s->z = (int16_t)Random(0xFFFF) - 0x8000; - s->y = (int16_t)Random(0xFFFF) - 0x8000; - s->x = (int16_t)Random(0xFFFF) - 0x8000; + s->z = (int16_t)randoml(0xFFFF) - 0x8000; + s->y = (int16_t)randoml(0xFFFF) - 0x8000; + s->x = (int16_t)randoml(0xFFFF) - 0x8000; } } break; @@ -357,17 +347,17 @@ void showAboutScreen(void) // called once when about screen is opened { if (i < OLD_NUM_STARS/4) { - s->z = (int16_t)Random(0xFFFF) - 0x8000; - s->y = (int16_t)Random(0xFFFF) - 0x8000; - s->x = (int16_t)Random(0xFFFF) - 0x8000; + s->z = (int16_t)randoml(0xFFFF) - 0x8000; + s->y = (int16_t)randoml(0xFFFF) - 0x8000; + s->x = (int16_t)randoml(0xFFFF) - 0x8000; } else { - int32_t r = Random(30000); - int32_t n = Random(5); - int32_t w = ((2 * Random(2)) - 1) * Sqr(Random(1000)); + int32_t r = randoml(30000); + int32_t n = randoml(5); + int32_t w = ((2 * randoml(2)) - 1) * Sqr(randoml(1000)); double ww = (((PI * 2.0) / 5.0) * n) + (r * (1.0 / 12000.0)) + (w * (1.0 / 3000000.0)); - int32_t h = ((Sqr(r) / 30000) * (Random(10000) - 5000)) / 12000; + int32_t h = ((Sqr(r) / 30000) * (randoml(10000) - 5000)) / 12000; s->x = (int16_t)(r * cos(ww)); s->y = (int16_t)(r * sin(ww)); @@ -384,8 +374,8 @@ void showAboutScreen(void) // called once when about screen is opened zSpeed = 0; for (int32_t i = 0; i < OLD_NUM_STARS; i++, s++) { - int32_t r = (int32_t)round(sqrt(Random(500) * 500)); - int32_t w = Random(3000); + int32_t r = (int32_t)round(sqrt(randoml(500) * 500)); + int32_t w = randoml(3000); double ww = ((w * 8) + r) * (1.0 / 16.0); const int16_t z = (int16_t)round(32767.0 * cos(w * (2.0 * PI / 1024.0))); @@ -421,9 +411,9 @@ void initAboutScreen(void) vector_t *s = starPoints; for (int32_t i = 0; i < NUM_STARS; i++, s++) { - s->x = (float)((Random(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); - s->y = (float)((Random(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); - s->z = (float)((Random(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); + s->x = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); + s->y = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); + s->z = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX)); } sinp1 = 0; diff --git a/src/ft2_checkboxes.c b/src/ft2_checkboxes.c @@ -15,6 +15,7 @@ #include "ft2_edit.h" #include "ft2_bmp.h" #include "ft2_wav_renderer.h" +#include "ft2_smpfx.h" #include "ft2_structs.h" checkBox_t checkBoxes[NUM_CHECKBOXES] = @@ -70,6 +71,10 @@ checkBox_t checkBoxes[NUM_CHECKBOXES] = { 3, 112, 148, 12, cbInstMidiEnable }, { 172, 112, 103, 12, cbInstMuteComputer }, + // ------ SAMPLE EDITOR EFFECTS CHECKBOXES ------ + //x, y, w, h, funcOnUp + { 119, 384, 95, 12, cbSfxNormalization }, + // ------ TRIM SCREEN CHECKBOXES ------ //x, y, w, h, funcOnUp { 3, 107, 113, 12, cbTrimUnusedPatt }, diff --git a/src/ft2_checkboxes.h b/src/ft2_checkboxes.h @@ -42,6 +42,9 @@ enum // CHECKBOXES CB_INST_EXT_MIDI, CB_INST_EXT_MUTE, + // SAMPLE EDITOR EFFECTS + CB_SAMPFX_NORMALIZATION, + // TRIM CB_TRIM_PATT, CB_TRIM_INST, diff --git a/src/ft2_edit.c b/src/ft2_edit.c @@ -369,7 +369,7 @@ void recordNote(uint8_t noteNum, int8_t vol) // directly ported from the origina time = INT32_MAX; for (i = 0; i < song.numChannels; i++) { - if (editor.chnMode[i] && config.multiRecChn[i] && editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0) + if (!editor.channelMuted[i] && config.multiRecChn[i] && editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0) { c = i; time = editor.keyOffTime[i]; diff --git a/src/ft2_header.h b/src/ft2_header.h @@ -12,7 +12,7 @@ #endif #include "ft2_replayer.h" -#define PROG_VER_STR "1.94" +#define PROG_VER_STR "1.95" // do NOT change these! It will only mess things up... diff --git a/src/ft2_main.c b/src/ft2_main.c @@ -35,6 +35,7 @@ #include "ft2_bmp.h" #include "ft2_structs.h" #include "ft2_hpc.h" +#include "ft2_smpfx.h" static void initializeVars(void); static void cleanUpAndExit(void); // never call this inside the main loop @@ -362,6 +363,7 @@ static void cleanUpAndExit(void) // never call this inside the main loop! freeSprites(); freeDiskOp(); clearCopyBuffer(); + clearSampleUndo(); freeAudioDeviceSelectorBuffers(); windUpFTHelp(); freeTextBoxes(); diff --git a/src/ft2_midi.c b/src/ft2_midi.c @@ -220,7 +220,7 @@ void recordMIDIEffect(uint8_t efx, uint8_t efxData) note_t *p = &pattern[editor.editPattern][editor.row * MAX_CHANNELS]; for (int32_t i = 0; i < song.numChannels; i++, p++) { - if (config.multiRecChn[i] && editor.chnMode[i]) + if (config.multiRecChn[i] && !editor.channelMuted[i]) { if (!allocatePattern(editor.editPattern)) return; diff --git a/src/ft2_module_loader.c b/src/ft2_module_loader.c @@ -19,6 +19,7 @@ #include "ft2_gui.h" #include "ft2_diskop.h" #include "ft2_sample_loader.h" +#include "ft2_smpfx.h" #include "ft2_mouse.h" #include "ft2_midi.h" #include "ft2_events.h" @@ -498,6 +499,7 @@ static void setupLoadedModule(void) updateChanNums(); resetWavRenderer(); clearPattMark(); + clearSampleUndo(); resetTrimSizes(); resetPlaybackTime(); diff --git a/src/ft2_pattern_draw.c b/src/ft2_pattern_draw.c @@ -300,7 +300,7 @@ static void writePatternBlockMark(int32_t currRow, uint32_t rowHeight, const pat static void drawChannelNumbering(uint16_t yPos) { uint16_t xPos = 30; - int32_t ch = ui.channelOffset + 1; + uint8_t ch = ui.channelOffset + 1; for (uint8_t i = 0; i < ui.numChannelsShown; i++) { @@ -310,8 +310,8 @@ static void drawChannelNumbering(uint16_t yPos) } else { - charOutOutlined(xPos, yPos, PAL_MOUSEPT, chDecTab1[ch]); - charOutOutlined(xPos + (FONT1_CHAR_W + 1), yPos, PAL_MOUSEPT, chDecTab2[ch]); + charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + (ch / 10)); + charOutOutlined(xPos + (FONT1_CHAR_W + 1), yPos, PAL_MOUSEPT, '0' + (ch % 10)); } ch++; diff --git a/src/ft2_pattern_ed.c b/src/ft2_pattern_ed.c @@ -613,6 +613,7 @@ void patternEditorExtended(void) ui._instEditorShown = ui.instEditorShown; ui._instEditorExtShown = ui.instEditorExtShown; ui._sampleEditorExtShown = ui.sampleEditorExtShown; + ui._sampleEditorEffectsShown = ui.sampleEditorEffectsShown; ui._patternEditorShown = ui.patternEditorShown; ui._sampleEditorShown = ui.sampleEditorShown; ui._advEditShown= ui.advEditShown; @@ -703,6 +704,7 @@ void exitPatternEditorExtended(void) ui.instEditorShown = ui._instEditorShown; ui.instEditorExtShown = ui._instEditorExtShown; ui.sampleEditorExtShown = ui._sampleEditorExtShown; + ui.sampleEditorEffectsShown = ui._sampleEditorEffectsShown; ui.patternEditorShown = ui._patternEditorShown; ui.sampleEditorShown = ui._sampleEditorShown; ui.advEditShown = ui._advEditShown; diff --git a/src/ft2_pushbuttons.c b/src/ft2_pushbuttons.c @@ -27,6 +27,7 @@ #include "ft2_mouse.h" #include "ft2_edit.h" #include "ft2_sample_ed_features.h" +#include "ft2_smpfx.h" #include "ft2_palette.h" #include "ft2_structs.h" #include "ft2_bmp.h" @@ -218,7 +219,7 @@ pushButton_t pushButtons[NUM_PUSHBUTTONS] = { 251, 382, 43, 16, 0, 0, "Paste", NULL, NULL, sampPaste }, { 300, 348, 50, 16, 0, 0, "Crop", NULL, NULL, sampCrop }, { 300, 365, 50, 16, 0, 0, "Volume", NULL, NULL, pbSampleVolume }, - { 300, 382, 50, 16, 0, 0, "X-Fade", NULL, NULL, sampXFade }, + { 300, 382, 50, 16, 0, 0, "Effects", NULL, NULL, pbEffects }, { 430, 348, 54, 16, 0, 0, "Exit", NULL, NULL, exitSampleEditor }, { 594, 348, 35, 13, 0, 0, "Clr S.", NULL, NULL, clearSample }, { 594, 360, 35, 13, 0, 0, "Min.", NULL, NULL, sampMinimize }, @@ -227,13 +228,34 @@ pushButton_t pushButtons[NUM_PUSHBUTTONS] = { 594, 385, 18, 13, 2, 4, ARROW_UP_STRING, NULL, sampReplenUp, NULL }, { 611, 385, 18, 13, 2, 4, ARROW_DOWN_STRING, NULL, sampReplenDown, NULL }, + // ------ SAMPLE EDITOR EFFECTS PUSHBUTTONS ------ + //x, y, w, h, p, d, text #1, text #2, funcOnDown, funcOnUp + { 78, 350, 18, 13, 2, 2, ARROW_UP_STRING, NULL, pbSfxCyclesUp, NULL }, + { 95, 350, 18, 13, 2, 2, ARROW_DOWN_STRING, NULL, pbSfxCyclesDown, NULL }, + { 3, 365, 54, 16, 0, 0, "Triangle", NULL, NULL, pbSfxTriangle }, + { 59, 365, 54, 16, 0, 0, "Saw", NULL, NULL, pbSfxSaw }, + { 3, 382, 54, 16, 0, 0, "Sine", NULL, NULL, pbSfxSine }, + { 59, 382, 54, 16, 0, 0, "Square", NULL, NULL, pbSfxSquare }, + { 192, 350, 18, 13, 1, 2, ARROW_UP_STRING, NULL, pbSfxResoUp, NULL }, + { 209, 350, 18, 13, 1, 2, ARROW_DOWN_STRING, NULL, pbSfxResoDown, NULL }, + { 119, 365, 53, 16, 0, 0, "lp filter", NULL, NULL, pbSfxLowPass }, + { 174, 365, 53, 16, 0, 0, "hp filter", NULL, NULL, pbSfxHighPass }, + { 269, 350, 13, 13, 0, 0, "-", NULL, NULL, pbSfxSubBass }, + { 281, 350, 13, 13, 0, 0, "+", NULL, NULL, pbSfxAddBass }, + { 269, 367, 13, 13, 0, 0, "-", NULL, NULL, pbSfxSubTreble }, + { 281, 367, 13, 13, 0, 0, "+", NULL, NULL, pbSfxAddTreble }, + { 233, 382, 61, 16, 0, 0, "Amplitude", NULL, NULL, pbSfxSetAmp }, + { 300, 348, 50, 16, 0, 0, "Undo", NULL, NULL, pbSfxUndo }, + { 300, 365, 50, 16, 0, 0, "X-Fade", NULL, NULL, sampXFade }, + { 300, 382, 50, 16, 0, 0, "Back...", NULL, NULL, hideSampleEffectsScreen }, + // ------ SAMPLE EDITOR EXTENSION PUSHBUTTONS ------ //x, y, w, h, p, d, text #1, text #2, funcOnDown, funcOnUp { 3, 138, 52, 16, 0, 0, "Clr. c.bf", NULL, NULL, clearCopyBuffer }, { 56, 138, 49, 16, 0, 0, "Sign", NULL, NULL, sampleChangeSign }, { 106, 138, 49, 16, 0, 0, "Echo", NULL, NULL, pbSampleEcho }, { 3, 155, 52, 16, 0, 0, "Backw.", NULL, NULL, sampleBackwards }, - { 56, 155, 49, 16, 0, 0, "B. swap", NULL, NULL, sampleByteSwap }, + { 56, 155, 49, 16, 0, 0, "B. swap", NULL, NULL, sampleByteSwap }, { 106, 155, 49, 16, 0, 0, "Fix DC", NULL, NULL, fixDC }, { 161, 121, 60, 16, 0, 0, "Copy ins.", NULL, NULL, copyInstr }, { 222, 121, 66, 16, 0, 0, "Copy smp.", NULL, NULL, copySmp }, diff --git a/src/ft2_pushbuttons.h b/src/ft2_pushbuttons.h @@ -168,7 +168,7 @@ enum // PUSHBUTTONS PB_SAMP_PASTE, PB_SAMP_CROP, PB_SAMP_VOLUME, - PB_SAMP_XFADE, + PB_SAMP_EFFECTS, PB_SAMP_EXIT, PB_SAMP_CLEAR, PB_SAMP_MIN, @@ -177,6 +177,26 @@ enum // PUSHBUTTONS PB_SAMP_REPLEN_UP, PB_SAMP_REPLEN_DOWN, + // SAMPLE EDITOR EFFECTS SCREEN + PB_SAMPFX_CYCLES_UP, + PB_SAMPFX_CYCLES_DOWN, + PB_SAMPFX_TRIANGLE, + PB_SAMPFX_SAW, + PB_SAMPFX_SINE, + PB_SAMPFX_SQUARE, + PB_SAMPFX_RESO_UP, + PB_SAMPFX_RESO_DOWN, + PB_SAMPFX_LOWPASS, + PB_SAMPFX_HIGHPASS, + PB_SAMPFX_SUB_BASS, + PB_SAMPFX_SUB_TREBLE, + PB_SAMPFX_ADD_BASS, + PB_SAMPFX_ADD_TREBLE, + PB_SAMPFX_SET_AMP, + PB_SAMPFX_UNDO, + PB_SAMPFX_XFADE, + PB_SAMPFX_BACK, + // SAMPLE EDITOR EXTENSION PB_SAMP_EXT_CLEAR_COPYBUF, PB_SAMP_EXT_CONV, diff --git a/src/ft2_random.c b/src/ft2_random.c @@ -0,0 +1,25 @@ +#include <SDL2/SDL.h> +#include <stdint.h> +#include <stdbool.h> + +static uint32_t randSeed; + +void randomize(void) +{ + randSeed = (uint32_t)SDL_GetTicks(); +} + +int32_t randoml(int32_t limit) +{ + randSeed *= 134775813; + randSeed++; + return ((int64_t)randSeed * (int32_t)limit) >> 32; +} + +int32_t random32(void) +{ + randSeed *= 134775813; + randSeed++; + + return randSeed; +} diff --git a/src/ft2_random.h b/src/ft2_random.h @@ -0,0 +1,7 @@ +#pragma once + +#include <stdint.h> + +void randomize(void); +int32_t randoml(int32_t limit); +int32_t random32(void); diff --git a/src/ft2_replayer.c b/src/ft2_replayer.c @@ -42,7 +42,7 @@ typedef void (*efxRoutine)(channel_t *ch, uint8_t param); int8_t playMode = 0; bool songPlaying = false, audioPaused = false, musicPaused = false; volatile bool replayerBusy = false; -const uint16_t *note2Period = NULL; +const uint16_t *note2PeriodLUT = NULL; int16_t patternNumRows[MAX_PATTERNS]; channel_t channel[MAX_CHANNELS]; song_t song; @@ -120,7 +120,7 @@ void resetChannels(void) ch->outPan = 128; ch->finalPan = 128; - ch->channelOff = !editor.chnMode[i]; // set channel mute flag from global mute flag + ch->channelOff = editor.channelMuted[i]; // set channel mute flag from global mute flag } if (audioWasntLocked) @@ -274,7 +274,7 @@ double getSampleC4Rate(sample_t *s) const int32_t C4Period = (note << 4) + (((int8_t)s->finetune >> 3) + 16); - const int32_t period = audio.linearPeriodsFlag ? linearPeriods[C4Period] : amigaPeriods[C4Period]; + const int32_t period = audio.linearPeriodsFlag ? linearPeriodLUT[C4Period] : amigaPeriodLUT[C4Period]; return dPeriod2Hz(period); } @@ -285,9 +285,9 @@ void setLinearPeriods(bool linearPeriodsFlag) audio.linearPeriodsFlag = linearPeriodsFlag; if (audio.linearPeriodsFlag) - note2Period = linearPeriods; + note2PeriodLUT = linearPeriodLUT; else - note2Period = amigaPeriods; + note2PeriodLUT = amigaPeriodLUT; resumeAudio(); @@ -302,7 +302,7 @@ void setLinearPeriods(bool linearPeriodsFlag) drawC4Rate(); } -static void resetVolumes(channel_t *ch) +void resetVolumes(channel_t *ch) { ch->realVol = ch->oldVol; ch->outVol = ch->oldVol; @@ -311,7 +311,7 @@ static void resetVolumes(channel_t *ch) ch->status |= IS_Vol + IS_Pan + IS_QuickVol; } -static void triggerInstrument(channel_t *ch) +void triggerInstrument(channel_t *ch) { if (!(ch->vibTremCtrl & 0x04)) ch->vibratoPos = 0; if (!(ch->vibTremCtrl & 0x40)) ch->tremoloPos = 0; @@ -430,8 +430,8 @@ void calcReplayerVars(int32_t audioFreq) // for piano in Instr. Ed. (values outside 0..95 can happen) int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote) { - assert(note2Period != NULL); - if (period > note2Period[0]) + assert(note2PeriodLUT != NULL); + if (period > note2PeriodLUT[0]) return -1; // outside left piano edge finetune = ((int8_t)finetune >> 3) + 16; // -128..127 -> 0..31 @@ -447,7 +447,7 @@ int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote) if (lookUp < 0) lookUp = 0; - if (period >= note2Period[lookUp]) + if (period >= note2PeriodLUT[lookUp]) hiPeriod = (tmpPeriod - finetune) & ~15; else loPeriod = (tmpPeriod - finetune) & ~15; @@ -456,7 +456,7 @@ int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote) return (loPeriod >> 4) - relativeNote; } -static void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch) +void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch) { if (note == NOTE_OFF) { @@ -507,8 +507,8 @@ static void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *c { const uint16_t noteIndex = ((note-1) * 16) + (((int8_t)ch->finetune >> 3) + 16); // 0..1920 - assert(note2Period != NULL); - ch->outPeriod = ch->realPeriod = note2Period[noteIndex]; + assert(note2PeriodLUT != NULL); + ch->outPeriod = ch->realPeriod = note2PeriodLUT[noteIndex]; } ch->status |= IS_Period + IS_Vol + IS_Pan + IS_Trigger + IS_QuickVol; @@ -1237,8 +1237,8 @@ static void preparePortamento(channel_t *ch, const note_t *p, uint8_t inst) const uint16_t note = (((p->note-1) + ch->relativeNote) * 16) + (((int8_t)ch->finetune >> 3) + 16); if (note < MAX_NOTES) { - assert(note2Period != NULL); - ch->portamentoTargetPeriod = note2Period[note]; + assert(note2PeriodLUT != NULL); + ch->portamentoTargetPeriod = note2PeriodLUT[note]; if (ch->portamentoTargetPeriod == ch->realPeriod) ch->portamentoDirection = 0; @@ -1366,7 +1366,7 @@ static void getNewNote(channel_t *ch, const note_t *p) handleEffects_TickZero(ch); } -static void updateVolPanAutoVib(channel_t *ch) +void updateVolPanAutoVib(channel_t *ch) { bool envInterpolateFlag, envDidInterpolate; uint8_t envPos; @@ -1708,7 +1708,7 @@ static uint16_t adjustPeriodFromNote(uint16_t period, uint8_t arpNote, channel_t if (lookUp < 0) lookUp = 0; // safety fix (C-0 w/ f.tune <= -65). This seems to result in 0 in FT2 (TODO: verify) - if (period >= note2Period[lookUp]) + if (period >= note2PeriodLUT[lookUp]) hiPeriod = (tmpPeriod - fineTune) & ~15; else loPeriod = (tmpPeriod - fineTune) & ~15; @@ -1718,7 +1718,7 @@ static uint16_t adjustPeriodFromNote(uint16_t period, uint8_t arpNote, channel_t if (tmpPeriod >= (8*12*16+15)-1) // FT2 bug, should've been 10*12*16+16 (also notice the +2 difference) tmpPeriod = (8*12*16+16)-1; - return note2Period[tmpPeriod]; + return note2PeriodLUT[tmpPeriod]; } static void doVibrato(channel_t *ch) @@ -2804,7 +2804,7 @@ bool setupReplayer(void) // unmute all channels (must be done before resetChannels() call) for (int32_t i = 0; i < MAX_CHANNELS; i++) - editor.chnMode[i] = 1; + editor.channelMuted[i] = false; resetChannels(); @@ -2814,7 +2814,7 @@ bool setupReplayer(void) editor.speed = song.initialSpeed = song.speed = 6; editor.globalVolume = song.globalVolume = 64; audio.linearPeriodsFlag = true; - note2Period = linearPeriods; + note2PeriodLUT = linearPeriodLUT; calcPanningTable(); diff --git a/src/ft2_replayer.h b/src/ft2_replayer.h @@ -295,6 +295,8 @@ double dAmigaPeriod2Hz(int32_t period); double dPeriod2Hz(int32_t period); int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote); // for piano in Instr. Ed. +void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch); +void updateVolPanAutoVib(channel_t *ch); bool allocateInstr(int16_t insNum); void freeInstr(int32_t insNum); @@ -324,6 +326,8 @@ void delta2Samp(int8_t *p, int32_t length, uint8_t smpFlags); void samp2Delta(int8_t *p, int32_t length, uint8_t smpFlags); void setPatternLen(uint16_t pattNum, int16_t numRows); void setLinearPeriods(bool linearPeriodsFlag); +void resetVolumes(channel_t *ch); +void triggerInstrument(channel_t *ch); void tickReplayer(void); // periodically called from audio callback void resetChannels(void); bool patternEmpty(uint16_t pattNum); @@ -347,7 +351,7 @@ void pbRecPtn(void); extern int8_t playMode; extern bool songPlaying, audioPaused, musicPaused; extern volatile bool replayerBusy; -extern const uint16_t *note2Period; +extern const uint16_t *note2PeriodLUT; extern int16_t patternNumRows[MAX_PATTERNS]; extern channel_t channel[MAX_CHANNELS]; extern song_t song; diff --git a/src/ft2_sample_ed.c b/src/ft2_sample_ed.c @@ -27,7 +27,9 @@ #include "ft2_diskop.h" #include "ft2_keyboard.h" #include "ft2_structs.h" +#include "ft2_random.h" #include "ft2_replayer.h" +#include "ft2_smpfx.h" #include "mixer/ft2_windowed_sinc.h" // SINC_TAPS, SINC_NEGATIVE_TAPS static const char sharpNote1Char[12] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' }; @@ -1436,8 +1438,7 @@ static void setSampleRange(int32_t start, int32_t end) void updateSampleEditorSample(void) { - smpEd_Rx1 = 0; - smpEd_Rx2 = 0; + smpEd_Rx1 = smpEd_Rx2 = 0; smpEd_ScrPos = 0; updateScrPos(); @@ -1493,27 +1494,30 @@ void updateSampleEditor(void) showRadioButtonGroup(RB_GROUP_SAMPLE_LOOP); - // draw sample play note + if (!ui.sampleEditorEffectsShown) + { + // draw sample play note - const uint32_t noteNr = editor.smpEd_NoteNr - 1; + const uint32_t noteNr = editor.smpEd_NoteNr - 1; - const uint32_t note = noteNr % 12; - const uint32_t octave = noteNr / 12; + const uint32_t note = noteNr % 12; + const uint32_t octave = noteNr / 12; - if (config.ptnAcc == 0) - { - noteChar1 = sharpNote1Char[note]; - noteChar2 = sharpNote2Char[note]; - } - else - { - noteChar1 = flatNote1Char[note]; - noteChar2 = flatNote2Char[note]; - } + if (config.ptnAcc == 0) + { + noteChar1 = sharpNote1Char[note]; + noteChar2 = sharpNote2Char[note]; + } + else + { + noteChar1 = flatNote1Char[note]; + noteChar2 = flatNote2Char[note]; + } - charOutBg(7, 369, PAL_FORGRND, PAL_BCKGRND, noteChar1); - charOutBg(15, 369, PAL_FORGRND, PAL_BCKGRND, noteChar2); - charOutBg(23, 369, PAL_FORGRND, PAL_BCKGRND, (char)('0' + octave)); + charOutBg(7, 369, PAL_FORGRND, PAL_BCKGRND, noteChar1); + charOutBg(15, 369, PAL_FORGRND, PAL_BCKGRND, noteChar2); + charOutBg(23, 369, PAL_FORGRND, PAL_BCKGRND, (char)('0' + octave)); + } // draw sample display/length @@ -2898,6 +2902,8 @@ void sampReplenDown(void) void hideSampleEditor(void) { + hideSampleEffectsScreen(); + hidePushButton(PB_SAMP_SCROLL_LEFT); hidePushButton(PB_SAMP_SCROLL_RIGHT); hidePushButton(PB_SAMP_PNOTE_UP); @@ -2917,7 +2923,7 @@ void hideSampleEditor(void) hidePushButton(PB_SAMP_PASTE); hidePushButton(PB_SAMP_CROP); hidePushButton(PB_SAMP_VOLUME); - hidePushButton(PB_SAMP_XFADE); + hidePushButton(PB_SAMP_EFFECTS); hidePushButton(PB_SAMP_EXIT); hidePushButton(PB_SAMP_CLEAR); hidePushButton(PB_SAMP_MIN); @@ -2995,7 +3001,7 @@ void showSampleEditor(void) showPushButton(PB_SAMP_PASTE); showPushButton(PB_SAMP_CROP); showPushButton(PB_SAMP_VOLUME); - showPushButton(PB_SAMP_XFADE); + showPushButton(PB_SAMP_EFFECTS); showPushButton(PB_SAMP_EXIT); showPushButton(PB_SAMP_CLEAR); showPushButton(PB_SAMP_MIN); @@ -3015,6 +3021,9 @@ void showSampleEditor(void) updateSampleEditor(); writeSample(true); + + if (ui.sampleEditorEffectsShown) + pbEffects(); } void toggleSampleEditor(void) diff --git a/src/ft2_sample_ed.h b/src/ft2_sample_ed.h @@ -18,7 +18,6 @@ bool reallocateSmpDataPtr(smpPtr_t *sp, int32_t length, bool sample16Bit); void setSmpDataPtr(sample_t *s, smpPtr_t *sp); void freeSmpDataPtr(smpPtr_t *sp); void freeSmpData(sample_t *s); - bool cloneSample(sample_t *src, sample_t *dst); sample_t *getCurSample(void); void sanitizeSample(sample_t *s); diff --git a/src/ft2_smpfx.c b/src/ft2_smpfx.c @@ -0,0 +1,1389 @@ +// for finding memory leaks in debug mode with Visual Studio +#if defined _DEBUG && defined _MSC_VER +#include <crtdbg.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <math.h> +#include "ft2_header.h" +#include "ft2_audio.h" +#include "ft2_pattern_ed.h" +#include "ft2_gui.h" +#include "ft2_sample_ed.h" +#include "ft2_structs.h" +#include "ft2_replayer.h" + +#define RESONANCE_RANGE 99 +#define RESONANCE_MIN 0.01 /* prevent massive blow-up */ + +enum +{ + REMOVE_SAMPLE_MARK = 0, + KEEP_SAMPLE_MARK = 1 +}; + +static struct +{ + bool filled, keepSampleMark; + uint8_t flags, undoInstr, undoSmp; + uint32_t length, loopStart, loopLength; + int8_t *smpData8; + int16_t *smpData16; +} sampleUndo; + +typedef struct +{ + double a1, a2, a3, b1, b2; + double inTmp[2], outTmp[2]; +} resoFilter_t; + +enum +{ + FILTER_LOWPASS = 0, + FILTER_HIGHPASS = 1 +}; + +static bool normalization; +static uint8_t lastFilterType; +static int32_t lastLpCutoff = 2000, lastHpCutoff = 200, filterResonance, smpCycles = 1, lastWaveLength = 64, lastAmp = 75; + +void clearSampleUndo(void) +{ + if (sampleUndo.smpData8 != NULL) + { + free(sampleUndo.smpData8); + sampleUndo.smpData8 = NULL; + } + + if (sampleUndo.smpData16 != NULL) + { + free(sampleUndo.smpData16); + sampleUndo.smpData16 = NULL; + } + + sampleUndo.filled = false; + sampleUndo.keepSampleMark = false; +} + +static void fillSampleUndo(bool keepSampleMark) +{ + sampleUndo.filled = false; + + sample_t *s = getCurSample(); + if (s != NULL && s->length > 0) + { + pauseAudio(); + unfixSample(s); + + clearSampleUndo(); + + sampleUndo.undoInstr = editor.curInstr; + sampleUndo.undoSmp = editor.curSmp; + sampleUndo.flags = s->flags; + sampleUndo.length = s->length; + sampleUndo.loopStart = s->loopStart; + sampleUndo.loopLength = s->loopLength; + sampleUndo.keepSampleMark = keepSampleMark; + + if (s->flags & SAMPLE_16BIT) + { + sampleUndo.smpData16 = (int16_t *)malloc(s->length * sizeof (int16_t)); + if (sampleUndo.smpData16 != NULL) + { + memcpy(sampleUndo.smpData16, s->dataPtr, s->length * sizeof (int16_t)); + sampleUndo.filled = true; + } + } + else + { + sampleUndo.smpData8 = (int8_t *)malloc(s->length * sizeof (int8_t)); + if (sampleUndo.smpData8 != NULL) + { + memcpy(sampleUndo.smpData8, s->dataPtr, s->length * sizeof (int8_t)); + sampleUndo.filled = true; + } + } + + fixSample(s); + resumeAudio(); + } +} + +static sample_t *setupNewSample(uint32_t length) +{ + pauseAudio(); + + if (instr[editor.curInstr] == NULL) + allocateInstr(editor.curInstr); + + if (instr[editor.curInstr] == NULL) + goto Error; + + sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp]; + + if (!reallocateSmpData(s, length, true)) + goto Error; + + s->isFixed = false; + s->length = length; + s->loopLength = s->loopStart = 0; + s->flags = SAMPLE_16BIT; + + resumeAudio(); + return s; + +Error: + resumeAudio(); + return NULL; +} + +void cbSfxNormalization(void) +{ + normalization ^= 1; +} + +static void drawSampleCycles(void) +{ + const int16_t x = 54; + + fillRect(x, 352, 7*3, 8, PAL_DESKTOP); + + char str[16]; + sprintf(str, "%03d", smpCycles); + textOut(x, 352, PAL_FORGRND, str); +} + +void pbSfxCyclesUp(void) +{ + if (smpCycles < 256) + { + smpCycles++; + drawSampleCycles(); + } +} + +void pbSfxCyclesDown(void) +{ + if (smpCycles > 1) + { + smpCycles--; + drawSampleCycles(); + } +} + +void pbSfxTriangle(void) +{ + char lengthStr[5+1]; + memset(lengthStr, '\0', sizeof (lengthStr)); + snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength); + + if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1) + return; + + if (lengthStr[0] == '\0') + return; + + lastWaveLength = (int32_t)atoi(lengthStr); + if (lastWaveLength <= 1 || lastWaveLength > 65536) + { + okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL); + return; + } + + fillSampleUndo(REMOVE_SAMPLE_MARK); + + int32_t newLength = lastWaveLength * smpCycles; + + pauseAudio(); + + sample_t *s = setupNewSample(newLength); + if (s == NULL) + { + resumeAudio(); + okBox(0, "System message", "Not enough memory!", NULL); + return; + } + + const double delta = 4.0 / lastWaveLength; + double phase = 0.0; + + int16_t *ptr16 = (int16_t *)s->dataPtr; + for (int32_t i = 0; i < newLength; i++) + { + double t = phase; + if (t > 3.0) + t -= 4.0; + else if (t >= 1.0) + t = 2.0 - t; + + *ptr16++ = (int16_t)(t * INT16_MAX); + phase = fmod(phase + delta, 4.0); + } + + s->loopLength = newLength; + s->flags |= LOOP_FORWARD; + fixSample(s); + resumeAudio(); + + updateSampleEditorSample(); +} + +void pbSfxSaw(void) +{ + char lengthStr[5+1]; + memset(lengthStr, '\0', sizeof (lengthStr)); + snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength); + + if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1) + return; + + if (lengthStr[0] == '\0') + return; + + lastWaveLength = (int32_t)atoi(lengthStr); + if (lastWaveLength <= 1 || lastWaveLength > 65536) + { + okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL); + return; + } + + fillSampleUndo(REMOVE_SAMPLE_MARK); + + int32_t newLength = lastWaveLength * smpCycles; + + pauseAudio(); + + sample_t *s = setupNewSample(newLength); + if (s == NULL) + { + resumeAudio(); + okBox(0, "System message", "Not enough memory!", NULL); + return; + } + + uint64_t point64 = 0; + uint64_t delta64 = ((uint64_t)(INT16_MAX*2) << 32ULL) / lastWaveLength; + + int16_t *ptr16 = (int16_t *)s->dataPtr; + for (int32_t i = 0; i < newLength; i++) + { + *ptr16++ = (int16_t)(point64 >> 32); + point64 += delta64; + } + + s->loopLength = newLength; + s->flags |= LOOP_FORWARD; + fixSample(s); + resumeAudio(); + + updateSampleEditorSample(); +} + +void pbSfxSine(void) +{ + char lengthStr[5+1]; + memset(lengthStr, '\0', sizeof (lengthStr)); + snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength); + + if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1) + return; + + if (lengthStr[0] == '\0') + return; + + lastWaveLength = (int32_t)atoi(lengthStr); + if (lastWaveLength <= 1 || lastWaveLength > 65536) + { + okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL); + return; + } + + fillSampleUndo(REMOVE_SAMPLE_MARK); + + int32_t newLength = lastWaveLength * smpCycles; + + pauseAudio(); + + sample_t *s = setupNewSample(newLength); + if (s == NULL) + { + resumeAudio(); + okBox(0, "System message", "Not enough memory!", NULL); + return; + } + + const double delta = 2.0 * M_PI / lastWaveLength; + double phase = 0.0; + + int16_t *ptr16 = (int16_t *)s->dataPtr; + for (int32_t i = 0; i < newLength; i++) + { + *ptr16++ = (int16_t)(INT16_MAX * sin(phase)); + phase += delta; + } + + s->loopLength = newLength; + s->flags |= LOOP_FORWARD; + fixSample(s); + resumeAudio(); + + updateSampleEditorSample(); +} + +void pbSfxSquare(void) +{ + char lengthStr[5+1]; + memset(lengthStr, '\0', sizeof (lengthStr)); + snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength); + + if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1) + return; + + if (lengthStr[0] == '\0') + return; + + lastWaveLength = (int32_t)atoi(lengthStr); + if (lastWaveLength <= 1 || lastWaveLength > 65536) + { + okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL); + return; + } + + fillSampleUndo(REMOVE_SAMPLE_MARK); + + uint32_t newLength = lastWaveLength * smpCycles; + + pauseAudio(); + + sample_t *s = setupNewSample(newLength); + if (s == NULL) + { + resumeAudio(); + okBox(0, "System message", "Not enough memory!", NULL); + return; + } + + const uint32_t halfWaveLength = lastWaveLength / 2; + + int16_t currValue = INT16_MAX; + uint32_t counter = 0; + + int16_t *ptr16 = (int16_t *)s->dataPtr; + for (uint32_t i = 0; i < newLength; i++) + { + *ptr16++ = currValue; + if (++counter >= halfWaveLength) + { + counter = 0; + currValue = -currValue; + } + } + + s->loopLength = newLength; + s->flags |= LOOP_FORWARD; + fixSample(s); + resumeAudio(); + + updateSampleEditorSample(); +} + +void drawFilterResonance(void) +{ + const int16_t x = 172; + + fillRect(x, 352, 18, 12, PAL_DESKTOP); + + if (filterResonance <= 0) + { + textOut(x, 352, PAL_FORGRND, "off"); + } + else + { + char str[16]; + sprintf(str, "%02d", filterResonance); + textOut(x+3, 352, PAL_FORGRND, str); + } +} + +void pbSfxResoUp(void) +{ + if (filterResonance < RESONANCE_RANGE) + { + filterResonance++; + drawFilterResonance(); + } +} + +void pbSfxResoDown(void) +{ + if (filterResonance > 0) + { + filterResonance--; + drawFilterResonance(); + } +} + +#define CUTOFF_EPSILON (1E-4) + +static void setupResoLpFilter(sample_t *s, resoFilter_t *f, double cutoff, uint32_t resonance, bool absoluteCutoff) +{ + // 12dB/oct resonant low-pass filter + + if (!absoluteCutoff) + { + const double sampleFreq = getSampleC4Rate(s); + if (cutoff >= sampleFreq/2.0) + cutoff = (sampleFreq/2.0) - CUTOFF_EPSILON; + + cutoff /= sampleFreq; + } + + double r = sqrt(2.0); + if (resonance > 0) + { + r = pow(10.0, (resonance * -24.0) / (RESONANCE_RANGE * 20.0)); + if (r < RESONANCE_MIN) + r = RESONANCE_MIN; + } + + const double c = 1.0 / tan(PI * cutoff); + + f->a1 = 1.0 / (1.0 + r * c + c * c); + f->a2 = 2.0 * f->a1; + f->a3 = f->a1; + f->b1 = 2.0 * (1.0 - c*c) * f->a1; + f->b2 = (1.0 - r * c + c * c) * f->a1; + + f->inTmp[0] = f->inTmp[1] = f->outTmp[0] = f->outTmp[1] = 0.0; // clear filter history +} + +static void setupResoHpFilter(sample_t *s, resoFilter_t *f, double cutoff, uint32_t resonance, bool absoluteCutoff) +{ + // 12dB/oct resonant high-pass filter + + if (!absoluteCutoff) + { + const double sampleFreq = getSampleC4Rate(s); + if (cutoff >= sampleFreq/2.0) + cutoff = (sampleFreq/2.0) - CUTOFF_EPSILON; + + cutoff /= sampleFreq; + } + + double r = sqrt(2.0); + if (resonance > 0) + { + r = pow(10.0, (resonance * -24.0) / (RESONANCE_RANGE * 20.0)); + if (r < RESONANCE_MIN) + r = RESONANCE_MIN; + } + + const double c = tan(PI * cutoff); + + f->a1 = 1.0 / (1.0 + r * c + c * c); + f->a2 = -2.0 * f->a1; + f->a3 = f->a1; + f->b1 = 2.0 * (c*c - 1.0) * f->a1; + f->b2 = (1.0 - r * c + c * c) * f->a1; + + f->inTmp[0] = f->inTmp[1] = f->outTmp[0] = f->outTmp[1] = 0.0; // clear filter history +} + +static bool applyResoFilter(sample_t *s, resoFilter_t *f) +{ + int32_t x1, x2; + if (smpEd_Rx1 < smpEd_Rx2) + { + x1 = smpEd_Rx1; + x2 = smpEd_Rx2; + + if (x2 > s->length) + x2 = s->length; + + if (x1 < 0) + x1 = 0; + + if (x2 <= x1) + return true; + } + else + { + // no mark, operate on whole sample + x1 = 0; + x2 = s->length; + } + + const int32_t len = x2 - x1; + + if (!normalization) + { + pauseAudio(); + unfixSample(s); + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + double out = (f->a1*ptr16[i]) + (f->a2*f->inTmp[0]) + (f->a3*f->inTmp[1]) - (f->b1*f->outTmp[0]) - (f->b2*f->outTmp[1]); + + f->inTmp[1] = f->inTmp[0]; + f->inTmp[0] = ptr16[i]; + + f->outTmp[1] = f->outTmp[0]; + f->outTmp[0] = out; + + ptr16[i] = (int16_t)CLAMP(out, INT16_MIN, INT16_MAX); + } + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + double out = (f->a1*ptr8[i]) + (f->a2*f->inTmp[0]) + (f->a3*f->inTmp[1]) - (f->b1*f->outTmp[0]) - (f->b2*f->outTmp[1]); + + f->inTmp[1] = f->inTmp[0]; + f->inTmp[0] = ptr8[i]; + + f->outTmp[1] = f->outTmp[0]; + f->outTmp[0] = out; + + ptr8[i] = (int8_t)CLAMP(out, INT8_MIN, INT8_MAX); + } + } + + fixSample(s); + resumeAudio(); + } + else // normalize peak, no clipping + { + double *dSmp = (double *)malloc(len * sizeof (double)); + if (dSmp == NULL) + { + okBox(0, "System message", "Not enough memory!", NULL); + return false; + } + + pauseAudio(); + unfixSample(s); + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + dSmp[i] = (double)ptr16[i]; + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + dSmp[i] = (double)ptr8[i]; + } + + double peak = 0.0; + for (int32_t i = 0; i < len; i++) + { + const double out = (f->a1*dSmp[i]) + (f->a2*f->inTmp[0]) + (f->a3*f->inTmp[1]) - (f->b1*f->outTmp[0]) - (f->b2*f->outTmp[1]); + + f->inTmp[1] = f->inTmp[0]; + f->inTmp[0] = dSmp[i]; + + f->outTmp[1] = f->outTmp[0]; + f->outTmp[0] = out; + + dSmp[i] = out; + + const double outAbs = fabs(out); + if (outAbs > peak) + peak = outAbs; + } + + if (s->flags & SAMPLE_16BIT) + { + const double scale = INT16_MAX / peak; + + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + ptr16[i] = (int16_t)(dSmp[i] * scale); + } + else + { + const double scale = INT8_MAX / peak; + + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + ptr8[i] = (int8_t)(dSmp[i] * scale); + } + + free(dSmp); + + fixSample(s); + resumeAudio(); + } + + return true; +} + +void pbSfxLowPass(void) +{ + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + char lengthStr[5+1]; + memset(lengthStr, '\0', sizeof (lengthStr)); + snprintf(lengthStr, sizeof (lengthStr), "%d", lastLpCutoff); + + lastFilterType = FILTER_LOWPASS; + if (inputBox(6, "Enter low-pass filter cutoff (in Hz):", lengthStr, sizeof (lengthStr)-1) != 1) + return; + + if (lengthStr[0] == '\0') + return; + + lastLpCutoff = (int32_t)atoi(lengthStr); + if (lastLpCutoff < 1 || lastLpCutoff > 99999) + { + okBox(0, "System message", "Illegal range! Allowed range is 1..99999", NULL); + return; + } + + setupResoLpFilter(s, &f, lastLpCutoff, filterResonance, false); + fillSampleUndo(KEEP_SAMPLE_MARK); + applyResoFilter(s, &f); + writeSample(true); +} + +void pbSfxHighPass(void) +{ + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + char lengthStr[5+1]; + memset(lengthStr, '\0', sizeof (lengthStr)); + snprintf(lengthStr, sizeof (lengthStr), "%d", lastHpCutoff); + + lastFilterType = FILTER_HIGHPASS; + if (inputBox(6, "Enter high-pass filter cutoff (in Hz):", lengthStr, sizeof (lengthStr)-1) != 1) + return; + + if (lengthStr[0] == '\0') + return; + + lastHpCutoff = (int32_t)atoi(lengthStr); + if (lastHpCutoff < 1 || lastHpCutoff > 99999) + { + okBox(0, "System message", "Illegal range! Allowed range is 1..99999", NULL); + return; + } + + setupResoHpFilter(s, &f, lastHpCutoff, filterResonance, false); + fillSampleUndo(KEEP_SAMPLE_MARK); + applyResoFilter(s, &f); + writeSample(true); +} + +void sfxPreviewFilter(uint32_t cutoff) +{ + sample_t oldSample; + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL || s->length == 0 || cutoff < 1 || cutoff > 99999) + return; + + int32_t x1, x2; + if (smpEd_Rx1 < smpEd_Rx2) + { + x1 = smpEd_Rx1; + x2 = smpEd_Rx2; + + if (x2 > s->length) + x2 = s->length; + + if (x1 < 0) + x1 = 0; + + if (x2 <= x1) + return; + } + else + { + // no mark, operate on whole sample + x1 = 0; + x2 = s->length; + } + + const int32_t len = x2 - x1; + + pauseAudio(); + unfixSample(s); + memcpy(&oldSample, s, sizeof (sample_t)); + + if (lastFilterType == FILTER_LOWPASS) + setupResoLpFilter(s, &f, cutoff, filterResonance, false); + else + setupResoHpFilter(s, &f, cutoff, filterResonance, false); + + // prepare new sample + int8_t *sampleData; + if (s->flags & SAMPLE_16BIT) + { + sampleData = (int8_t *)malloc((len * sizeof (int16_t)) + SAMPLE_PAD_LENGTH); + if (sampleData == NULL) + goto Error; + + memcpy(sampleData + SMP_DAT_OFFSET, (int16_t *)s->dataPtr + x1, len * sizeof (int16_t)); + } + else + { + sampleData = (int8_t *)malloc((len * sizeof (int8_t)) + SAMPLE_PAD_LENGTH); + if (sampleData == NULL) + goto Error; + + memcpy(sampleData + SMP_DAT_OFFSET, (int8_t *)s->dataPtr + x1, len * sizeof (int8_t)); + } + + s->origDataPtr = sampleData; + s->length = len; + s->dataPtr = s->origDataPtr + SMP_DAT_OFFSET; + s->loopStart = s->loopLength = 0; + fixSample(s); + + const int32_t oldX1 = smpEd_Rx1; + const int32_t oldX2 = smpEd_Rx2; + smpEd_Rx1 = smpEd_Rx2 = 0; + applyResoFilter(s, &f); + smpEd_Rx1 = oldX1; + smpEd_Rx2 = oldX2; + + // set up preview sample on channel 0 + channel_t *ch = &channel[0]; + uint8_t note = editor.smpEd_NoteNr; + ch->smpNum = editor.curSmp; + ch->instrNum = editor.curInstr; + ch->copyOfInstrAndNote = (ch->instrNum << 8) | note; + ch->efx = 0; + ch->smpStartPos = 0; + resumeAudio(); + triggerNote(note, 0, 0, ch); + resetVolumes(ch); + triggerInstrument(ch); + ch->realVol = ch->outVol = ch->oldVol = 64; + updateVolPanAutoVib(ch); + + while (ch->status & IS_Trigger); // wait for sample to latch in mixer + SDL_Delay(1500); // wait 1.5 seconds + + // we're done, stop voice and free temporary data + pauseAudio(); + free(sampleData); + +Error: + // set back old sample + memcpy(s, &oldSample, sizeof (sample_t)); + fixSample(s); + resumeAudio(); +} + +void pbSfxSubBass(void) +{ + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + setupResoHpFilter(s, &f, 0.001, 0, true); + fillSampleUndo(KEEP_SAMPLE_MARK); + applyResoFilter(s, &f); + writeSample(true); +} + +void pbSfxAddBass(void) +{ + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + int32_t x1, x2; + if (smpEd_Rx1 < smpEd_Rx2) + { + x1 = smpEd_Rx1; + x2 = smpEd_Rx2; + + if (x2 > s->length) + x2 = s->length; + + if (x1 < 0) + x1 = 0; + + if (x2 <= x1) + return; + } + else + { + // no mark, operate on whole sample + x1 = 0; + x2 = s->length; + } + + const int32_t len = x2 - x1; + + setupResoLpFilter(s, &f, 0.015, 0, true); + + double *dSmp = (double *)malloc(len * sizeof (double)); + if (dSmp == NULL) + { + okBox(0, "System message", "Not enough memory!", NULL); + return; + } + + fillSampleUndo(KEEP_SAMPLE_MARK); + + pauseAudio(); + unfixSample(s); + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + dSmp[i] = (double)ptr16[i]; + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + dSmp[i] = (double)ptr8[i]; + } + + if (!normalization) + { + for (int32_t i = 0; i < len; i++) + { + double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]); + + f.inTmp[1] = f.inTmp[0]; + f.inTmp[0] = dSmp[i]; + + f.outTmp[1] = f.outTmp[0]; + f.outTmp[0] = out; + + dSmp[i] = out; + } + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + double out = ptr16[i] + (dSmp[i] * 0.25); + out = CLAMP(out, INT16_MIN, INT16_MAX); + + ptr16[i] = (int16_t)out; + } + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + double out = ptr8[i] + (dSmp[i] * 0.25); + out = CLAMP(out, INT8_MIN, INT8_MAX); + + ptr8[i] = (int8_t)out; + } + } + } + else + { + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + + double peak = 0.0; + for (int32_t i = 0; i < len; i++) + { + double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]); + + f.inTmp[1] = f.inTmp[0]; + f.inTmp[0] = dSmp[i]; + + f.outTmp[1] = f.outTmp[0]; + f.outTmp[0] = out; + + dSmp[i] = out; + double bass = ptr16[i] + (out * 0.25); + + const double outAbs = fabs(bass); + if (outAbs > peak) + peak = outAbs; + } + + double scale = INT16_MAX / peak; + for (int32_t i = 0; i < len; i++) + ptr16[i] = (int16_t)((ptr16[i] + (dSmp[i] * 0.25)) * scale); + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + + double peak = 0.0; + for (int32_t i = 0; i < len; i++) + { + double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]); + + f.inTmp[1] = f.inTmp[0]; + f.inTmp[0] = dSmp[i]; + + f.outTmp[1] = f.outTmp[0]; + f.outTmp[0] = out; + + dSmp[i] = out; + double bass = ptr8[i] + (out * 0.25); + + const double outAbs = fabs(bass); + if (outAbs > peak) + peak = outAbs; + } + + double scale = INT8_MAX / peak; + for (int32_t i = 0; i < len; i++) + ptr8[i] = (int8_t)((ptr8[i] + (dSmp[i] * 0.25)) * scale); + } + } + + free(dSmp); + + fixSample(s); + resumeAudio(); + + writeSample(true); +} + +void pbSfxSubTreble(void) +{ + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + setupResoLpFilter(s, &f, 0.33, 0, true); + fillSampleUndo(KEEP_SAMPLE_MARK); + applyResoFilter(s, &f); + writeSample(true); +} + +void pbSfxAddTreble(void) +{ + resoFilter_t f; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + int32_t x1, x2; + if (smpEd_Rx1 < smpEd_Rx2) + { + x1 = smpEd_Rx1; + x2 = smpEd_Rx2; + + if (x2 > s->length) + x2 = s->length; + + if (x1 < 0) + x1 = 0; + + if (x2 <= x1) + return; + } + else + { + // no mark, operate on whole sample + x1 = 0; + x2 = s->length; + } + + const int32_t len = x2 - x1; + + setupResoHpFilter(s, &f, 0.27, 0, true); + + double *dSmp = (double *)malloc(len * sizeof (double)); + if (dSmp == NULL) + { + okBox(0, "System message", "Not enough memory!", NULL); + return; + } + + fillSampleUndo(KEEP_SAMPLE_MARK); + + pauseAudio(); + unfixSample(s); + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + dSmp[i] = (double)ptr16[i]; + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + dSmp[i] = (double)ptr8[i]; + } + + if (!normalization) + { + for (int32_t i = 0; i < len; i++) + { + double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]); + + f.inTmp[1] = f.inTmp[0]; + f.inTmp[0] = dSmp[i]; + + f.outTmp[1] = f.outTmp[0]; + f.outTmp[0] = out; + + dSmp[i] = out; + } + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + double out = ptr16[i] - (dSmp[i] * 0.25); + out = CLAMP(out, INT16_MIN, INT16_MAX); + + ptr16[i] = (int16_t)out; + } + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + double out = ptr8[i] - (dSmp[i] * 0.25); + out = CLAMP(out, INT8_MIN, INT8_MAX); + + ptr8[i] = (int8_t)out; + } + } + } + else + { + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + + double peak = 0.0; + for (int32_t i = 0; i < len; i++) + { + double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]); + + f.inTmp[1] = f.inTmp[0]; + f.inTmp[0] = dSmp[i]; + + f.outTmp[1] = f.outTmp[0]; + f.outTmp[0] = out; + + dSmp[i] = out; + double treble = ptr16[i] - (out * 0.25); + + const double outAbs = fabs(treble); + if (outAbs > peak) + peak = outAbs; + } + + double scale = INT16_MAX / peak; + for (int32_t i = 0; i < len; i++) + ptr16[i] = (int16_t)((ptr16[i] - (dSmp[i] * 0.25)) * scale); + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + + double peak = 0.0; + for (int32_t i = 0; i < len; i++) + { + double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]); + + f.inTmp[1] = f.inTmp[0]; + f.inTmp[0] = dSmp[i]; + + f.outTmp[1] = f.outTmp[0]; + f.outTmp[0] = out; + + dSmp[i] = out; + double treble = ptr8[i] - (out * 0.25); + + const double outAbs = fabs(treble); + if (outAbs > peak) + peak = outAbs; + } + + double scale = INT8_MAX / peak; + for (int32_t i = 0; i < len; i++) + ptr8[i] = (int8_t)((ptr8[i] - (dSmp[i] * 0.25)) * scale); + } + } + + free(dSmp); + + fixSample(s); + resumeAudio(); + + writeSample(true); +} + +void pbSfxSetAmp(void) +{ + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + int32_t x1, x2; + if (smpEd_Rx1 < smpEd_Rx2) + { + x1 = smpEd_Rx1; + x2 = smpEd_Rx2; + + if (x2 > s->length) + x2 = s->length; + + if (x1 < 0) + x1 = 0; + + if (x2 <= x1) + return; + } + else + { + // no mark, operate on whole sample + x1 = 0; + x2 = s->length; + } + + const int32_t len = x2 - x1; + + char ampStr[3+1]; + memset(ampStr, '\0', sizeof (ampStr)); + snprintf(ampStr, sizeof (ampStr), "%d", lastAmp); + + if (inputBox(1, "Change sample amplitude (in percentage, 0..999):", ampStr, sizeof (ampStr)-1) != 1) + return; + + if (ampStr[0] == '\0') + return; + + lastAmp = (int32_t)atoi(ampStr); + + fillSampleUndo(KEEP_SAMPLE_MARK); + + pauseAudio(); + unfixSample(s); + + const int32_t mul = (int32_t)round((1 << 22UL) * (lastAmp / 100.0)); + + if (s->flags & SAMPLE_16BIT) + { + int16_t *ptr16 = (int16_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + int32_t sample = ((int64_t)ptr16[i] * (int32_t)mul) >> 22; + sample = CLAMP(sample, INT16_MIN, INT16_MAX); + ptr16[i] = (int16_t)sample; + } + } + else + { + int8_t *ptr8 = (int8_t *)s->dataPtr + x1; + for (int32_t i = 0; i < len; i++) + { + int32_t sample = ((int64_t)ptr8[i] * (int32_t)mul) >> 22; + sample = CLAMP(sample, INT8_MIN, INT8_MAX); + ptr8[i] = (int8_t)sample; + } + } + + fixSample(s); + resumeAudio(); + + writeSample(true); +} + +void pbSfxUndo(void) +{ + if (!sampleUndo.filled || sampleUndo.undoInstr != editor.curInstr || sampleUndo.undoSmp != editor.curSmp) + return; + + sample_t *s = getCurSample(); + if (s == NULL || s->dataPtr == NULL) + return; + + pauseAudio(); + + freeSmpData(s); + s->flags = sampleUndo.flags; + s->length = sampleUndo.length; + s->loopStart = sampleUndo.loopStart; + s->loopLength = sampleUndo.loopLength; + + if (allocateSmpData(s, s->length, !!(s->flags & SAMPLE_16BIT))) + { + if (s->flags & SAMPLE_16BIT) + memcpy(s->dataPtr, sampleUndo.smpData16, s->length * sizeof (int16_t)); + else + memcpy(s->dataPtr, sampleUndo.smpData8, s->length * sizeof (int8_t)); + + fixSample(s); + resumeAudio(); + } + else + { + resumeAudio(); + okBox(0, "System message", "Not enough memory!", NULL); + } + + int32_t oldRx1 = smpEd_Rx1; + int32_t oldRx2 = smpEd_Rx2; + + updateSampleEditorSample(); + + if (sampleUndo.keepSampleMark && oldRx1 < oldRx2) + { + smpEd_Rx1 = oldRx1; + smpEd_Rx2 = oldRx2; + writeSample(false); // redraw sample mark only + } + + sampleUndo.keepSampleMark = false; + sampleUndo.filled = false; +} + +void hideSampleEffectsScreen(void) +{ + ui.sampleEditorEffectsShown = false; + + hideCheckBox(CB_SAMPFX_NORMALIZATION); + hidePushButton(PB_SAMPFX_CYCLES_UP); + hidePushButton(PB_SAMPFX_CYCLES_DOWN); + hidePushButton(PB_SAMPFX_TRIANGLE); + hidePushButton(PB_SAMPFX_SAW); + hidePushButton(PB_SAMPFX_SINE); + hidePushButton(PB_SAMPFX_SQUARE); + hidePushButton(PB_SAMPFX_RESO_UP); + hidePushButton(PB_SAMPFX_RESO_DOWN); + hidePushButton(PB_SAMPFX_LOWPASS); + hidePushButton(PB_SAMPFX_HIGHPASS); + hidePushButton(PB_SAMPFX_SUB_BASS); + hidePushButton(PB_SAMPFX_SUB_TREBLE); + hidePushButton(PB_SAMPFX_ADD_BASS); + hidePushButton(PB_SAMPFX_ADD_TREBLE); + hidePushButton(PB_SAMPFX_SET_AMP); + hidePushButton(PB_SAMPFX_UNDO); + hidePushButton(PB_SAMPFX_XFADE); + hidePushButton(PB_SAMPFX_BACK); + + drawFramework(0, 346, 115, 54, FRAMEWORK_TYPE1); + drawFramework(115, 346, 133, 54, FRAMEWORK_TYPE1); + drawFramework(248, 346, 49, 54, FRAMEWORK_TYPE1); + drawFramework(297, 346, 56, 54, FRAMEWORK_TYPE1); + + showPushButton(PB_SAMP_PNOTE_UP); + showPushButton(PB_SAMP_PNOTE_DOWN); + showPushButton(PB_SAMP_STOP); + showPushButton(PB_SAMP_PWAVE); + showPushButton(PB_SAMP_PRANGE); + showPushButton(PB_SAMP_PDISPLAY); + showPushButton(PB_SAMP_SHOW_RANGE); + showPushButton(PB_SAMP_RANGE_ALL); + showPushButton(PB_SAMP_CLR_RANGE); + showPushButton(PB_SAMP_ZOOM_OUT); + showPushButton(PB_SAMP_SHOW_ALL); + showPushButton(PB_SAMP_SAVE_RNG); + showPushButton(PB_SAMP_CUT); + showPushButton(PB_SAMP_COPY); + showPushButton(PB_SAMP_PASTE); + showPushButton(PB_SAMP_CROP); + showPushButton(PB_SAMP_VOLUME); + showPushButton(PB_SAMP_EFFECTS); + + drawFramework(2, 366, 34, 15, FRAMEWORK_TYPE2); + textOutShadow(5, 352, PAL_FORGRND, PAL_DSKTOP2, "Play:"); + updateSampleEditor(); +} + +void pbEffects(void) +{ + hidePushButton(PB_SAMP_PNOTE_UP); + hidePushButton(PB_SAMP_PNOTE_DOWN); + hidePushButton(PB_SAMP_STOP); + hidePushButton(PB_SAMP_PWAVE); + hidePushButton(PB_SAMP_PRANGE); + hidePushButton(PB_SAMP_PDISPLAY); + hidePushButton(PB_SAMP_SHOW_RANGE); + hidePushButton(PB_SAMP_RANGE_ALL); + hidePushButton(PB_SAMP_CLR_RANGE); + hidePushButton(PB_SAMP_ZOOM_OUT); + hidePushButton(PB_SAMP_SHOW_ALL); + hidePushButton(PB_SAMP_SAVE_RNG); + hidePushButton(PB_SAMP_CUT); + hidePushButton(PB_SAMP_COPY); + hidePushButton(PB_SAMP_PASTE); + hidePushButton(PB_SAMP_CROP); + hidePushButton(PB_SAMP_VOLUME); + hidePushButton(PB_SAMP_EFFECTS); + + drawFramework(0, 346, 116, 54, FRAMEWORK_TYPE1); + drawFramework(116, 346, 114, 54, FRAMEWORK_TYPE1); + drawFramework(230, 346, 67, 54, FRAMEWORK_TYPE1); + drawFramework(297, 346, 56, 54, FRAMEWORK_TYPE1); + + checkBoxes[CB_SAMPFX_NORMALIZATION].checked = normalization ? true : false; + showCheckBox(CB_SAMPFX_NORMALIZATION); + showPushButton(PB_SAMPFX_CYCLES_UP); + showPushButton(PB_SAMPFX_CYCLES_DOWN); + showPushButton(PB_SAMPFX_TRIANGLE); + showPushButton(PB_SAMPFX_SAW); + showPushButton(PB_SAMPFX_SINE); + showPushButton(PB_SAMPFX_SQUARE); + showPushButton(PB_SAMPFX_RESO_UP); + showPushButton(PB_SAMPFX_RESO_DOWN); + showPushButton(PB_SAMPFX_LOWPASS); + showPushButton(PB_SAMPFX_HIGHPASS); + showPushButton(PB_SAMPFX_SUB_BASS); + showPushButton(PB_SAMPFX_SUB_TREBLE); + showPushButton(PB_SAMPFX_ADD_BASS); + showPushButton(PB_SAMPFX_ADD_TREBLE); + showPushButton(PB_SAMPFX_SET_AMP); + showPushButton(PB_SAMPFX_UNDO); + showPushButton(PB_SAMPFX_XFADE); + showPushButton(PB_SAMPFX_BACK); + + textOutShadow(4, 352, PAL_FORGRND, PAL_DSKTOP2, "Cycles:"); + drawSampleCycles(); + + textOutShadow(121, 352, PAL_FORGRND, PAL_DSKTOP2, "Reson.:"); + drawFilterResonance(); + + textOutShadow(135, 386, PAL_FORGRND, PAL_DSKTOP2, "Normalization"); + + textOutShadow(235, 352, PAL_FORGRND, PAL_DSKTOP2, "Bass"); + textOutShadow(235, 369, PAL_FORGRND, PAL_DSKTOP2, "Treb."); + + ui.sampleEditorEffectsShown = true; +} diff --git a/src/ft2_smpfx.h b/src/ft2_smpfx.h @@ -0,0 +1,26 @@ +#pragma once + +#include <stdint.h> +#include "ft2_header.h" + +void clearSampleUndo(void); +void cbSfxNormalization(void); +void pbSfxCyclesUp(void); +void pbSfxCyclesDown(void); +void pbSfxTriangle(void); +void pbSfxSaw(void); +void pbSfxSine(void); +void pbSfxSquare(void); +void pbSfxResoUp(void); +void pbSfxResoDown(void); +void pbSfxLowPass(void); +void pbSfxHighPass(void); +void sfxPreviewFilter(uint32_t cutof); +void pbSfxSubBass(void); +void pbSfxSubTreble(void); +void pbSfxAddBass(void); +void pbSfxAddTreble(void); +void pbSfxSetAmp(void); +void pbSfxUndo(void); +void hideSampleEffectsScreen(void); +void pbEffects(void); diff --git a/src/ft2_structs.h b/src/ft2_structs.h @@ -25,7 +25,7 @@ typedef struct editor_t bool autoPlayOnDrop, trimThreadWasDone, throwExit, editTextFlag; bool copyMaskEnable, diskOpReadOnOpen, samplingAudioFlag, editSampleFlag; - bool instrBankSwapped, chnMode[MAX_CHANNELS], NI_Play; + bool instrBankSwapped, channelMuted[MAX_CHANNELS], NI_Play; uint8_t curPlayInstr, curPlaySmp, curSmpChannel, currPanEnvPoint, currVolEnvPoint; uint8_t copyMask[5], pasteMask[5], transpMask[5], smpEd_NoteNr, instrBankOffset, sampleBankOffset; @@ -56,7 +56,7 @@ typedef struct ui_t uint8_t oldTopLeftScreen; // bottom screens - bool patternEditorShown, instEditorShown, sampleEditorShown, pattChanScrollShown; + bool patternEditorShown, instEditorShown, sampleEditorShown, sampleEditorEffectsShown, pattChanScrollShown; bool leftLoopPinMoving, rightLoopPinMoving; bool drawReplayerPianoFlag, drawPianoFlag, updatePatternEditor; uint8_t channelOffset, numChannelsShown, maxVisibleChannels; @@ -66,7 +66,7 @@ typedef struct ui_t // backup flag for when entering/exiting extended pattern editor (TODO: this is lame and shouldn't be hardcoded) bool _aboutScreenShown, _helpScreenShown, _configScreenShown, _diskOpShown; bool _nibblesShown, _transposeShown, _instEditorShown; - bool _instEditorExtShown, _sampleEditorExtShown, _patternEditorShown; + bool _instEditorExtShown, _sampleEditorExtShown, _sampleEditorEffectsShown, _patternEditorShown; bool _sampleEditorShown, _advEditShown, _wavRendererShown, _trimScreenShown; // ------------------------------------------------------------------------- } ui_t; diff --git a/src/ft2_sysreqs.c b/src/ft2_sysreqs.c @@ -10,6 +10,7 @@ #include "ft2_sysreqs.h" #include "ft2_structs.h" #include "ft2_events.h" +#include "ft2_smpfx.h" #define SYSTEM_REQUEST_H 67 #define SYSTEM_REQUEST_Y 249 @@ -21,7 +22,7 @@ void (*loaderMsgBox)(const char *, ...); int16_t (*loaderSysReq)(int16_t, const char *, const char *, void (*)(void)); // ---------------- -#define NUM_SYSREQ_TYPES 6 +#define NUM_SYSREQ_TYPES 7 static char *buttonText[NUM_SYSREQ_TYPES][5] = { @@ -33,7 +34,8 @@ static char *buttonText[NUM_SYSREQ_TYPES][5] = // custom dialogs { "All", "Song", "Instruments", "Cancel", "" }, // "song clear" dialog { "Read left", "Read right", "Convert", "", "" }, // "stereo sample loader" dialog - { "Mono", "Stereo", "Cancel", "","" } // "audio sampling" dialog + { "Mono", "Stereo", "Cancel", "","" }, // "audio sampling" dialog + { "OK", "Preview", "Cancel", "","" } // sample editor effects filters }; static SDL_Keycode shortCut[NUM_SYSREQ_TYPES][5] = @@ -47,6 +49,7 @@ static SDL_Keycode shortCut[NUM_SYSREQ_TYPES][5] = { SDLK_a, SDLK_s, SDLK_i, SDLK_c, 0 }, // "song clear" dialog { SDLK_l, SDLK_r, SDLK_c, 0, 0 }, // "stereo sample loader" dialog { SDLK_m, SDLK_s, SDLK_c, 0, 0 }, // "audio sampling" dialog + { SDLK_o, SDLK_p, SDLK_c, 0, 0 } // sample editor effects filters }; typedef struct quitType_t @@ -191,7 +194,10 @@ int16_t okBox(int16_t type, const char *headline, const char *text, void (*check SDL_Event inputEvent; if (editor.editTextFlag) + { exitTextEditing(); + keyb.ignoreCurrKeyUp = false; // don't handle key-up kludge here + } // revert "delete/rename" mouse modes (disk op.) if (mouse.mode != MOUSE_MODE_NORMAL) @@ -396,7 +402,10 @@ int16_t inputBox(int16_t type, const char *headline, char *edText, uint16_t maxS SDL_Event inputEvent; if (editor.editTextFlag) + { exitTextEditing(); + keyb.ignoreCurrKeyUp = false; // don't handle key-up kludge here + } // revert "delete/rename" mouse modes (disk op.) if (mouse.mode != MOUSE_MODE_NORMAL) @@ -553,10 +562,19 @@ int16_t inputBox(int16_t type, const char *headline, char *edText, uint16_t maxS { if (shortCut[1][i] == inputEvent.key.keysym.sym) { - returnVal = i + 1; - ui.sysReqShown = false; - keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed - break; + if (type == 6 && returnVal == 2) + { + // special case for filters in sample editor "effects" + if (edText[0] != '\0') + sfxPreviewFilter(atoi(edText)); + } + else + { + returnVal = i + 1; + ui.sysReqShown = false; + keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed + break; + } } } } @@ -567,7 +585,17 @@ int16_t inputBox(int16_t type, const char *headline, char *edText, uint16_t maxS { returnVal = testPushButtonMouseRelease(false) + 1; if (returnVal > 0) - ui.sysReqShown = false; + { + if (type == 6 && returnVal == 2) + { + if (edText[0] != '\0') + sfxPreviewFilter(atoi(edText)); // special case for filters in sample editor "effects" + } + else + { + ui.sysReqShown = false; + } + } mouse.lastUsedObjectID = OBJECT_ID_NONE; mouse.lastUsedObjectType = OBJECT_NONE; diff --git a/src/ft2_tables.c b/src/ft2_tables.c @@ -79,7 +79,7 @@ const uint16_t modPeriods[8 * 12] = // used for .MOD loading/saving 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28 }; -const uint16_t linearPeriods[1936] = // bit-exact to FT2 table +const uint16_t linearPeriodLUT[1936] = // bit-exact to FT2 table { 7744, 7740, 7736, 7732, 7728, 7724, 7720, 7716, 7712, 7708, 7704, 7700, 7696, 7692, 7688, 7684, 7680, 7676, 7672, 7668, 7664, 7660, 7656, 7652, 7648, 7644, 7640, 7636, 7632, 7628, 7624, 7620, @@ -204,7 +204,7 @@ const uint16_t linearPeriods[1936] = // bit-exact to FT2 table 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4 }; -const uint16_t amigaPeriods[1936] = // bit-exact to FT2 table +const uint16_t amigaPeriodLUT[1936] = // bit-exact to FT2 table { 29024, 28912, 28800, 28704, 28608, 28496, 28384, 28288, 28192, 28096, 28000, 27888, 27776, 27680, 27584, 27488, 27392, 27296, 27200, 27104, 27008, 26912, 26816, 26720, 26624, 26528, 26432, 26336, 26240, 26144, 26048, 25952, @@ -712,23 +712,6 @@ const uint8_t pattCursorWTab[2 * 4 * 8] = 24, 4, 4, 4, 4, 4, 4, 4 // 12 columns visible }; -// these two are for channel numbering on pattern data/scopes -const char chDecTab1[MAX_CHANNELS+1] = -{ - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3' -}; - -const char chDecTab2[MAX_CHANNELS+1] = -{ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2' -}; - const SDL_Keycode key2VolTab[16] = { SDLK_0, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_MINUS, SDLK_PLUS, SDLK_d, diff --git a/src/ft2_tables.h b/src/ft2_tables.h @@ -16,8 +16,8 @@ extern const uint8_t arpeggioTab[256]; extern const int8_t autoVibSineTab[256]; extern const uint8_t vibratoTab[32]; extern const uint16_t modPeriods[8 * 12]; -extern const uint16_t linearPeriods[1936]; -extern const uint16_t amigaPeriods[1936]; +extern const uint16_t linearPeriodLUT[1936]; +extern const uint16_t amigaPeriodLUT[1936]; extern const char *dec2StrTab[100]; extern const char *dec3StrTab[256]; @@ -37,8 +37,6 @@ extern const pattCoord2_t pattCoord2Table[2][2][2]; extern const markCoord_t markCoordTable[2][2][2]; extern const uint8_t pattCursorXTab[2 * 4 * 8]; extern const uint8_t pattCursorWTab[2 * 4 * 8]; -extern const char chDecTab1[MAX_CHANNELS+1]; -extern const char chDecTab2[MAX_CHANNELS+1]; extern const SDL_Keycode key2VolTab[16]; extern const SDL_Keycode key2EfxTab[36]; extern const SDL_Keycode key2HexTab[16]; diff --git a/src/ft2_textboxes.c b/src/ft2_textboxes.c @@ -722,7 +722,10 @@ bool testTextBoxMouseDown(void) // if we were editing text and we clicked outside of a text box, exit text editing if (editor.editTextFlag) + { exitTextEditing(); + keyb.ignoreCurrKeyUp = false; // if we exited with mouse, don't handle key-up kludge + } return false; } diff --git a/src/scopes/ft2_scopes.c b/src/scopes/ft2_scopes.c @@ -66,11 +66,11 @@ void stopAllScopes(void) } // toggle mute -static void setChannel(int32_t chNr, bool on) +static void setChannelMute(int32_t chNr, bool off) { channel_t *ch = &channel[chNr]; - ch->channelOff = !on; + ch->channelOff = off; if (ch->channelOff) { ch->efx = 0; @@ -104,8 +104,8 @@ static void drawScopeNumber(uint16_t scopeXOffs, uint16_t scopeYOffs, uint8_t ch } else { - charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]); - charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]); + charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + (chNr / 10)); + charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, '0' + (chNr % 10)); } } else @@ -116,8 +116,8 @@ static void drawScopeNumber(uint16_t scopeXOffs, uint16_t scopeYOffs, uint8_t ch } else { - charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]); - charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]); + charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + (chNr / 10)); + charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, '0' + (chNr % 10)); } } } @@ -157,7 +157,7 @@ static void redrawScope(int32_t ch) drawFramework(x, y, scopeLen + 2, 38, FRAMEWORK_TYPE2); // draw mute graphics if channel is muted - if (!editor.chnMode[i]) + if (editor.channelMuted[i]) { const uint16_t muteGfxLen = scopeMuteBMP_Widths[chanLookup]; const uint16_t muteGfxX = x + ((scopeLen - muteGfxLen) >> 1); @@ -191,41 +191,41 @@ static void channelMode(int32_t chn) bool test = false; for (i = 0; i < song.numChannels; i++) { - if (i != chn && !editor.chnMode[i]) + if (i != chn && editor.channelMuted[i]) test = true; } if (test) { for (i = 0; i < song.numChannels; i++) - editor.chnMode[i] = true; + editor.channelMuted[i] = false; } else { for (i = 0; i < song.numChannels; i++) - editor.chnMode[i] = (i == chn); + editor.channelMuted[i] = !(i == chn); } } else if (m) { - editor.chnMode[chn] ^= 1; + editor.channelMuted[chn] ^= 1; } else { - if (editor.chnMode[chn]) + if (!editor.channelMuted[chn]) { config.multiRecChn[chn] ^= 1; } else { config.multiRecChn[chn] = true; - editor.chnMode[chn] = true; + editor.channelMuted[chn] = false; m = true; } } for (i = 0; i < song.numChannels; i++) - setChannel(i, editor.chnMode[i]); + setChannelMute(i, editor.channelMuted[i]); if (m2) { @@ -421,7 +421,7 @@ void drawScopes(void) } const uint16_t scopeDrawLen = scopeLens[i]; - if (!editor.chnMode[i]) // scope muted (mute graphics blit()'ed elsewhere) + if (editor.channelMuted[i]) // scope muted (mute graphics blit()'ed elsewhere) { scopeXOffs += scopeDrawLen+3; // align x to next scope continue; diff --git a/vs2019_project/ft2-clone/ft2-clone.vcxproj b/vs2019_project/ft2-clone/ft2-clone.vcxproj @@ -319,6 +319,7 @@ <ClCompile Include="..\..\src\ft2_pattern_draw.c" /> <ClCompile Include="..\..\src\ft2_pushbuttons.c" /> <ClCompile Include="..\..\src\ft2_radiobuttons.c" /> + <ClCompile Include="..\..\src\ft2_random.c" /> <ClCompile Include="..\..\src\ft2_sample_ed_features.c" /> <ClCompile Include="..\..\src\ft2_sampling.c" /> <ClCompile Include="..\..\src\ft2_replayer.c" /> @@ -326,6 +327,7 @@ <ClCompile Include="..\..\src\ft2_sample_loader.c" /> <ClCompile Include="..\..\src\ft2_sample_saver.c" /> <ClCompile Include="..\..\src\ft2_scrollbars.c" /> + <ClCompile Include="..\..\src\ft2_smpfx.c" /> <ClCompile Include="..\..\src\ft2_structs.c" /> <ClCompile Include="..\..\src\ft2_sysreqs.c" /> <ClCompile Include="..\..\src\ft2_tables.c" /> @@ -417,6 +419,7 @@ <ClInclude Include="..\..\src\ft2_pattern_draw.h" /> <ClInclude Include="..\..\src\ft2_pushbuttons.h" /> <ClInclude Include="..\..\src\ft2_radiobuttons.h" /> + <ClInclude Include="..\..\src\ft2_random.h" /> <ClInclude Include="..\..\src\ft2_sample_ed_features.h" /> <ClInclude Include="..\..\src\ft2_sampling.h" /> <ClInclude Include="..\..\src\ft2_replayer.h" /> @@ -425,6 +428,7 @@ <ClInclude Include="..\..\src\ft2_sample_saver.h" /> <ClInclude Include="..\..\src\ft2_scopedraw.h" /> <ClInclude Include="..\..\src\ft2_scrollbars.h" /> + <ClInclude Include="..\..\src\ft2_smpfx.h" /> <ClInclude Include="..\..\src\ft2_structs.h" /> <ClInclude Include="..\..\src\ft2_sysreqs.h" /> <ClInclude Include="..\..\src\ft2_tables.h" /> diff --git a/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters b/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters @@ -176,6 +176,8 @@ <ClCompile Include="..\..\src\mixer\ft2_quadratic_spline.c"> <Filter>mixer</Filter> </ClCompile> + <ClCompile Include="..\..\src\ft2_random.c" /> + <ClCompile Include="..\..\src\ft2_smpfx.c" /> </ItemGroup> <ItemGroup> <ClInclude Include="..\..\src\rtmidi\RtMidi.h"> @@ -337,6 +339,12 @@ <ClInclude Include="..\..\src\mixer\ft2_quadratic_spline.h"> <Filter>mixer</Filter> </ClInclude> + <ClInclude Include="..\..\src\ft2_random.h"> + <Filter>headers</Filter> + </ClInclude> + <ClInclude Include="..\..\src\ft2_smpfx.h"> + <Filter>headers</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Filter Include="mixer">