diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 6a58b86de..98b518456 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3598,6 +3598,73 @@ void DivEngine::noteOff(int chan) { BUSY_END; } +int DivEngine::getViableChannel(int chan, int off, int ins) { + // if the offset is zero, we don't have to do anything + if (off==0) return chan; + + // if there isn't an instrument, just offset chan by off + if (ins==-1) { + return (chan+off)%chans; + } + + bool isViable[DIV_MAX_CHANS]; + bool isAtLeastOneViable=false; + int finalChan=chan; + int finalChanType=getChannelType(finalChan); + + // this is a copy of the routine in autoNoteOn...... I am lazy + DivInstrument* insInst=getIns(ins); + for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) { + if (insInst->type==DIV_INS_OPL) { + if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { + isViable[i]=true; + isAtLeastOneViable=true; + } else { + isViable[i]=false; + } + } else { + isViable[i]=true; + isAtLeastOneViable=true; + } + } else { + isViable[i]=false; + } + } + + // screw it if none of the channels are viable + if (!isAtLeastOneViable) { + return (chan+off)%chans; + } + + // now offset (confined to viable channels) + int channelsCycled=0; + int i=(chan+1)%chans; + int attempts=0; + while (true) { + if (isViable[i]) { + channelsCycled++; + if (channelsCycled==off) { + // we found it + return i; + } + } + + if (++i>=chans) { + i=0; + } + + // fail-safe + if (++attempts>1024) { + logE("getViableChannel(): too many attempts!"); + break; + } + } + + // fail-safe + return (chan+off)%chans; +} + bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { bool isViable[DIV_MAX_CHANS]; bool canPlayAnyway=false; diff --git a/src/engine/engine.h b/src/engine/engine.h index d272e8c14..81698fdf6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -1202,6 +1202,10 @@ class DivEngine { // set whether autoNoteIn is mono or poly void setAutoNotePoly(bool poly); + // get next viable channel with an offset + // chan is the base channel, off is the offset and ins is the instrument. + int getViableChannel(int chan, int off, int ins); + // go to order void setOrder(unsigned char order); diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 40e18565b..d0c0fd833 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -759,8 +759,18 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); pushToggleColors(noteInputPoly); - if (ImGui::Button(noteInputPoly?(_("Poly##PolyInput")):(_("Mono##PolyInput")))) { - noteInputPoly=!noteInputPoly; + if (ImGui::Button(noteInputPoly?(noteInputChord?(_("Chord##PolyInput")):(_("Poly##PolyInput"))):(_("Mono##PolyInput")))) { + if (noteInputPoly) { + if (noteInputChord) { + noteInputPoly=false; + noteInputChord=false; + } else { + noteInputChord=true; + } + } else { + noteInputPoly=true; + noteInputChord=false; + } e->setAutoNotePoly(noteInputPoly); } if (ImGui::IsItemHovered()) { @@ -889,8 +899,18 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); pushToggleColors(noteInputPoly); - if (ImGui::Button(noteInputPoly?_("Poly##PolyInput"):_("Mono##PolyInput"))) { - noteInputPoly=!noteInputPoly; + if (ImGui::Button(noteInputPoly?(noteInputChord?(_("Chord##PolyInput")):(_("Poly##PolyInput"))):(_("Mono##PolyInput")))) { + if (noteInputPoly) { + if (noteInputChord) { + noteInputPoly=false; + noteInputChord=false; + } else { + noteInputChord=true; + } + } else { + noteInputPoly=true; + noteInputChord=false; + } e->setAutoNotePoly(noteInputPoly); } if (ImGui::IsItemHovered()) { @@ -1027,8 +1047,18 @@ void FurnaceGUI::drawEditControls() { popToggleColors(); pushToggleColors(noteInputPoly); - if (ImGui::Button(noteInputPoly?_("Poly##PolyInput"):_("Mono##PolyInput"))) { - noteInputPoly=!noteInputPoly; + if (ImGui::Button(noteInputPoly?(noteInputChord?(_("Chord##PolyInput")):(_("Poly##PolyInput"))):(_("Mono##PolyInput")))) { + if (noteInputPoly) { + if (noteInputChord) { + noteInputPoly=false; + noteInputChord=false; + } else { + noteInputChord=true; + } + } else { + noteInputPoly=true; + noteInputChord=false; + } e->setAutoNotePoly(noteInputPoly); } if (ImGui::IsItemHovered()) { @@ -1127,8 +1157,18 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); pushToggleColors(noteInputPoly); - if (ImGui::Button(noteInputPoly?_("Poly##PolyInput"):_("Mono##PolyInput"))) { - noteInputPoly=!noteInputPoly; + if (ImGui::Button(noteInputPoly?(noteInputChord?(_("Chord##PolyInput")):(_("Poly##PolyInput"))):(_("Mono##PolyInput")))) { + if (noteInputPoly) { + if (noteInputChord) { + noteInputPoly=false; + noteInputChord=false; + } else { + noteInputChord=true; + } + } else { + noteInputPoly=true; + noteInputChord=false; + } e->setAutoNotePoly(noteInputPoly); } if (ImGui::IsItemHovered()) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8cf76bfa7..302b07dfa 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1296,6 +1296,7 @@ void FurnaceGUI::play(int row) { } curNibble=false; orderNibble=false; + chordInputOffset=0; activeNotes.clear(); } @@ -1311,6 +1312,7 @@ void FurnaceGUI::stop() { e->stop(); curNibble=false; orderNibble=false; + chordInputOffset=0; if (followPattern && wasPlaying) { nextScroll=-1.0f; nextAddScroll=0.0f; @@ -1353,13 +1355,24 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { } } -void FurnaceGUI::noteInput(int num, int key, int vol) { +void FurnaceGUI::noteInput(int num, int key, int vol, int chanOff) { int ch=cursor.xCoarse; int ord=curOrder; int y=cursor.y; int tick=0; int speed=0; + if (chanOff>0 && noteInputChord) { + ch=e->getViableChannel(ch,chanOff,curIns); + if ((!e->isPlaying() || !followPattern)) { + y-=editStep; + while (y<0) { + if (--ord<0) ord=0; + y+=e->curSubSong->patLen; + } + } + } + if (e->isPlaying() && !e->isStepping() && followPattern) { e->getPlayPosTick(ord,y,tick,speed); if (tick<=(speed/2)) { // round @@ -1373,12 +1386,12 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { } } - logV("noteInput: chan %d, %d:%d %d/%d",ch,ord,y,tick,speed); + logV("noteInput: chan %d, offset %d, %d:%d %d/%d",ch,chanOff,ord,y,tick,speed); DivPattern* pat=e->curPat[ch].getPattern(e->curOrders->ord[ch][ord],true); bool removeIns=false; - prepareUndo(GUI_UNDO_PATTERN_EDIT); + prepareUndo(GUI_UNDO_PATTERN_EDIT,UndoRegion(ord,ch,y,ord,ch,y)); if (key==GUI_NOTE_OFF) { // note off pat->newData[y][DIV_PAT_NOTE]=DIV_NOTE_OFF; @@ -1416,8 +1429,10 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { pat->newData[y][DIV_PAT_VOL]=-1; } } - editAdvance(); - makeUndo(GUI_UNDO_PATTERN_EDIT); + if ((!e->isPlaying() || !followPattern) && (chanOff<1 || !noteInputChord)) { + editAdvance(); + } + makeUndo(GUI_UNDO_PATTERN_EDIT,UndoRegion(ord,ch,y,ord,ch,y)); curNibble=false; } @@ -1736,8 +1751,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (num>119) num=119; // B-9 if (edit) { - noteInput(num,key); + noteInput(num,key,-1,chordInputOffset); } + chordInputOffset++; } } else if (edit) { // value auto it=valueKeys.find(ev.key.keysym.sym); @@ -1833,7 +1849,10 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } void FurnaceGUI::keyUp(SDL_Event& ev) { - // nothing for now + // this is very, very lazy... + if (--chordInputOffset<0) { + chordInputOffset=0; + } } bool dirExists(String s) { @@ -3995,6 +4014,9 @@ bool FurnaceGUI::loop() { break; case SDL_KEYUP: // for now + if (!ImGui::GetIO().WantCaptureKeyboard || (newFilePicker->isOpened() && !ImGui::GetIO().WantTextInput)) { + keyUp(ev); + } insEditMayBeDirty=true; if (introPos<11.0 && introSkip<0.5 && !shortIntro) { introSkipDo=false; @@ -4178,14 +4200,19 @@ bool FurnaceGUI::loop() { if (action!=0) { doAction(action); } else switch (msg.type&0xf0) { + case TA_MIDI_NOTE_OFF: + if (--chordInputOffset<0) chordInputOffset=0; + break; case TA_MIDI_NOTE_ON: if (midiMap.valueInputStyle==0 || midiMap.valueInputStyle>3 || cursor.xFine==0) { if (midiMap.noteInput && edit && msg.data[1]!=0) { noteInput( msg.data[0]-12, 0, - midiMap.volInput?msg.data[1]:-1 + midiMap.volInput?msg.data[1]:-1, + chordInputOffset ); + chordInputOffset++; } } else { if (edit && msg.data[1]!=0) { @@ -7423,6 +7450,9 @@ bool FurnaceGUI::loop() { break; } } + + // reset chord count just in case + chordInputOffset=0; } if (!settings.renderClearPos || renderBackend==GUI_BACKEND_METAL) { @@ -8254,6 +8284,7 @@ void FurnaceGUI::syncState() { followOrders=e->getConfBool("followOrders",true); followPattern=e->getConfBool("followPattern",true); noteInputPoly=e->getConfBool("noteInputPoly",true); + noteInputChord=e->getConfBool("noteInputChord",false); filePlayerSync=e->getConfBool("filePlayerSync",true); audioExportOptions.loops=e->getConfInt("exportLoops",0); if (audioExportOptions.loops<0) audioExportOptions.loops=0; @@ -8414,6 +8445,7 @@ void FurnaceGUI::commitState(DivConfig& conf) { conf.set("followPattern",followPattern); conf.set("orderEditMode",orderEditMode); conf.set("noteInputPoly",noteInputPoly); + conf.set("noteInputChord",noteInputChord); conf.set("filePlayerSync",filePlayerSync); if (settings.persistFadeOut) { conf.set("exportLoops",audioExportOptions.loops); @@ -8588,6 +8620,7 @@ FurnaceGUI::FurnaceGUI(): sysDupCloneChannels(true), sysDupEnd(false), noteInputPoly(true), + noteInputChord(false), notifyWaveChange(false), notifySampleChange(false), recalcTimestamps(true), @@ -8623,6 +8656,7 @@ FurnaceGUI::FurnaceGUI(): drawHalt(10), macroPointSize(16), waveEditStyle(0), + chordInputOffset(0), displayInsTypeListMakeInsSample(-1), makeDrumkitOctave(3), mobileEditPage(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index 77df3318c..f9ebdac2f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1713,7 +1713,8 @@ class FurnaceGUI { bool vgmExportDirectStream, displayInsTypeList, displayWaveSizeList; bool portrait, injectBackUp, mobileMenuOpen, warnColorPushed; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; - bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd, noteInputPoly, notifyWaveChange, notifySampleChange; + bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd, noteInputPoly, noteInputChord; + bool notifyWaveChange, notifySampleChange; bool recalcTimestamps; bool wantScrollListIns, wantScrollListWave, wantScrollListSample; bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString; @@ -1737,6 +1738,7 @@ class FurnaceGUI { int drawHalt; int macroPointSize; int waveEditStyle; + int chordInputOffset; int displayInsTypeListMakeInsSample; int makeDrumkitOctave; int mobileEditPage; @@ -3080,7 +3082,7 @@ class FurnaceGUI { void doDrag(bool copy=false); void editOptions(bool topMenu); DivSystem systemPicker(bool fullWidth); - void noteInput(int num, int key, int vol=-1); + void noteInput(int num, int key, int vol=-1, int chanOff=0); void valueInput(int num, bool direct=false, int target=-1); void orderInput(int num);