diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20e267370..e85c4de77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: - BUILD_TYPE: RelWithDebInfo + BUILD_TYPE: Debug jobs: build: diff --git a/demos/arcade/Maximum_Overdrive_NamcoWSG.fur b/demos/arcade/Maximum_Overdrive_NamcoWSG.fur new file mode 100644 index 000000000..0fdbc113d Binary files /dev/null and b/demos/arcade/Maximum_Overdrive_NamcoWSG.fur differ diff --git a/demos/ay8910/Second_Addition.fur b/demos/ay8910/Second_Addition.fur new file mode 100644 index 000000000..0ba3f5ed8 Binary files /dev/null and b/demos/ay8910/Second_Addition.fur differ diff --git a/demos/misc/BlueBolt_VIC20.fur b/demos/misc/BlueBolt_VIC20.fur new file mode 100644 index 000000000..51458e7fd Binary files /dev/null and b/demos/misc/BlueBolt_VIC20.fur differ diff --git a/demos/misc/GreenIdeas_PET.fur b/demos/misc/GreenIdeas_PET.fur new file mode 100644 index 000000000..c1571cc40 Binary files /dev/null and b/demos/misc/GreenIdeas_PET.fur differ diff --git a/demos/misc/TimeMan_SharpX68k.fur b/demos/misc/TimeMan_SharpX68k.fur index 0b51a2835..ae09890af 100644 Binary files a/demos/misc/TimeMan_SharpX68k.fur and b/demos/misc/TimeMan_SharpX68k.fur differ diff --git a/demos/msx/Nemesis2AirBattle.fur b/demos/msx/Nemesis2AirBattle.fur index 85da695ba..1eb325c30 100644 Binary files a/demos/msx/Nemesis2AirBattle.fur and b/demos/msx/Nemesis2AirBattle.fur differ diff --git a/demos/multichip/MegaMari-Cirno.fur b/demos/multichip/MegaMari-Cirno.fur new file mode 100644 index 000000000..2781a795f Binary files /dev/null and b/demos/multichip/MegaMari-Cirno.fur differ diff --git a/extern/nfd-modified/src/nfd_win.cpp b/extern/nfd-modified/src/nfd_win.cpp index e4342243c..9c12c3485 100644 --- a/extern/nfd-modified/src/nfd_win.cpp +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -204,7 +204,9 @@ static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr ) #ifdef _DEBUG int inStrCharacterCount = static_cast(NFDi_UTF8_Strlen(inStr)); - assert( ret == inStrCharacterCount ); + if (ret!=inStrCharacterCount) { + logW("length does not match! %d != %d",ret,inStrCharacterCount); + } #else _NFD_UNUSED(ret); #endif diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index c9e51bf0b..0ca5dff69 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -902,13 +902,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } #endif - int scaledLen=(double)length/samplePitches[pitch]; + int scaledLen=ceil((double)length/samplePitches[pitch]); if (scaledLen>0) { // resample logD("%d: scaling from %d...",i,pitch); short* newData=new short[scaledLen]; + memset(newData,0,scaledLen*sizeof(short)); int k=0; float mult=(float)(vol)/50.0f; for (double j=0; j=0x1b) { if (cutStart<0 || cutStart>scaledLen) { - logE("cutStart is out of range! (%d)",cutStart); + logE("cutStart is out of range! (%d, scaledLen: %d)",cutStart,scaledLen); lastError="file is corrupt or unreadable at samples"; delete[] file; return false; } if (cutEnd<0 || cutEnd>scaledLen) { - logE("cutEnd is out of range! (%d)",cutEnd); + logE("cutEnd is out of range! (%d, scaledLen: %d)",cutEnd,scaledLen); lastError="file is corrupt or unreadable at samples"; delete[] file; return false; diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 28ad45eb2..5f6e28292 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -433,7 +433,6 @@ void DivPlatformES5506::tick(bool sysTick) { off=(double)center/8363.0; } if (ins->amiga.useNoteMap) { - off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)next-48.0)/12.0)); chan[i].pcm.note=next; } // get loop mode @@ -618,10 +617,6 @@ void DivPlatformES5506::tick(bool sysTick) { } else { off=(double)center/8363.0; } - if (ins->amiga.useNoteMap) { - DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[chan[i].pcm.note]; - off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)chan[i].pcm.note-48.0)/12.0)); - } chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800; chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd-1)<<11))&0xffffff80; chan[i].pcm.freqOffs=PITCH_OFFSET*off; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 3ab6cec4e..08b7a65cc 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -433,13 +433,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) { DivInstrumentFM::Operator op=chan[2].state.op[ordch]; if (isOpMuted[ch-2] || !op.enable) { rWrite(baseAddr+0x40,127); - immWrite(baseAddr+0x40,127); - } else if (KVS(2,ordch)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); - immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,op.tl); - immWrite(baseAddr+0x40,op.tl); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index 04e1bde5e..0b11d45f6 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -34,7 +34,7 @@ const char* regCheatSheetK007232[]={ "CHX_StartM", "X*6+3", "CHX_StartH", "X*6+4", "CHX_Keyon", "X*6+5", - "SLEV", "C", // external IO + "SLEV", "C", // external IO (Volume for Mono speaker) "Loop", "D", // off-chip "CHX_Volume", "X*2+10", @@ -157,8 +157,7 @@ void DivPlatformK007232::tick(bool sysTick) { rWrite(0x10+i,(chan[i].lvol&0xf)|((chan[i].rvol&0xf)<<4)); chan[i].prevPan=newPan; } - } - else { + } else { const unsigned char prevVolume=lastVolume; lastVolume=(lastVolume&~(0xf<<(i<<2)))|((chan[i].resVol&0xf)<<(i<<2)); if (prevVolume!=lastVolume) { @@ -480,6 +479,7 @@ void DivPlatformK007232::setFlags(const DivConfig& flags) { rate=chipClock/4; stereo=flags.getBool("stereo",false); for (int i=0; i<2; i++) { + chan[i].volumeChanged=true; oscBuf[i]->rate=rate; } } diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 7367f50ce..728d7a91a 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -138,9 +138,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE); if (chan[i].pcm) { - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - // TODO: sample map? - DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); + DivSample* sample=parent->getSample(chan[i].sample); if (sample!=NULL) { double off=0.25; if (sample->centerRate<1) { @@ -209,6 +207,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { writeControlUpper(c.chan); } chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample); + if (chan[c.chan].pcm) { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value); chan[c.chan].freqChanged=true; diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index 13b4d4bba..de67c2faf 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -26,7 +26,7 @@ class DivPlatformSoundUnit: public DivDispatch { struct Channel: public SharedChannel { - int cutoff, baseCutoff, res, control, hasOffset; + int cutoff, baseCutoff, res, control, hasOffset, sample; signed char pan; unsigned char duty; bool noise, pcm, phaseReset, filterPhaseReset, switchRoles; @@ -43,6 +43,7 @@ class DivPlatformSoundUnit: public DivDispatch { res(0), control(0), hasOffset(0), + sample(-1), pan(0), duty(63), noise(false), diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index ae6eacd3e..6398ba7f1 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -542,13 +542,8 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) { DivInstrumentFM::Operator op=chan[2].state.op[ordch]; if (isOpMuted[ch-2] || !op.enable) { rWrite(baseAddr+0x40,127); - immWrite(baseAddr+0x40,127); - } else if (KVS(2,ordch)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); - immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,op.tl); - immWrite(baseAddr+0x40,op.tl); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } } diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 387ba110e..120261c0c 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -564,13 +564,8 @@ void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) { DivInstrumentFM::Operator op=chan[2].state.op[ordch]; if (isOpMuted[ch-2] || !op.enable) { rWrite(baseAddr+0x40,127); - immWrite(baseAddr+0x40,127); - } else if (KVS(2,ordch)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); - immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,op.tl); - immWrite(baseAddr+0x40,op.tl); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127)); } rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 97f5c0d61..5ab45ecbe 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -560,13 +560,8 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch]; if (isOpMuted[ch-extChanOffs] || !op.enable) { rWrite(baseAddr+0x40,127); - immWrite(baseAddr+0x40,127); - } else if (KVS(2,ordch)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); - immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,op.tl); - immWrite(baseAddr+0x40,op.tl); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); } rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 09347443a..620973503 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -560,13 +560,8 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) { DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch]; if (isOpMuted[ch-extChanOffs] || !op.enable) { rWrite(baseAddr+0x40,127); - immWrite(baseAddr+0x40,127); - } else if (KVS(2,ordch)) { - rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); - immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); } else { - rWrite(baseAddr+0x40,op.tl); - immWrite(baseAddr+0x40,op.tl); + rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127)); } rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); diff --git a/src/engine/platform/zxbeeperquadtone.cpp b/src/engine/platform/zxbeeperquadtone.cpp index 8bfce1701..3be196ad0 100644 --- a/src/engine/platform/zxbeeperquadtone.cpp +++ b/src/engine/platform/zxbeeperquadtone.cpp @@ -32,11 +32,11 @@ const char** DivPlatformZXBeeperQuadTone::getRegisterSheet() { void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) { bool o=false; for (size_t h=0; h=0 && curSamplesong.sampleLen) { + if (curSample>=0 && curSamplesong.sampleLen && !isMuted[4]) { while (curSamplePeriod>=chan[4].freq) { DivSample* s=parent->getSample(curSample); if (s->samples>0) { - o=(!isMuted[4]&&s->data8[curSamplePos++]>0); + if (!isMuted[4]) o=(s->data8[curSamplePos++]>0); if (curSamplePos>=s->samples) curSample=-1; // (theoretical) 32KiB limit if (curSamplePos>=32768*8) curSample=-1; @@ -59,6 +59,7 @@ void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) { if ((outputClock&1)==0) { chan[ch].sPosition+=(regPool[1+b]<<8)|regPool[0+b]; chan[ch].out=regPool[3+b]+((((chan[ch].sPosition>>8)&0xff)data[oscBuf[4]->needle++]=0; @@ -66,6 +67,22 @@ void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) { o=chan[ch].out&0x10; oscBuf[ch]->data[oscBuf[ch]->needle++]=o?32767:0; chan[ch].out<<=1; + + // if muted, ztill run sample + if (curSample>=0 && curSamplesong.sampleLen && isMuted[4]) { + while (curSamplePeriod>=chan[4].freq) { + DivSample* s=parent->getSample(curSample); + if (s->samples>0) { + if (curSamplePos>=s->samples) curSample=-1; + // (theoretical) 32KiB limit + if (curSamplePos>=32768*8) curSample=-1; + } else { + curSample=-1; + } + curSamplePeriod-=chan[4].freq; + } + curSamplePeriod+=40; + } } outputClock=(outputClock+1)&7; buf[0][h]=o?32767:0; @@ -283,7 +300,7 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) { void DivPlatformZXBeeperQuadTone::writeOutVol(int ch) { if (ch>=4) return; unsigned char val=(chan[ch].outVol>=1)?((chan[ch].outVol>=2)?31:7):0; - rWrite(3+ch*4,(!isMuted[ch]&&chan[ch].active)?val:0); + rWrite(3+ch*4,(chan[ch].active)?val:0); } void DivPlatformZXBeeperQuadTone::muteChannel(int ch, bool mute) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8947d566e..39872f3fd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1327,6 +1327,41 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { return; } + if (sampleMapWaitingInput) { + if (sampleMapColumn==1) { + // TODO: map? + if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { + alterSampleMap(true,-1); + return; + } + try { + int key=noteKeys.at(ev.key.keysym.scancode); + int num=12*curOctave+key; + + if (num<-60) num=-60; // C-(-5) + if (num>119) num=119; // B-9 + + alterSampleMap(true,num); + return; + } catch (std::out_of_range& e) { + } + } else { + // TODO: map? + if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { + alterSampleMap(false,-1); + return; + } + try { + int num=valueKeys.at(ev.key.keysym.sym); + if (num<10) { + alterSampleMap(false,num); + return; + } + } catch (std::out_of_range& e) { + } + } + } + // PER-WINDOW KEYS switch (curWindow) { case GUI_WINDOW_PATTERN: @@ -2905,7 +2940,7 @@ int FurnaceGUI::processEvent(SDL_Event* ev) { } #endif if (ev->type==SDL_KEYDOWN) { - if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { + if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) { if (settings.notePreviewBehavior==0) return 1; switch (curWindow) { case GUI_WINDOW_SAMPLE_EDIT: @@ -4913,6 +4948,8 @@ bool FurnaceGUI::loop() { } if (displayNew) { + newSongQuery=""; + newSongFirstFrame=true; displayNew=false; ImGui::OpenPopup("New Song"); } @@ -5561,9 +5598,57 @@ bool FurnaceGUI::loop() { } } } + + sampleMapWaitingInput=(curWindow==GUI_WINDOW_INS_EDIT && sampleMapFocused); curWindowThreadSafe=curWindow; + if (curWindow!=curWindowLast) { + int curWindowCat=0; + int lastWindowCat=0; + + switch (curWindow) { + case GUI_WINDOW_WAVE_LIST: + case GUI_WINDOW_WAVE_EDIT: + curWindowCat=1; + break; + case GUI_WINDOW_SAMPLE_LIST: + case GUI_WINDOW_SAMPLE_EDIT: + curWindowCat=2; + break; + default: + curWindowCat=0; + break; + } + switch (curWindowLast) { + case GUI_WINDOW_WAVE_LIST: + case GUI_WINDOW_WAVE_EDIT: + lastWindowCat=1; + break; + case GUI_WINDOW_SAMPLE_LIST: + case GUI_WINDOW_SAMPLE_EDIT: + lastWindowCat=2; + break; + default: + lastWindowCat=0; + break; + } + + if (curWindowCat!=lastWindowCat) { + switch (lastWindowCat) { + case 0: + e->autoNoteOffAll(); + break; + case 1: + e->stopWavePreview(); + break; + case 2: + e->stopSamplePreview(); + break; + } + } + } + SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, uiColors[GUI_COLOR_BACKGROUND].y*255, uiColors[GUI_COLOR_BACKGROUND].z*255, @@ -6426,8 +6511,12 @@ FurnaceGUI::FurnaceGUI(): samplePreviewOn(false), samplePreviewKey((SDL_Scancode)0), samplePreviewNote(0), - arpMacroScroll(-12), - pitchMacroScroll(-80), + sampleMapSelStart(-1), + sampleMapSelEnd(-1), + sampleMapDigit(0), + sampleMapColumn(0), + sampleMapFocused(false), + sampleMapWaitingInput(false), macroDragStart(0,0), macroDragAreaSize(0,0), macroDragCTarget(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index c712b531a..75bd3d265 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1566,6 +1566,7 @@ class FurnaceGUI { double exportFadeOut; + bool newSongFirstFrame; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; @@ -1680,8 +1681,11 @@ class FurnaceGUI { std::vector pressedPoints; std::vector releasedPoints; - int arpMacroScroll; - int pitchMacroScroll; + int sampleMapSelStart; + int sampleMapSelEnd; + int sampleMapDigit; + int sampleMapColumn; + bool sampleMapFocused, sampleMapWaitingInput; ImVec2 macroDragStart; ImVec2 macroDragAreaSize; @@ -1936,6 +1940,7 @@ class FurnaceGUI { void drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index); void drawMacros(std::vector& macros, FurnaceGUIMacroEditState& state); + void alterSampleMap(bool isNote, int val); void drawOrderButtons(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 86abb7817..bc1edc5fb 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2023,6 +2023,59 @@ void FurnaceGUI::drawMacros(std::vector& macros, FurnaceGUI } } +void FurnaceGUI::alterSampleMap(bool isNote, int val) { + if (curIns<0 || curIns>=(int)e->song.ins.size()) return; + DivInstrument* ins=e->song.ins[curIns]; + int sampleMapMin=sampleMapSelStart; + int sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; + } + + for (int i=sampleMapMin; i<=sampleMapMax; i++) { + if (i<0 || i>=120) continue; + + if (sampleMapColumn==1 && isNote) { + ins->amiga.noteMap[i].freq=val; + } else if (sampleMapColumn==0 && !isNote) { + if (val<0) { + ins->amiga.noteMap[i].map=-1; + } else if (sampleMapDigit>0) { + ins->amiga.noteMap[i].map*=10; + ins->amiga.noteMap[i].map+=val; + } else { + ins->amiga.noteMap[i].map=val; + } + if (ins->amiga.noteMap[i].map>=(int)e->song.sample.size()) { + ins->amiga.noteMap[i].map=((int)e->song.sample.size())-1; + } + } + } + + bool advance=false; + if (sampleMapColumn==1 && isNote) { + advance=true; + } else if (sampleMapColumn==0 && !isNote) { + int digits=1; + if (e->song.sample.size()>=10) digits=2; + if (e->song.sample.size()>=100) digits=3; + if (++sampleMapDigit>=digits) { + sampleMapDigit=0; + advance=true; + } + } + + if (advance && sampleMapMin==sampleMapMax) { + sampleMapSelStart++; + if (sampleMapSelStart>119) sampleMapSelStart=119; + sampleMapSelEnd=sampleMapSelStart; + } + + MARK_MODIFIED; +} + #define DRUM_FREQ(name,db,df,prop) \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ @@ -4342,6 +4395,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_K053260) { if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) { String sName; + bool wannaOpenSMPopup=false; if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) { sName="none selected"; } else { @@ -4406,71 +4460,191 @@ void FurnaceGUI::drawInsEdit() { ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { - if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) sampleMapFocused=false; + if (curWindowLast!=GUI_WINDOW_INS_EDIT) sampleMapFocused=false; + if (!sampleMapFocused) sampleMapDigit=0; + if (ImGui::BeginTable("NoteMap",4,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0,1); ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::TableNextColumn(); - ImGui::Text("Sample"); + ImGui::Text("#"); ImGui::TableNextColumn(); - ImGui::Text("Note"); + ImGui::Text("note"); + ImGui::TableNextColumn(); + ImGui::Text("sample name"); + int sampleMapMin=sampleMapSelStart; + int sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; + } + + ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(ImGuiCol_HeaderHovered)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive,ImGui::GetColorU32(ImGuiCol_HeaderHovered)); for (int i=0; i<120; i++) { DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i]; ImGui::TableNextRow(); - ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); ImGui::TableNextColumn(); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(ImGuiCol_TableHeaderBg)); ImGui::Text("%s",noteNames[60+i]); ImGui::TableNextColumn(); if (sampleMap.map<0 || sampleMap.map>=e->song.sampleLen) { - sName="-- empty --"; + sName=fmt::sprintf("---##SM%d",i); sampleMap.map=-1; } else { - sName=e->song.sample[sampleMap.map]->name; + sName=fmt::sprintf("%3d##SM%d",sampleMap.map,i); } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##SM",sName.c_str())) { - String id; - if (ImGui::Selectable("-- empty --",sampleMap.map==-1)) { PARAMETER - sampleMap.map=-1; + ImGui::PushFont(patFont); + ImGui::SetNextItemWidth(ImGui::CalcTextSize("00000").x); + ImGui::Selectable(sName.c_str(),(sampleMapWaitingInput && sampleMapColumn==0 && i>=sampleMapMin && i<=sampleMapMax)); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + sampleMapFocused=true; + sampleMapColumn=0; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; } - for (int j=0; jsong.sampleLen; j++) { - id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); - if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER - sampleMap.map=j; + ImGui::InhibitInertialScroll(); + } + if (sampleMapFocused && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + sampleMapSelEnd=i; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (sampleMapSelStart==sampleMapSelEnd) { + sampleMapFocused=true; + sampleMapColumn=0; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; } } - ImGui::EndCombo(); + if (sampleMapFocused) { + wannaOpenSMPopup=true; + } } + ImGui::PopFont(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - const char* nName="???"; + sName="???"; if ((sampleMap.freq+60)>0 && (sampleMap.freq+60)<180) { - nName=noteNames[sampleMap.freq+60]; + sName=noteNames[sampleMap.freq+60]; } - if (ImGui::BeginCombo("##SN",nName)) { - for (int j=0; j<180; j++) { - const char* nName2="???"; - nName2=noteNames[j]; - if (ImGui::Selectable(nName2,(sampleMap.freq+60)==j)) { - sampleMap.freq=j-60; + sName+=fmt::sprintf("##SN%d",i); + ImGui::PushFont(patFont); + ImGui::SetNextItemWidth(ImGui::CalcTextSize("00000").x); + ImGui::Selectable(sName.c_str(),(sampleMapWaitingInput && sampleMapColumn==1 && i>=sampleMapMin && i<=sampleMapMax)); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + sampleMapFocused=true; + sampleMapColumn=1; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; + } + ImGui::InhibitInertialScroll(); + } + if (sampleMapFocused && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + sampleMapSelEnd=i; + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (sampleMapSelStart==sampleMapSelEnd) { + sampleMapFocused=true; + sampleMapColumn=1; + sampleMapDigit=0; + sampleMapSelStart=i; + sampleMapSelEnd=i; + + sampleMapMin=sampleMapSelStart; + sampleMapMax=sampleMapSelEnd; + if (sampleMapMin>sampleMapMax) { + sampleMapMin^=sampleMapMax; + sampleMapMax^=sampleMapMin; + sampleMapMin^=sampleMapMax; } } - ImGui::EndCombo(); + if (sampleMapFocused) { + wannaOpenSMPopup=true; + } } + ImGui::PopFont(); - ImGui::PopID(); + ImGui::TableNextColumn(); + if (sampleMap.map>=0 && sampleMap.mapsong.sampleLen) { + ImGui::TextUnformatted(e->song.sample[sampleMap.map]->name.c_str()); + } } + ImGui::PopStyleColor(2); ImGui::EndTable(); } + } else { + sampleMapFocused=false; } ImGui::EndDisabled(); + if (wannaOpenSMPopup) { + ImGui::OpenPopup("SampleMapUtils"); + } + if (ImGui::BeginPopup("SampleMapUtils",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { + if (sampleMapSelStart==sampleMapSelEnd && sampleMapSelStart>=0 && sampleMapSelStart<120) { + if (ImGui::MenuItem("set entire map to this note")) { + if (sampleMapSelStart>=0 && sampleMapSelStart<120) { + for (int i=0; i<120; i++) { + if (i==sampleMapSelStart) continue; + ins->amiga.noteMap[i].freq=ins->amiga.noteMap[sampleMapSelStart].freq; + } + } + } + if (ImGui::MenuItem("set entire map to this sample")) { + if (sampleMapSelStart>=0 && sampleMapSelStart<120) { + for (int i=0; i<120; i++) { + if (i==sampleMapSelStart) continue; + ins->amiga.noteMap[i].map=ins->amiga.noteMap[sampleMapSelStart].map; + } + } + } + } + if (ImGui::MenuItem("reset notes")) { + for (int i=0; i<120; i++) { + ins->amiga.noteMap[i].freq=i; + } + } + if (ImGui::MenuItem("clear map samples")) { + for (int i=0; i<120; i++) { + ins->amiga.noteMap[i].map=-1; + } + } + ImGui::EndPopup(); + } ImGui::EndTabItem(); + } else { + sampleMapFocused=false; } } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem(settings.c163Name.c_str())) { diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 8fdafeb9b..735f8bfb0 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -34,6 +34,8 @@ void FurnaceGUI::drawNewSong() { avail.y-=ImGui::GetFrameHeightWithSpacing(); if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { + if (newSongFirstFrame) + ImGui::SetKeyboardFocusHere(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputTextWithHint("##SysSearch","Search...",&newSongQuery)) { String lowerCase=newSongQuery; @@ -159,4 +161,6 @@ void FurnaceGUI::drawNewSong() { updateWindowTitle(); ImGui::CloseCurrentPopup(); } + + newSongFirstFrame=false; } diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index f04587c8b..e5bc09e4b 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -221,8 +221,9 @@ void FurnaceGUI::drawOsc() { waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); } if (settings.oscEscapesBoundary) { - ImDrawList* dlf=ImGui::GetForegroundDrawList(); - dlf->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); + dl->PushClipRectFullScreen(); + dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); + dl->PopClipRect(); } else { dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); } diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index fe451c3f7..baae316d1 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -415,10 +415,14 @@ void FurnaceGUI::drawPiano() { e->previewSample(curSample,note); break; default: - e->synchronized([this,note]() { - e->autoNoteOn(-1,curIns,note); - }); - if (edit && curWindow!=GUI_WINDOW_INS_LIST && curWindow!=GUI_WINDOW_INS_EDIT) noteInput(note,0); + if (sampleMapWaitingInput) { + alterSampleMap(true,note); + } else { + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); + if (edit && curWindow!=GUI_WINDOW_INS_LIST && curWindow!=GUI_WINDOW_INS_EDIT) noteInput(note,0); + } break; } }