diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e85c4de7..20e26737 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: - BUILD_TYPE: Debug + BUILD_TYPE: RelWithDebInfo jobs: build: diff --git a/CMakeLists.txt b/CMakeLists.txt index c8d61667..7105990b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -793,7 +793,7 @@ endif() if(ANDROID AND NOT TERMUX) add_library(furnace SHARED ${USED_SOURCES}) elseif(WIN32) - add_executable(furnace ${USED_SOURCES}) + add_executable(furnace WIN32 ${USED_SOURCES}) else() add_executable(furnace ${USED_SOURCES}) endif() diff --git a/demos/ay8930/joyful_.fur b/demos/ay8930/joyful_.fur new file mode 100644 index 00000000..a486bfd6 Binary files /dev/null and b/demos/ay8930/joyful_.fur differ diff --git a/demos/c64/yeah!.fur b/demos/c64/yeah!.fur new file mode 100644 index 00000000..54312aff Binary files /dev/null and b/demos/c64/yeah!.fur differ diff --git a/demos/nes/invicibility_mmc5_n163_fds.fur b/demos/nes/invicibility_mmc5_n163_fds.fur new file mode 100644 index 00000000..ef9b05bf Binary files /dev/null and b/demos/nes/invicibility_mmc5_n163_fds.fur differ diff --git a/extern/imgui_patched/imgui_widgets.cpp b/extern/imgui_patched/imgui_widgets.cpp index 737c6e09..45bfd82c 100644 --- a/extern/imgui_patched/imgui_widgets.cpp +++ b/extern/imgui_patched/imgui_widgets.cpp @@ -3112,8 +3112,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(frame_bb, id, NULL, ImGuiItemFlags_NoInertialScroll)) + if (!ItemAdd(frame_bb, id, NULL, (temp_input_allowed ? ImGuiItemFlags_Inputable : 0) | ImGuiItemFlags_NoInertialScroll)) return false; // Default format string when passing NULL @@ -3122,13 +3123,29 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) format = PatchFormatStringFloatToInt(format); + // Tabbing or CTRL-clicking on Slider turns it into an input box const bool hovered = ItemHoverable(frame_bb, id); - if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id) + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) { - SetActiveID(id, window); - SetFocusID(id, window); - FocusWindow(window); - g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + if (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (temp_input_allowed && (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id)) + temp_input_is_active = true; + } + } + + if (temp_input_is_active) + { + // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set + const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); } // Draw frame @@ -3154,6 +3171,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return value_changed; } diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index 9d784d38..54d18000 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -121,8 +121,8 @@ ID | macro 33 | KSR ---|----------------------------- 40 | operator 2 macros -60 | operator 2 macros -80 | operator 2 macros +60 | operator 3 macros +80 | operator 4 macros the interpretation of duty, wave and extra macros depends on chip/instrument type: diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index e04834cc..19a07b10 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -1,8 +1,14 @@ # wavetable editor -Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. +Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.6pre4, wavetable editor affects PC Engine, WonderSwan, Namco WSGs, Virtual Boy, Game.com, SCC, FDS, Seta X1-010, Konami Bubble System WSG, SNES, Amiga and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Namco WSG, N163, Game.com, Virtual Boy and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System, SNES, Namco WSG and N163, 32-level height for PCE and 64-level height for Virtual Boy. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. + +Furnace's wavetable editor features multiple ways of creating desired waveform shape: + +- Shape tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders TODO: what the last two are doing? What is amplitude/phase for?) +- FM is for creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators. +- WaveTools allows user to fine-tune the waveform: scale said waveform in both X and Y axes, smoothen, amplify, normalize, convert to signed/unisgned, invert or even randomize the wavetable. ## wavetable synthesizer diff --git a/papers/doc/7-systems/qsound.md b/papers/doc/7-systems/qsound.md index 7089ec0e..81bfe4b9 100644 --- a/papers/doc/7-systems/qsound.md +++ b/papers/doc/7-systems/qsound.md @@ -8,7 +8,7 @@ because the chip lacks sample interpolation, it is recommended that you try to p the QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. it is however possible to adjust the feedback and length of the echo buffer (the initial values can be set in the "configure chip" option in the file menu or the chip manager). -there are also 3 ADPCM channels, however they cannot be used in Furnace yet. they have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz. +there are also 3 ADPCM channels. ADPCM samples are fixed to 8012 Hz. # effects diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 137dbdaf..d2333ab5 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -178,7 +178,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc logD("config does not exist"); if (createOnFail) { logI("creating default config."); - reportError(fmt::sprintf("Creating default config: %s",strerror(errno))); + //reportError(fmt::sprintf("Creating default config: %s",strerror(errno))); return save(path,redundancy); } else { reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno))); @@ -191,7 +191,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc logD("config does not exist"); if (createOnFail) { logI("creating default config."); - reportError(fmt::sprintf("Creating default config: %s",strerror(errno))); + //reportError(fmt::sprintf("Creating default config: %s",strerror(errno))); return save(path); } else { reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno))); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 12fe249f..55c74417 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -55,6 +55,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul return "03xx: Portamento"; case 0x04: return "04xy: Vibrato (x: speed; y: depth)"; + case 0x05: + return "05xy: Volume slide + vibrato (compatibility only!)"; + case 0x06: + return "06xy: Volume slide + portamento (compatibility only!)"; case 0x07: return "07xy: Tremolo (x: speed; y: depth)"; case 0x08: @@ -2419,6 +2423,13 @@ void DivEngine::stepOne(int row) { void DivEngine::stop() { BUSY_BEGIN; freelance=false; + if (!playing) { + //Send midi panic + if (output) if (output->midiOut!=NULL) { + output->midiOut->send(TAMidiMessage(TA_MIDI_CONTROL,0x7B,0)); + logV("Midi panic sent"); + } + } playing=false; extValuePresent=false; endOfSong=false; // what? diff --git a/src/engine/engine.h b/src/engine/engine.h index d31de903..7705b259 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -105,7 +105,7 @@ struct DivChannelState { int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; - unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR; + unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp; bool wentThroughNote, goneThroughNote; @@ -146,6 +146,8 @@ struct DivChannelState { panR(255), panRL(0), panRR(0), + lastVibrato(0), + lastPorta(0), doNote(false), legato(false), portaStop(false), diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index f7b833e1..ea0cca7e 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -23,11 +23,7 @@ static DivPattern emptyPat; DivPattern::DivPattern() { - memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); - for (int i=0; idata,data,sizeof(data)); } +void DivPattern::clear() { + memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); + for (int i=0; i>8); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 6fea25c3..a54667a7 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1558,7 +1558,7 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) { } DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { - if (oplType==759) { + if (oplType==759 || chipType==8950) { if (ch>=totalChans+1) return NULL; } else { if (ch>=totalChans) return NULL; diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index d5f54e6a..4d32fc5a 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -29,6 +29,7 @@ const char* regCheatSheetPV1000[]={ "CH1_Pitch", "00", "CH2_Pitch", "01", "CH3_Pitch", "02", + "Control", "03", NULL }; @@ -38,11 +39,10 @@ const char** DivPlatformPV1000::getRegisterSheet() { void DivPlatformPV1000::acquire(short** buf, size_t len) { for (size_t h=0; hdata[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12); + oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]); } } } @@ -73,17 +73,17 @@ void DivPlatformPV1000::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=0x3f-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); - if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>62) chan[i].freq=62; if (isMuted[i]) chan[i].keyOn=false; if (chan[i].keyOn) { - rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0 : chan[i].freq); + rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq); chan[i].keyOn=false; } else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) { - rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0 : chan[i].freq); + rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq); } if (chan[i].keyOff) { - rWrite(i,0); + rWrite(i,0x3f); chan[i].keyOff=false; } chan[i].freqChanged=false; @@ -137,6 +137,13 @@ int DivPlatformPV1000::dispatch(DivCommand c) { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; + case DIV_CMD_STD_NOISE_MODE: // ring modulation + if (c.value&1) { + rWrite(3,3); + } else { + rWrite(3,2); + } + break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; @@ -224,16 +231,21 @@ unsigned char* DivPlatformPV1000::getRegisterPool() { } int DivPlatformPV1000::getRegisterPoolSize() { - return 3; + return 4; } void DivPlatformPV1000::reset() { - memset(regPool,0,3); + memset(regPool,0,4); for (int i=0; i<3; i++) { chan[i]=Channel(); chan[i].std.setEngine(parent); } d65010g031_reset(&d65010g031); + // mute + rWrite(0,0x3f); + rWrite(1,0x3f); + rWrite(2,0x3f); + rWrite(3,2); } int DivPlatformPV1000::getOutputCount() { diff --git a/src/engine/platform/pv1000.h b/src/engine/platform/pv1000.h index c3254076..852bf120 100644 --- a/src/engine/platform/pv1000.h +++ b/src/engine/platform/pv1000.h @@ -33,7 +33,7 @@ class DivPlatformPV1000: public DivDispatch { DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; - unsigned char regPool[3]; + unsigned char regPool[4]; d65010g031_t d65010g031; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index fbf8e00d..7be43801 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -151,8 +151,8 @@ void DivPlatformRF5C68::tick(bool sysTick) { if (s->isLoopable()) { loop=start+s->loopStart; } - start=MIN(start,getSampleMemCapacity()-31); - loop=MIN(loop,getSampleMemCapacity()-31); + start=MIN(start,getSampleMemCapacity()-32); + loop=MIN(loop,getSampleMemCapacity()-32); rWrite(8,keyoff); // force keyoff first chWrite(i,6,start>>8); chWrite(i,4,loop&0xff); @@ -425,7 +425,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) { } int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); - int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length); + int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-32,length); if (actualLength>0) { sampleOffRFC[i]=memPos; for (int j=0; j0)?(val|0x80):(0-val); } // write end of sample marker - memset(&sampleMem[memPos],0xff,31); - memPos+=31; + memset(&sampleMem[memPos],0xff,32); + memPos+=32; } if (actualLengthperiod > 0) { - int period = d65010g031_max(1, (0x3f - square->period)); + const int period = square->period; square->counter += cycle; while (square->counter >= period) { @@ -82,9 +82,9 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle) // this is the bit I altered // THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such! const int d65Volumes[3]={ - 3840, - 5120, - 8192 + 3840, // -6dB + 5120, // -3dB + 8192 // 0dB }; int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle) @@ -92,7 +92,29 @@ int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle) int out = 0; for (int i = 0; i < 3; i++) { - out += d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i]; + d65010g031->out[i] = 0; + } + if (d65010g031->ctrl & 2) + { + if (d65010g031->ctrl & 1) // ring modulation + { + int sout[3] = { + d65010g031_square_tick(&d65010g031->square[0], cycle), + d65010g031_square_tick(&d65010g031->square[1], cycle), + d65010g031_square_tick(&d65010g031->square[2], cycle), + }; + d65010g031->out[0] = (sout[0] ^ sout[1]) ? d65Volumes[0] : -d65Volumes[0]; + d65010g031->out[1] = (sout[1] ^ sout[2]) ? d65Volumes[1] : -d65Volumes[1]; + d65010g031->out[2] = (sout[2] ? d65Volumes[2] : -d65Volumes[2]); + } + else + { + for (int i = 0; i < 3; i++) + { + d65010g031->out[i] = d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i]; + } + } + out = d65010g031->out[0] + d65010g031->out[1] + d65010g031->out[2]; } return out; } @@ -105,12 +127,25 @@ void d65010g031_reset(struct d65010g031_t *d65010g031) d65010g031->square[i].counter = 0; d65010g031->square[i].out = 0; } + d65010g031->ctrl = 0; } void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d) { - if (a < 3) + switch (a) { - d65010g031->square[a].period = d & 0x3f; + case 3: + d65010g031->ctrl = d; + break; + default: + { + const unsigned char per = (unsigned char)(~d) & 0x3f; + if ((per == 0) && (d65010g031->square[a].period != 0)) + { + d65010g031->square[a].out ^= 1; + } + d65010g031->square[a].period = per; + break; + } } } diff --git a/src/engine/platform/sound/d65modified.h b/src/engine/platform/sound/d65modified.h index 49dc13a1..eae0f898 100644 --- a/src/engine/platform/sound/d65modified.h +++ b/src/engine/platform/sound/d65modified.h @@ -54,6 +54,8 @@ struct d65010g031_square_t struct d65010g031_t { struct d65010g031_square_t square[3]; + signed short out[3]; + unsigned char ctrl; }; int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 63800174..44f94d99 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -222,7 +222,8 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) { if (stereo) buf[1][h]=tempR; for (int i=0; i<16; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1; + int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767); } } } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 808e2b15..8cc2f825 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -680,6 +680,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { + chan[i].lastPorta=effectVal; calledPorta=true; if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { chan[i].portaNote=chan[i].note; @@ -700,11 +701,78 @@ void DivEngine::processRow(int i, bool afterDelay) { } break; case 0x04: // vibrato + if (effectVal) chan[i].lastVibrato=effectVal; chan[i].vibratoDepth=effectVal&15; chan[i].vibratoRate=effectVal>>4; dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; + case 0x05: // vol slide + vibrato + if (effectVal==0) { + chan[i].vibratoDepth=0; + chan[i].vibratoRate=0; + } else { + chan[i].vibratoDepth=chan[i].lastVibrato&15; + chan[i].vibratoRate=chan[i].lastVibrato>>4; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + // TODO: non-0x-or-x0 value should be treated as 00 + if (effectVal!=0) { + if ((effectVal&15)!=0) { + chan[i].volSpeed=-(effectVal&15)*64; + } else { + chan[i].volSpeed=(effectVal>>4)*64; + } + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + } else { + chan[i].volSpeed=0; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); + break; + case 0x06: // vol slide + porta + if (effectVal==0 || chan[i].lastPorta==0) { + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); + chan[i].inPorta=false; + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + } else { + calledPorta=true; + if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { + chan[i].portaNote=chan[i].note; + chan[i].portaSpeed=-1; + } else { + chan[i].portaNote=chan[i].note; + chan[i].portaSpeed=chan[i].lastPorta; + chan[i].inPorta=true; + chan[i].wasShorthandPorta=false; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0))); + chan[i].portaStop=true; + if (chan[i].keyOn) chan[i].doNote=false; + chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! + chan[i].scheduledSlideReset=false; + dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1)); + lastSlide=0x1337; // i hate this so much + } + // TODO: non-0x-or-x0 value should be treated as 00 + if (effectVal!=0) { + if ((effectVal&15)!=0) { + chan[i].volSpeed=-(effectVal&15)*64; + } else { + chan[i].volSpeed=(effectVal>>4)*64; + } + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + } else { + chan[i].volSpeed=0; + } + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); + break; case 0x07: // tremolo // TODO // this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different @@ -1125,25 +1193,47 @@ void DivEngine::nextRow() { bool wantPreNote=false; if (disCont[dispatchOfChan[i]].dispatch!=NULL) { wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote(); - if (wantPreNote) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + if (wantPreNote) { + int addition=0; + for (int j=0; jdata[curRow][4+(j<<1)]==0xed) { + if (pat->data[curRow][5+(j<<1)]>0) { + addition=pat->data[curRow][5+(j<<1)]; + break; + } + } + } + dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks+addition)); + } } if (song.oneTickCut) { bool doPrepareCut=true; + int addition=0; for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { doPrepareCut=false; break; } + if (pat->data[curRow][4+(j<<1)]==0x06) { + doPrepareCut=false; + break; + } if (pat->data[curRow][4+(j<<1)]==0xea) { if (pat->data[curRow][5+(j<<1)]>0) { doPrepareCut=false; break; } } + if (pat->data[curRow][4+(j<<1)]==0xed) { + if (pat->data[curRow][5+(j<<1)]>0) { + addition=pat->data[curRow][5+(j<<1)]; + break; + } + } } - if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks; + if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks+addition; } } } diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index f882a3d0..d46ced4f 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -892,10 +892,10 @@ bool DivSample::resampleBlep(double r) { } } for (int i=0; i127) result=127; - data16[i]=round(result); + data8[i]=round(result); } } delete[] floatData; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 0ce88965..86a1ce0f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1839,7 +1839,11 @@ void DivEngine::registerSystems() { {"Square 1", "Square 2", "Square 3"}, {"S1", "S2", "S3"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_PV1000, DIV_INS_PV1000, DIV_INS_PV1000} + {DIV_INS_PV1000, DIV_INS_PV1000, DIV_INS_PV1000}, + {}, + { + {0x10, {DIV_CMD_STD_NOISE_MODE, "10xx: Set ring modulation (0: disable, 1: enable)"}} + } ); sysDefs[DIV_SYSTEM_SFX_BEEPER_QUADTONE]=new DivSysDef( diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index d68302b5..20ec440f 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -1083,6 +1083,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p bool trailing=false; bool beenOneLoopAlready=false; + bool mayWriteRate=(fmod(curSubSong->hz,1.0)<0.00001 || fmod(curSubSong->hz,1.0)>0.99999); int countDown=MAX(0,trailingTicks)+1; for (int i=0; iwriteI(0); w->writeI(0); } + if (mayWriteRate) { + w->writeI(round(curSubSong->hz)); + } w->seek(0x34,SEEK_SET); w->writeI(songOff-0x34); if (version>=0x170) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 9345181b..da732bed 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -561,18 +561,32 @@ void FurnaceGUI::doAction(int what) { doFlip(); break; case GUI_ACTION_PAT_COLLAPSE_ROWS: - doCollapse(2); + doCollapse(2,selStart,selEnd); break; case GUI_ACTION_PAT_EXPAND_ROWS: - doExpand(2); + doExpand(2,selStart,selEnd); break; - case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO + case GUI_ACTION_PAT_COLLAPSE_PAT: { + SelectionPoint selEndPat; + selEndPat.xCoarse=e->getTotalChannelCount()-1; + selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2; + selEndPat.y=e->curSubSong->patLen-1; + doCollapse(2,SelectionPoint(0,0,0),selEndPat); break; - case GUI_ACTION_PAT_EXPAND_PAT: // TODO + } + case GUI_ACTION_PAT_EXPAND_PAT: { + SelectionPoint selEndPat; + selEndPat.xCoarse=e->getTotalChannelCount()-1; + selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2; + selEndPat.y=e->curSubSong->patLen-1; + doExpand(2,SelectionPoint(0,0,0),selEndPat); break; - case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + } + case GUI_ACTION_PAT_COLLAPSE_SONG: + doCollapseSong(2); break; - case GUI_ACTION_PAT_EXPAND_SONG: // TODO + case GUI_ACTION_PAT_EXPAND_SONG: + doExpandSong(2); break; case GUI_ACTION_PAT_LATCH: // TODO break; diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 77d76da2..1a2b1b11 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -67,6 +67,9 @@ void FurnaceGUI::prepareUndo(ActionType action) { e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO + break; case GUI_UNDO_REPLACE: // this is handled by doReplace() break; } @@ -130,6 +133,9 @@ void FurnaceGUI::makeUndo(ActionType action) { doPush=true; } break; + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO + break; case GUI_UNDO_REPLACE: // this is handled by doReplace() break; } @@ -839,50 +845,56 @@ void FurnaceGUI::doFlip() { makeUndo(GUI_UNDO_PATTERN_FLIP); } -void FurnaceGUI::doCollapse(int divider) { +void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd) { + if (divider<2) return; + if (e->curSubSong->patLencurSubSong->chanShow[iCoarse]) continue; DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); - for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsecurPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; } patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; } - for (int j=0; j<=selEnd.y-selStart.y; j++) { - if (j*divider>=selEnd.y-selStart.y) { + for (int j=0; j<=sEnd.y-sStart.y; j++) { + if (j*divider>=sEnd.y-sStart.y) { if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; + pat->data[j+sStart.y][0]=0; + pat->data[j+sStart.y][1]=0; } else { - pat->data[j+selStart.y][iFine+1]=-1; + pat->data[j+sStart.y][iFine+1]=-1; } } else { if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; + pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y][0]; } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; + pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y][iFine+1]; if (iFine==0) { for (int k=1; k=selEnd.y-selStart.y) break; - if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; - pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + if ((j*divider+k)>=sEnd.y-sStart.y) break; + if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break; + pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0]; + pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1]; } } else { for (int k=1; k=selEnd.y-selStart.y) break; - if (pat->data[j+selStart.y][iFine+1]!=-1) break; - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + if ((j*divider+k)>=sEnd.y-sStart.y) break; + if (pat->data[j+sStart.y][iFine+1]!=-1) break; + pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y+k][iFine+1]; } } } @@ -894,41 +906,41 @@ void FurnaceGUI::doCollapse(int divider) { makeUndo(GUI_UNDO_PATTERN_COLLAPSE); } -void FurnaceGUI::doExpand(int multiplier) { - if (multiplier<1) return; +void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) { + if (multiplier<2) return; finishSelection(); prepareUndo(GUI_UNDO_PATTERN_EXPAND); DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + int iCoarse=sStart.xCoarse; + int iFine=sStart.xFine; + for (; iCoarse<=sEnd.xCoarse; iCoarse++) { if (!e->curSubSong->chanShow[iCoarse]) continue; DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); - for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsecurPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; } patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; } - for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->curSubSong->patLen) break; + for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) { + if ((j+sStart.y)>=e->curSubSong->patLen) break; if ((j%multiplier)!=0) { if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; + pat->data[j+sStart.y][0]=0; + pat->data[j+sStart.y][1]=0; } else { - pat->data[j+selStart.y][iFine+1]=-1; + pat->data[j+sStart.y][iFine+1]=-1; } continue; } if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; + pat->data[j+sStart.y][0]=patBuffer.data[j/multiplier+sStart.y][0]; } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; + pat->data[j+sStart.y][iFine+1]=patBuffer.data[j/multiplier+sStart.y][iFine+1]; } } iFine=0; @@ -937,6 +949,162 @@ void FurnaceGUI::doExpand(int multiplier) { makeUndo(GUI_UNDO_PATTERN_EXPAND); } +void FurnaceGUI::doCollapseSong(int divider) { + if (divider<2) return; + finishSelection(); + + UndoStep us; + us.type=GUI_UNDO_PATTERN_COLLAPSE_SONG; + + DivPattern patCopy; + + size_t subSong=e->getCurrentSubSong(); + for (int i=0; igetTotalChannelCount(); i++) { + for (int j=0; jcurPat[i].data[j]==NULL) continue; + + DivPattern* pat=e->curPat[i].getPattern(j,true); + pat->copyOn(&patCopy); + pat->clear(); + for (int k=0; kdata[k/divider][0]==0 && pat->data[k/divider][1]==0)) continue; + } else { + if (pat->data[k/divider][l+1]!=-1) continue; + } + + if (l==0) { + pat->data[k/divider][l]=patCopy.data[k][l]; + } + pat->data[k/divider][l+1]=patCopy.data[k][l+1]; + + if (l>3 && !(l&1)) { // scale effects as needed + switch (pat->data[k/divider][l]) { + case 0x0d: + pat->data[k/divider][l+1]/=divider; + break; + case 0x0f: + pat->data[k/divider][l+1]=CLAMP(pat->data[k/divider][l+1]*divider,1,255); + break; + } + } + } + } + + // put undo + for (int k=0; kdata[k][l]!=patCopy.data[k][l]) { + us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l])); + } + } + } + } + } + // magic + unsigned char* subSongInfoCopy=new unsigned char[1024]; + memcpy(subSongInfoCopy,e->curSubSong,1024); + e->curSubSong->patLen/=divider; + for (int i=0; icurSubSong->speeds.len; i++) { + e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]*divider,1,255); + } + unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong; + for (int i=0; i<1024; i++) { + if (subSongInfoCopy[i]!=newSubSongInfo[i]) { + us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i])); + } + } + + if (!us.pat.empty()) { + undoHist.push_back(us); + redoHist.clear(); + if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); + } + + if (e->isPlaying()) e->play(); +} + +void FurnaceGUI::doExpandSong(int multiplier) { + if (multiplier<2) return; + if (e->curSubSong->patLen>(256/multiplier)) { + showError("can't expand any further!"); + return; + } + finishSelection(); + + UndoStep us; + us.type=GUI_UNDO_PATTERN_EXPAND_SONG; + + DivPattern patCopy; + + size_t subSong=e->getCurrentSubSong(); + for (int i=0; igetTotalChannelCount(); i++) { + for (int j=0; jcurPat[i].data[j]==NULL) continue; + + DivPattern* pat=e->curPat[i].getPattern(j,true); + pat->copyOn(&patCopy); + pat->clear(); + for (int k=0; k<(256/multiplier); k++) { + for (int l=0; ldata[k*multiplier][0]==0 && pat->data[k*multiplier][1]==0)) continue; + } else { + if (pat->data[k*multiplier][l+1]!=-1) continue; + } + + if (l==0) { + pat->data[k*multiplier][l]=patCopy.data[k][l]; + } + pat->data[k*multiplier][l+1]=patCopy.data[k][l+1]; + + if (l>3 && !(l&1)) { // scale effects as needed + switch (pat->data[k*multiplier][l]) { + case 0x0d: + pat->data[k*multiplier][l+1]/=multiplier; + break; + case 0x0f: + pat->data[k*multiplier][l+1]=CLAMP(pat->data[k*multiplier][l+1]/multiplier,1,255); + break; + } + } + } + } + + // put undo + for (int k=0; kdata[k][l]!=patCopy.data[k][l]) { + us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l])); + } + } + } + } + } + // magic + unsigned char* subSongInfoCopy=new unsigned char[1024]; + memcpy(subSongInfoCopy,e->curSubSong,1024); + e->curSubSong->patLen*=multiplier; + for (int i=0; icurSubSong->speeds.len; i++) { + e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]/multiplier,1,255); + } + unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong; + for (int i=0; i<1024; i++) { + if (subSongInfoCopy[i]!=newSubSongInfo[i]) { + us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i])); + } + } + + if (!us.pat.empty()) { + undoHist.push_back(us); + redoHist.clear(); + if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); + } + + if (e->isPlaying()) e->play(); +} + void FurnaceGUI::doDrag() { int len=dragEnd.xCoarse-dragStart.xCoarse+1; @@ -985,6 +1153,8 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: case GUI_UNDO_PATTERN_DRAG: case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { @@ -1005,6 +1175,22 @@ void FurnaceGUI::doUndo() { break; } + bool shallReplay=false; + for (UndoOtherData& i: us.other) { + switch (i.target) { + case GUI_UNDO_TARGET_SONG: + ((unsigned char*)(&e->song))[i.off]=i.oldVal; + shallReplay=true; + break; + case GUI_UNDO_TARGET_SUBSONG: + if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break; + ((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.oldVal; + shallReplay=true; + break; + } + } + if (shallReplay && e->isPlaying()) play(); + if (curOrder>=e->curSubSong->ordersLen) { curOrder=e->curSubSong->ordersLen-1; oldOrder=curOrder; @@ -1045,6 +1231,8 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); @@ -1065,6 +1253,22 @@ void FurnaceGUI::doRedo() { break; } + bool shallReplay=false; + for (UndoOtherData& i: us.other) { + switch (i.target) { + case GUI_UNDO_TARGET_SONG: + ((unsigned char*)(&e->song))[i.off]=i.newVal; + shallReplay=true; + break; + case GUI_UNDO_TARGET_SUBSONG: + if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break; + ((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.newVal; + shallReplay=true; + break; + } + } + if (shallReplay && e->isPlaying()) play(); + if (curOrder>=e->curSubSong->ordersLen) { curOrder=e->curSubSong->ordersLen-1; oldOrder=curOrder; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1f1ce052..0c6b3254 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2842,9 +2842,23 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); - if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); - if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); + if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2,selStart,selEnd); + if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2,selStart,selEnd); + if (topMenu) { + ImGui::Separator(); + if (ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT))) doAction(GUI_ACTION_PAT_COLLAPSE_PAT); + if (ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT))) doAction(GUI_ACTION_PAT_EXPAND_PAT); + } + } + + if (topMenu) { + ImGui::Separator(); + if (ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG))) doAction(GUI_ACTION_PAT_COLLAPSE_SONG); + if (ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG))) doAction(GUI_ACTION_PAT_EXPAND_SONG); + } + + if (!basicMode) { if (topMenu) { ImGui::Separator(); if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) { @@ -2856,16 +2870,6 @@ void FurnaceGUI::editOptions(bool topMenu) { } } } - - /*if (topMenu) { - ImGui::Separator(); - ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); - ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); - - ImGui::Separator(); - ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); - ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); - }*/ } void FurnaceGUI::toggleMobileUI(bool enable, bool force) { @@ -3245,18 +3249,18 @@ void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) { // how many pixels should be visible at least at x/y dir #define OOB_PIXELS_SAFETY 25 -bool FurnaceGUI::detectOutOfBoundsWindow() { +bool FurnaceGUI::detectOutOfBoundsWindow(SDL_Rect& failing) { int count=SDL_GetNumVideoDisplays(); if (count<1) { - logW("bounds check: error %s",SDL_GetError()); + logW("bounds check: error: %s",SDL_GetError()); return false; } SDL_Rect rect; for (int i=0; i=scrX); @@ -3268,6 +3272,7 @@ bool FurnaceGUI::detectOutOfBoundsWindow() { } } + failing=rect; return false; } @@ -3838,11 +3843,11 @@ bool FurnaceGUI::loop() { } ImGui::Checkbox("loop",&vgmExportLoop); if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("trailing ticks:"); + ImGui::Text("loop trail:"); if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { vgmExportTrailingTicks=-1; } - if (ImGui::RadioButton("one loop",vgmExportTrailingTicks==-2)) { + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { vgmExportTrailingTicks=-2; } if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { @@ -6003,10 +6008,23 @@ bool FurnaceGUI::init() { #ifndef IS_MOBILE // if window would spawn out of bounds, force it to be get default position - if (!detectOutOfBoundsWindow()) { + SDL_Rect bounds; + if (!detectOutOfBoundsWindow(bounds)) { scrMax=false; scrX=scrConfX=SDL_WINDOWPOS_CENTERED; scrY=scrConfY=SDL_WINDOWPOS_CENTERED; + + // make sure our window isn't big + /*if (bounds.w ord; std::vector pat; + std::vector other; }; // -1 = any @@ -2065,8 +2088,10 @@ class FurnaceGUI { void doScale(float top); void doRandomize(int bottom, int top, bool mode); void doFlip(); - void doCollapse(int divider); - void doExpand(int multiplier); + void doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd); + void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd); + void doCollapseSong(int divider); + void doExpandSong(int multiplier); void doUndo(); void doRedo(); void doFind(); @@ -2135,7 +2160,7 @@ class FurnaceGUI { void runBackupThread(); void pushPartBlend(); void popPartBlend(); - bool detectOutOfBoundsWindow(); + bool detectOutOfBoundsWindow(SDL_Rect& failing); int processEvent(SDL_Event* ev); bool loop(); bool finish(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index bc1edc5f..a100b37a 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1464,7 +1464,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (!i.isBitfield) { if (settings.oldMacroVSlider) { ImGui::SameLine(0.0f); - if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { + if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"",ImGuiSliderFlags_NoInput)) { if (i.macro->vScroll<0) i.macro->vScroll=0; if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; } @@ -1592,7 +1592,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER if (i.macro->val[2]<0) i.macro->val[2]=0; if (i.macro->val[2]>255) i.macro->val[2]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("Sustain"); @@ -1601,7 +1601,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER if (i.macro->val[5]<0) i.macro->val[5]=0; if (i.macro->val[5]>255) i.macro->val[5]=255; - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1611,7 +1611,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER if (i.macro->val[3]<0) i.macro->val[3]=0; if (i.macro->val[3]>255) i.macro->val[3]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("SusTime"); @@ -1620,7 +1620,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER if (i.macro->val[6]<0) i.macro->val[6]=0; if (i.macro->val[6]>255) i.macro->val[6]=255; - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1630,7 +1630,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER if (i.macro->val[4]<0) i.macro->val[4]=0; if (i.macro->val[4]>255) i.macro->val[4]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("SusDecay"); @@ -1639,7 +1639,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER if (i.macro->val[7]<0) i.macro->val[7]=0; if (i.macro->val[7]>255) i.macro->val[7]=255; - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1652,7 +1652,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER if (i.macro->val[8]<0) i.macro->val[8]=0; if (i.macro->val[8]>255) i.macro->val[8]=255; - } + } rightClickable ImGui::EndTable(); } @@ -1695,7 +1695,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MLSpeed",&i.macro->val[11],0,255)) { PARAMETER if (i.macro->val[11]<0) i.macro->val[11]=0; if (i.macro->val[11]>255) i.macro->val[11]=255; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("Phase"); @@ -1704,7 +1704,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MLPhase",&i.macro->val[13],0,1023)) { PARAMETER if (i.macro->val[13]<0) i.macro->val[13]=0; if (i.macro->val[13]>1023) i.macro->val[13]=1023; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("Shape"); @@ -1713,7 +1713,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail if (CWSliderInt("##MLShape",&i.macro->val[12],0,2,macroLFOShapes[i.macro->val[12]&3])) { PARAMETER if (i.macro->val[12]<0) i.macro->val[12]=0; if (i.macro->val[12]>2) i.macro->val[12]=2; - } + } rightClickable ImGui::EndTable(); } @@ -2839,37 +2839,37 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; CENTER_VSLIDER; - P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); op.dr&=maxArDr; CENTER_VSLIDER; - P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable if (settings.susPosition==0) { ImGui::TableNextColumn(); op.sl&=15; CENTER_VSLIDER; - P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableNextColumn(); op.d2r&=31; CENTER_VSLIDER; - P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable } ImGui::TableNextColumn(); op.rr&=15; CENTER_VSLIDER; - P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable if (settings.susPosition==1) { ImGui::TableNextColumn(); op.sl&=15; CENTER_VSLIDER; - P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } ImGui::TableNextColumn(); @@ -2878,38 +2878,38 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; CENTER_VSLIDER; - P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable ImGui::TableNextColumn(); CENTER_VSLIDER; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { - P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); + P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable } else { int ksl=ins->type==DIV_INS_OPLL?op.ksl:kslMap[op.ksl&3]; if (CWVSliderInt("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),&ksl,0,3)) { op.ksl=(ins->type==DIV_INS_OPLL?ksl:kslMap[ksl&3]); PARAMETER; - } + } rightClickable } if (ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); + P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); rightClickable } ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable if (ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_VSLIDER; - P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); rightClickable } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { @@ -2920,7 +2920,7 @@ void FurnaceGUI::drawInsEdit() { if (detune<-3) detune=-3; if (detune>7) detune=7; op.dt=detuneUnmap[settings.unsignedDetune?1:0][detune+3]; - } + } rightClickable if (ins->type!=DIV_INS_FM) { ImGui::TableNextColumn(); @@ -3169,19 +3169,19 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; - P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::SameLine(); op.dr&=maxArDr; float textX_DR=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable float textX_SL=0.0f; if (settings.susPosition==0) { ImGui::SameLine(); op.sl&=15; textX_SL=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } float textX_D2R=0.0f; @@ -3189,19 +3189,19 @@ void FurnaceGUI::drawInsEdit() { ImGui::SameLine(); op.d2r&=31; textX_D2R=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable } ImGui::SameLine(); op.rr&=15; float textX_RR=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable if (settings.susPosition==1) { ImGui::SameLine(); op.sl&=15; textX_SL=ImGui::GetCursorPosX(); - P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable } ImVec2 prevCurPos=ImGui::GetCursorPos(); @@ -3500,7 +3500,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; - P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); @@ -4299,13 +4299,13 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); @@ -4800,7 +4800,7 @@ void FurnaceGUI::drawInsEdit() { // filter ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWSliderScalar("Filter 4,3 Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3])); rightClickable + P(CWSliderScalar("Filter Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3])); ImGui::TableNextRow(); ImGui::TableNextColumn(); P(CWSliderScalar("Filter K1",ImGuiDataType_U16,&ins->es5506.filter.k1,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable @@ -4884,17 +4884,17 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); @@ -4952,17 +4952,17 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); + P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); rightClickable ImGui::TableNextColumn(); - P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); + P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); rightClickable if (ins->snes.sus) { ImGui::TableNextColumn(); - P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); + P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); rightClickable } ImGui::TableNextColumn(); - P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); + P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); rightClickable ImGui::TableNextColumn(); drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.sus?ins->snes.d2:ins->snes.r,ins->snes.sus?ins->snes.r:31,(14-ins->snes.s*2),(ins->snes.r==0 || (ins->snes.sus && ins->snes.d2==0)),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); @@ -5024,7 +5024,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31; if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; - P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); + P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); rightClickable ImGui::TableNextColumn(); ImGui::Text("Envelope goes here..."); diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index e5bc09e4..7f783dde 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -100,7 +100,7 @@ void FurnaceGUI::drawOsc() { if (ImGui::VSliderFloat("##OscZoom",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscZoom,0.5,2.0)) { if (oscZoom<0.5) oscZoom=0.5; if (oscZoom>2.0) oscZoom=2.0; - } + } rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("zoom: %.2fx (%.1fdB)",oscZoom,20.0*log10(oscZoom*2.0)); } @@ -111,7 +111,7 @@ void FurnaceGUI::drawOsc() { if (ImGui::VSliderFloat("##OscWinSize",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscWindowSize,5.0,100.0)) { if (oscWindowSize<5.0) oscWindowSize=5.0; if (oscWindowSize>100.0) oscWindowSize=100.0; - } + } rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("window size: %.1fms",oscWindowSize); } diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 3a8fae4b..f5216db0 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -1111,23 +1111,26 @@ void FurnaceGUI::drawSampleEdit() { if (sampleTex!=NULL) { if (updateSampleTex) { - unsigned int* data=NULL; + unsigned int* dataT=NULL; int pitch=0; logD("updating sample texture."); - if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { + if (SDL_LockTexture(sampleTex,NULL,(void**)&dataT,&pitch)!=0) { logE("error while locking sample texture! %s",SDL_GetError()); } else { + unsigned int* data=new unsigned int[sampleTexW*sampleTexH]; + ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]); ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]); ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]); ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]); + int ij=0; for (int i=0; iisLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { - data[i*availX+j]=bgColorLoop; + data[ij++]=bgColorLoop; } else { - data[i*availX+j]=bgColor; + data[ij++]=bgColor; } } } @@ -1143,11 +1146,15 @@ void FurnaceGUI::drawSampleEdit() { for (unsigned int i=0; i<(unsigned int)availX; i++) { if (xCoarse>=sample->samples) break; int y1, y2; + int candMin=INT_MAX; + int candMax=INT_MIN; int totalAdvance=0; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { - y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse]; + if (candMaxdata8[xCoarse]) candMax=sample->data8[xCoarse]; } else { - y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse]; + if (candMaxdata16[xCoarse]) candMax=sample->data16[xCoarse]; } xFine+=xAdvanceFine; if (xFine>=16777216) { @@ -1157,27 +1164,44 @@ void FurnaceGUI::drawSampleEdit() { totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { + if (xCoarse>=sample->samples) break; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { - y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse]; + if (candMaxdata8[xCoarse]) candMax=sample->data8[xCoarse]; } else { - y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; - } - if (y1>y2) { - y2^=y1; - y1^=y2; - y2^=y1; - } - if (y1<0) y1=0; - if (y1>=availY) y1=availY-1; - if (y2<0) y2=0; - if (y2>=availY) y2=availY-1; - for (int j=y1; j<=y2; j++) { - data[i+availX*(availY-j-1)]=lineColor; + if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse]; + if (candMaxdata16[xCoarse]) candMax=sample->data16[xCoarse]; } if (totalAdvance>0) xCoarse++; } while ((totalAdvance--)>0); + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { + y1=(((unsigned char)candMin^0x80)*availY)>>8; + y2=(((unsigned char)candMax^0x80)*availY)>>8; + } else { + y1=(((unsigned short)candMin^0x8000)*availY)>>16; + y2=(((unsigned short)candMax^0x8000)*availY)>>16; + } + if (y1>y2) { + y2^=y1; + y1^=y2; + y2^=y1; + } + if (y1<0) y1=0; + if (y1>=availY) y1=availY-1; + if (y2<0) y2=0; + if (y2>=availY) y2=availY-1; + + const int s1=i+availX*(availY-y1-1); + const int s2=i+availX*(availY-y2-1); + + for (int j=s2; j<=s1; j+=availX) { + data[j]=lineColor; + } } + + memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int)); SDL_UnlockTexture(sampleTex); + delete[] data; } updateSampleTex=false; } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 26bed280..9ff8a5c8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -276,6 +276,8 @@ void FurnaceGUI::drawSettings() { ImVec2 setWindowSize=ImVec2(canvasW,canvasH); ImGui::SetNextWindowPos(setWindowPos); ImGui::SetNextWindowSize(setWindowSize); + } else { + ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f*dpiScale,100.0f*dpiScale),ImVec2(canvasW,canvasH)); } if (ImGui::Begin("Settings",&settingsOpen,ImGuiWindowFlags_NoDocking|globalWinFlags)) { if (!settingsOpen) { @@ -2303,10 +2305,12 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FLIP_SELECTION); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_PAT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_PAT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_SONG); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_SONG); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); - // TODO: collapse/expand pattern and song - KEYBIND_CONFIG_END; ImGui::TreePop(); } diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 0530a2d2..dce89bc9 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -598,7 +598,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGDuty",&waveGenDuty,0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -607,7 +607,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderInt("##WGExp",&waveGenPower,1,8)) { doGenerateWave(); - } + } rightClickable ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -616,7 +616,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGXOR",&waveGenInvertPoint,0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable ImGui::EndTable(); } @@ -636,7 +636,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGAmp",&waveGenAmp[i],-1.0f,1.0f)) { doGenerateWave(); - } + } rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { waveGenAmp[i]=0.0f; doGenerateWave(); @@ -647,7 +647,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (CWSliderFloat("##WGPhase",&waveGenPhase[i],0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { waveGenPhase[i]=0.0f; doGenerateWave(); @@ -690,7 +690,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::PushID(i); if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) { doGenerateWave(); - } + } rightClickable ImGui::PopID(); ImGui::TableNextColumn(); @@ -698,7 +698,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::PushID(i); if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) { doGenerateWave(); - } + } rightClickable ImGui::PopID(); ImGui::TableNextColumn(); @@ -706,7 +706,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::PushID(i); if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) { doGenerateWave(); - } + } rightClickable ImGui::PopID(); }