Merge branch 'master' into spectrum

This commit is contained in:
Eknous-P 2025-11-01 17:19:30 +04:00
commit 4f85dec801
23 changed files with 488 additions and 124 deletions

View file

@ -3665,7 +3665,7 @@ int DivEngine::getViableChannel(int chan, int off, int ins) {
return (chan+off)%chans;
}
bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol, int transpose) {
bool isViable[DIV_MAX_CHANS];
bool canPlayAnyway=false;
bool notInViableChannel=false;
@ -3708,7 +3708,7 @@ bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
if ((!midiPoly) || (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel))) {
chan[finalChan].midiNote=note;
chan[finalChan].midiAge=midiAgeCounter++;
pendingNotes.push_back(DivNoteEvent(finalChan,ins,note,vol,true));
pendingNotes.push_back(DivNoteEvent(finalChan,ins,note+transpose,vol,true));
return true;
}
if (++finalChan>=chans) {
@ -3729,7 +3729,7 @@ bool DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
chan[candidate].midiNote=note;
chan[candidate].midiAge=midiAgeCounter++;
pendingNotes.push_back(DivNoteEvent(candidate,ins,note,vol,true));
pendingNotes.push_back(DivNoteEvent(candidate,ins,note+transpose,vol,true));
return true;
}

View file

@ -1195,7 +1195,7 @@ class DivEngine {
void noteOff(int chan);
// returns whether it could
bool autoNoteOn(int chan, int ins, int note, int vol=-1);
bool autoNoteOn(int chan, int ins, int note, int vol=-1, int transpose=0);
void autoNoteOff(int chan, int note, int vol=-1);
void autoNoteOffAll();

View file

@ -84,6 +84,15 @@ void DivChannelData::wipePatterns() {
}
}
bool DivPattern::isEmpty() {
for (int i=0; i<DIV_MAX_ROWS; i++) {
for (int j=0; j<DIV_MAX_COLS; j++) {
if (newData[i][j]!=-1) return false;
}
}
return true;
}
void DivPattern::copyOn(DivPattern* dest) {
dest->name=name;
memcpy(dest->newData,newData,sizeof(newData));

View file

@ -39,6 +39,12 @@ struct DivPattern {
*/
short newData[DIV_MAX_ROWS][DIV_MAX_COLS];
/**
* check whether this pattern is empty.
* @return whether it is.
*/
bool isEmpty();
/**
* clear the pattern.
*/

View file

@ -367,7 +367,7 @@ void FurnaceGUI::drawPalette() {
openRecentFile(recentFile[i]);
break;
case CMDPAL_TYPE_INSTRUMENTS:
curIns=i-1;
setCurIns(i-1);
break;
case CMDPAL_TYPE_SAMPLES:
curSample=i;

View file

@ -150,10 +150,42 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) {
} else {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
}
bool insReleased=ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i));
bool insSelected=(curIns==i);
int multiInsColor=-1;
if (i==-1) {
insSelected=(curIns<0 || curIns>=e->song.insLen);
} else {
for (int j=0; j<7; j++) {
if (multiIns[j]==i) {
insSelected=true;
multiInsColor=j;
break;
}
}
}
// push multi ins colors if necessary
if (multiInsColor>=0) {
ImVec4 colorActive=uiColors[GUI_COLOR_MULTI_INS_1+multiInsColor];
ImVec4 colorHover=ImVec4(colorActive.x,colorActive.y,colorActive.z,colorActive.w*0.5);
ImVec4 color=ImVec4(colorActive.x,colorActive.y,colorActive.z,colorActive.w*0.25);
ImGui::PushStyleColor(ImGuiCol_Header,color);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,colorHover);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,colorActive);
}
bool insReleased=ImGui::Selectable(name.c_str(),insSelected);
if (multiInsColor>=0) {
ImGui::PopStyleColor(3);
}
bool insPressed=ImGui::IsItemActivated();
if (insReleased || (!insListDir && insPressed && !settings.draggableDataView)) {
curIns=i;
if (mobileMultiInsToggle || ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
setMultiIns(i);
} else {
setCurIns(i);
}
if (!insReleased || insListDir || settings.draggableDataView) {
wavePreviewInit=true;
updateFMPreview=true;
@ -181,7 +213,7 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) {
}
if (ImGui::BeginPopupContextItem("InsRightMenu")) {
curIns=i;
setCurIns(i);
updateFMPreview=true;
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
if (ImGui::MenuItem(_("edit"))) {
@ -732,6 +764,16 @@ void FurnaceGUI::drawInsList(bool asChild) {
}
}
if (mobileUI) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
pushToggleColors(mobileMultiInsToggle);
if (ImGui::SmallButton("Select multiple")) {
mobileMultiInsToggle=!mobileMultiInsToggle;
}
popToggleColors();
}
if (settings.unifiedDataView) {
ImGui::Unindent();

View file

@ -143,16 +143,18 @@ void FurnaceGUI::doAction(int what) {
}
break;
case GUI_ACTION_INS_UP:
if (--curIns<-1) {
curIns=-1;
setCurIns(curIns-1);
if (curIns<-1) {
setCurIns(-1);
}
wavePreviewInit=true;
wantScrollListIns=true;
updateFMPreview=true;
break;
case GUI_ACTION_INS_DOWN:
if (++curIns>=(int)e->song.ins.size()) {
curIns=((int)e->song.ins.size())-1;
setCurIns(curIns+1);
if (curIns>=(int)e->song.ins.size()) {
setCurIns(((int)e->song.ins.size())-1);
}
wavePreviewInit=true;
wantScrollListIns=true;
@ -366,6 +368,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_WINDOW_REF_PLAYER:
nextWindow=GUI_WINDOW_REF_PLAYER;
break;
case GUI_ACTION_WINDOW_MULTI_INS_SETUP:
nextWindow=GUI_WINDOW_MULTI_INS_SETUP;
break;
case GUI_ACTION_COLLAPSE_WINDOW:
collapseWindow=true;
@ -477,6 +482,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_WINDOW_REF_PLAYER:
refPlayerOpen=false;
break;
case GUI_WINDOW_MULTI_INS_SETUP:
multiInsSetupOpen=false;
break;
case GUI_WINDOW_TUNER:
tunerOpen=false;
break;
@ -751,7 +759,7 @@ void FurnaceGUI::doAction(int what) {
break;
}
}
curIns=e->addInstrument(cursor.xCoarse);
setCurIns(e->addInstrument(cursor.xCoarse));
if (curIns==-1) {
showError(_("too many instruments!"));
} else {
@ -780,7 +788,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_INS_LIST_DUPLICATE:
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
int prevIns=curIns;
curIns=e->addInstrument(cursor.xCoarse);
setCurIns(e->addInstrument(cursor.xCoarse));
if (curIns==-1) {
showError(_("too many instruments!"));
} else {
@ -832,13 +840,15 @@ void FurnaceGUI::doAction(int what) {
insEditOpen=true;
break;
case GUI_ACTION_INS_LIST_UP:
if (--curIns<0) curIns=0;
setCurIns(curIns-1);
if (curIns<0) setCurIns(0);
wantScrollListIns=true;
wavePreviewInit=true;
updateFMPreview=true;
break;
case GUI_ACTION_INS_LIST_DOWN:
if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1;
setCurIns(curIns+1);
if (curIns>=(int)e->song.ins.size()) setCurIns(((int)e->song.ins.size())-1);
wantScrollListIns=true;
wavePreviewInit=true;
updateFMPreview=true;
@ -1727,7 +1737,7 @@ void FurnaceGUI::doAction(int what) {
}
DivSample* sample=e->song.sample[curSample];
curIns=e->addInstrument(cursor.xCoarse);
setCurIns(e->addInstrument(cursor.xCoarse));
if (curIns==-1) {
showError(_("too many instruments!"));
} else {

View file

@ -169,6 +169,20 @@ const bool mobileButtonPersist[32]={
false,
};
const char* noteInputModes[4]={
_N("Mono##PolyInput"),
_N("Poly##PolyInput"),
_N("Chord##PolyInput"),
// unused
_N("Of fuckin' course!##PolyInput")
};
#define CHANGE_NOTE_INPUT_MODE \
noteInputMode++; \
if (noteInputMode>GUI_NOTE_INPUT_CHORD) noteInputMode=GUI_NOTE_INPUT_MONO; \
if (noteInputMode==GUI_NOTE_INPUT_MONO) memset(multiIns,-1,7*sizeof(int)); \
e->setAutoNotePoly(noteInputMode!=GUI_NOTE_INPUT_MONO);
void FurnaceGUI::drawMobileControls() {
float timeScale=60.0*ImGui::GetIO().DeltaTime;
if (dragMobileMenu) {
@ -758,20 +772,9 @@ void FurnaceGUI::drawEditControls() {
}
ImGui::SameLine();
pushToggleColors(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);
pushToggleColors(noteInputMode!=GUI_NOTE_INPUT_MONO);
if (ImGui::Button(_(noteInputModes[noteInputMode&3]))) {
CHANGE_NOTE_INPUT_MODE;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Polyphony"));
@ -898,20 +901,9 @@ void FurnaceGUI::drawEditControls() {
unimportant(ImGui::Checkbox(_("Pattern"),&followPattern));
ImGui::SameLine();
pushToggleColors(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);
pushToggleColors(noteInputMode!=GUI_NOTE_INPUT_MONO);
if (ImGui::Button(_(noteInputModes[noteInputMode&3]))) {
CHANGE_NOTE_INPUT_MODE;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Polyphony"));
@ -1046,20 +1038,9 @@ void FurnaceGUI::drawEditControls() {
}
popToggleColors();
pushToggleColors(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);
pushToggleColors(noteInputMode!=GUI_NOTE_INPUT_MONO);
if (ImGui::Button(_(noteInputModes[noteInputMode&3]))) {
CHANGE_NOTE_INPUT_MODE;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Polyphony"));
@ -1156,20 +1137,9 @@ void FurnaceGUI::drawEditControls() {
popToggleColors();
ImGui::SameLine();
pushToggleColors(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);
pushToggleColors(noteInputMode!=GUI_NOTE_INPUT_MONO);
if (ImGui::Button(_(noteInputModes[noteInputMode&3]))) {
CHANGE_NOTE_INPUT_MODE;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Polyphony"));

View file

@ -1912,7 +1912,7 @@ void FurnaceGUI::doAbsorbInstrument() {
// absorb most recent instrument
if (!foundIns && pat->newData[i][DIV_PAT_INS] >= 0) {
foundIns=true;
curIns=pat->newData[i][DIV_PAT_INS];
setCurIns(pat->newData[i][DIV_PAT_INS]);
}
// absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of
@ -1932,7 +1932,7 @@ void FurnaceGUI::doAbsorbInstrument() {
}
// if no instrument has been set at this point, the only way to match it is to use "none"
if (!foundIns) curIns=-1;
if (!foundIns) setCurIns(-1);
logD("doAbsorbInstrument -- searched %d orders", curOrder-orderIdx);
}

View file

@ -868,6 +868,39 @@ void FurnaceGUI::autoDetectSystem() {
}
}
void FurnaceGUI::setCurIns(int newIns) {
curIns=newIns;
memset(multiIns,-1,7*sizeof(int));
}
bool FurnaceGUI::setMultiIns(int newIns) {
// set primary instrument if not set (or if polyphony is mono)
if (curIns<0 || noteInputMode==GUI_NOTE_INPUT_MONO) {
setCurIns(newIns);
return true;
}
// don't allow using the primary instrument twice
if (curIns==newIns) return false;
for (int i=0; i<7; i++) {
// don't allow using an instrument more than once
if (multiIns[i]==newIns) return false;
// if slot is empty, assign instrument to it
if (multiIns[i]==-1) {
multiIns[i]=newIns;
return true;
}
}
// no more slots available
return false;
}
bool FurnaceGUI::isMultiInsActive() {
for (int i=0; i<7; i++) {
if (multiIns[i]>=0) return true;
}
return false;
}
void FurnaceGUI::updateROMExportAvail() {
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
romExportExists=false;
@ -1338,6 +1371,11 @@ void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) {
e->setMidiBaseChan(refChan);
e->synchronized([this,note]() {
if (!e->autoNoteOn(-1,curIns,note)) failedNoteOn=true;
for (int mi=0; mi<7; mi++) {
if (multiIns[mi]!=-1) {
e->autoNoteOn(-1,multiIns[mi],note,-1,multiInsTranspose[mi]);
}
}
});
}
@ -1367,7 +1405,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol, int chanOff) {
int tick=0;
int speed=0;
if (chanOff>0 && noteInputChord) {
if (chanOff>0 && noteInputMode==GUI_NOTE_INPUT_CHORD) {
ch=e->getViableChannel(ch,chanOff,curIns);
if ((!e->isPlaying() || !followPattern)) {
y-=editStep;
@ -1434,7 +1472,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol, int chanOff) {
pat->newData[y][DIV_PAT_VOL]=-1;
}
}
if ((!e->isPlaying() || !followPattern) && (chanOff<1 || !noteInputChord)) {
if ((!e->isPlaying() || !followPattern) && (chanOff<1 || noteInputMode!=GUI_NOTE_INPUT_CHORD)) {
editAdvance();
}
makeUndo(GUI_UNDO_PATTERN_EDIT,UndoRegion(ord,ch,y,ord,ch,y));
@ -1474,7 +1512,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
}
}
if (settings.absorbInsInput) {
curIns=pat->newData[y][target];
setCurIns(pat->newData[y][target]);
wavePreviewInit=true;
updateFMPreview=true;
}
@ -1854,10 +1892,8 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
}
void FurnaceGUI::keyUp(SDL_Event& ev) {
// this is very, very lazy...
if (--chordInputOffset<0) {
chordInputOffset=0;
}
// this is very, very lazy... but it works.
chordInputOffset=0;
}
bool dirExists(String s) {
@ -1982,7 +2018,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (curIns!=-2) {
prevIns=curIns;
}
curIns=-2;
setCurIns(-2);
}
}
for (DivInstrument* i: instruments) delete i;
@ -3865,6 +3901,7 @@ bool FurnaceGUI::loop() {
DECLARE_METRIC(effectList)
DECLARE_METRIC(userPresets)
DECLARE_METRIC(refPlayer)
DECLARE_METRIC(multiInsSetup)
DECLARE_METRIC(popup)
#ifdef IS_MOBILE
@ -4052,7 +4089,7 @@ bool FurnaceGUI::loop() {
instrumentCount=e->addInstrumentPtr(i);
}
if (instrumentCount>=0 && settings.selectAssetOnLoad) {
curIns=instrumentCount-1;
setCurIns(instrumentCount-1);
}
nextWindow=GUI_WINDOW_INS_LIST;
MARK_MODIFIED;
@ -4208,7 +4245,7 @@ bool FurnaceGUI::loop() {
doAction(action);
} else switch (msg.type&0xf0) {
case TA_MIDI_NOTE_OFF:
if (--chordInputOffset<0) chordInputOffset=0;
chordInputOffset=0;
break;
case TA_MIDI_NOTE_ON:
if (midiMap.valueInputStyle==0 || midiMap.valueInputStyle>3 || cursor.xFine==0) {
@ -4246,8 +4283,8 @@ bool FurnaceGUI::loop() {
break;
case TA_MIDI_PROGRAM:
if (midiMap.programChange && !(midiMap.directChannel && midiMap.directProgram)) {
curIns=msg.data[0];
if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1;
setCurIns(msg.data[0]);
if (curIns>=(int)e->song.ins.size()) setCurIns(e->song.ins.size()-1);
wavePreviewInit=true;
updateFMPreview=true;
}
@ -4489,6 +4526,7 @@ bool FurnaceGUI::loop() {
IMPORT_CLOSE(csPlayerOpen);
IMPORT_CLOSE(userPresetsOpen);
IMPORT_CLOSE(refPlayerOpen);
IMPORT_CLOSE(multiInsSetupOpen);
} else if (pendingLayoutImportStep==1) {
// let the UI settle
} else if (pendingLayoutImportStep==2) {
@ -4863,6 +4901,7 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem(_("play/edit controls"),BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen;
if (ImGui::MenuItem(_("piano/input pad"),BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen;
if (ImGui::MenuItem(_("reference music player"),BIND_FOR(GUI_ACTION_WINDOW_REF_PLAYER),refPlayerOpen)) refPlayerOpen=!refPlayerOpen;
if (ImGui::MenuItem(_("multi-ins setup"),BIND_FOR(GUI_ACTION_WINDOW_MULTI_INS_SETUP),multiInsSetupOpen)) multiInsSetupOpen=!multiInsSetupOpen;
if (spoilerOpen) if (ImGui::MenuItem(_("spoiler"),NULL,spoilerOpen)) spoilerOpen=!spoilerOpen;
ImGui::EndMenu();
@ -5063,6 +5102,7 @@ bool FurnaceGUI::loop() {
MEASURE(effectList,drawEffectList());
MEASURE(userPresets,drawUserPresets());
MEASURE(refPlayer,drawRefPlayer());
MEASURE(multiInsSetup,drawMultiInsSetup());
MEASURE(patManager,drawPatManager());
} else {
@ -5111,6 +5151,7 @@ bool FurnaceGUI::loop() {
MEASURE(effectList,drawEffectList());
MEASURE(userPresets,drawUserPresets());
MEASURE(refPlayer,drawRefPlayer());
MEASURE(multiInsSetup,drawMultiInsSetup());
}
@ -5180,7 +5221,7 @@ bool FurnaceGUI::loop() {
}
}
} else {
curIns=prevIns;
setCurIns(prevIns);
wavePreviewInit=true;
updateFMPreview=true;
}
@ -5686,7 +5727,7 @@ bool FurnaceGUI::loop() {
MARK_MODIFIED;
}
if (instrumentCount>=0 && settings.selectAssetOnLoad) {
curIns=instrumentCount-1;
setCurIns(instrumentCount-1);
}
}
}
@ -6563,7 +6604,7 @@ bool FurnaceGUI::loop() {
e->lockEngine([this]() {
e->song.clearInstruments();
});
curIns=-1;
setCurIns(-1);
MARK_MODIFIED;
ImGui::CloseCurrentPopup();
}
@ -6768,7 +6809,7 @@ bool FurnaceGUI::loop() {
strncpy(temp,insTypes[i][0],1023);
if (ImGui::MenuItem(temp)) {
// create ins
curIns=e->addInstrument(-1,i);
setCurIns(e->addInstrument(-1,i));
if (curIns==-1) {
showError(_("too many instruments!"));
} else {
@ -7628,7 +7669,7 @@ bool FurnaceGUI::init() {
initSystemPresets();
e->setAutoNotePoly(noteInputPoly);
e->setAutoNotePoly(noteInputMode!=GUI_NOTE_INPUT_MONO);
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1");
#if SDL_VERSION_ATLEAST(2,0,17)
@ -8264,6 +8305,7 @@ void FurnaceGUI::syncState() {
spoilerOpen=e->getConfBool("spoilerOpen",false);
userPresetsOpen=e->getConfBool("userPresetsOpen",false);
refPlayerOpen=e->getConfBool("refPlayerOpen",false);
multiInsSetupOpen=e->getConfBool("multiInsSetupOpen",false);
insListDir=e->getConfBool("insListDir",false);
waveListDir=e->getConfBool("waveListDir",false);
@ -8298,8 +8340,10 @@ void FurnaceGUI::syncState() {
orderLock=e->getConfBool("orderLock",false);
followOrders=e->getConfBool("followOrders",true);
followPattern=e->getConfBool("followPattern",true);
noteInputPoly=e->getConfBool("noteInputPoly",true);
noteInputChord=e->getConfBool("noteInputChord",false);
noteInputMode=e->getConfInt("noteInputMode",GUI_NOTE_INPUT_POLY);
if (noteInputMode!=GUI_NOTE_INPUT_MONO && noteInputMode!=GUI_NOTE_INPUT_POLY && noteInputMode!=GUI_NOTE_INPUT_CHORD) {
noteInputMode=GUI_NOTE_INPUT_POLY;
}
filePlayerSync=e->getConfBool("filePlayerSync",true);
audioExportOptions.loops=e->getConfInt("exportLoops",0);
if (audioExportOptions.loops<0) audioExportOptions.loops=0;
@ -8329,6 +8373,7 @@ void FurnaceGUI::syncState() {
pianoOffsetEdit=e->getConfInt("pianoOffsetEdit",pianoOffsetEdit);
pianoView=e->getConfInt("pianoView",pianoView);
pianoInputPadMode=e->getConfInt("pianoInputPadMode",pianoInputPadMode);
pianoLabelsMode=e->getConfInt("pianoLabelsMode",pianoLabelsMode);
chanOscCols=e->getConfInt("chanOscCols",3);
chanOscAutoColsType=e->getConfInt("chanOscAutoColsType",0);
@ -8437,6 +8482,7 @@ void FurnaceGUI::commitState(DivConfig& conf) {
conf.set("spoilerOpen",spoilerOpen);
conf.set("userPresetsOpen",userPresetsOpen);
conf.set("refPlayerOpen",refPlayerOpen);
conf.set("multiInsSetupOpen",multiInsSetupOpen);
// commit dir state
conf.set("insListDir",insListDir);
@ -8467,8 +8513,7 @@ void FurnaceGUI::commitState(DivConfig& conf) {
conf.set("followOrders",followOrders);
conf.set("followPattern",followPattern);
conf.set("orderEditMode",orderEditMode);
conf.set("noteInputPoly",noteInputPoly);
conf.set("noteInputChord",noteInputChord);
conf.set("noteInputMode",(int)noteInputMode);
conf.set("filePlayerSync",filePlayerSync);
if (settings.persistFadeOut) {
conf.set("exportLoops",audioExportOptions.loops);
@ -8498,6 +8543,7 @@ void FurnaceGUI::commitState(DivConfig& conf) {
conf.set("pianoOffsetEdit",pianoOffsetEdit);
conf.set("pianoView",pianoView);
conf.set("pianoInputPadMode",pianoInputPadMode);
conf.set("pianoLabelsMode",pianoLabelsMode);
// commit per-chan osc state
conf.set("chanOscCols",chanOscCols);
@ -8677,8 +8723,7 @@ FurnaceGUI::FurnaceGUI():
preserveChanPos(false),
sysDupCloneChannels(true),
sysDupEnd(false),
noteInputPoly(true),
noteInputChord(false),
noteInputMode(GUI_NOTE_INPUT_POLY),
notifyWaveChange(false),
notifySampleChange(false),
recalcTimestamps(true),
@ -8827,6 +8872,7 @@ FurnaceGUI::FurnaceGUI():
curPaletteChoice(0),
curPaletteType(0),
soloTimeout(0.0f),
mobileMultiInsToggle(false),
purgeYear(2021),
purgeMonth(4),
purgeDay(4),
@ -8877,6 +8923,7 @@ FurnaceGUI::FurnaceGUI():
cvOpen(false),
userPresetsOpen(false),
refPlayerOpen(false),
multiInsSetupOpen(false),
cvNotSerious(false),
shortIntro(false),
insListDir(false),
@ -9178,6 +9225,7 @@ FurnaceGUI::FurnaceGUI():
pianoOffsetEdit(9),
pianoView(PIANO_LAYOUT_AUTOMATIC),
pianoInputPadMode(PIANO_INPUT_PAD_SPLIT_AUTO),
pianoLabelsMode(PIANO_LABELS_OCTAVE),
#else
pianoOctaves(7),
pianoOctavesEdit(4),
@ -9188,6 +9236,7 @@ FurnaceGUI::FurnaceGUI():
pianoOffsetEdit(6),
pianoView(PIANO_LAYOUT_STANDARD),
pianoInputPadMode(PIANO_INPUT_PAD_DISABLE),
pianoLabelsMode(PIANO_LABELS_OCTAVE),
#endif
hasACED(false),
waveGenBaseShape(0),
@ -9345,6 +9394,9 @@ FurnaceGUI::FurnaceGUI():
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
memset(multiIns,-1,7*sizeof(int));
memset(multiInsTranspose,0,7*sizeof(int));
strncpy(noteOffLabel,"OFF",32);
strncpy(noteRelLabel,"===",32);
strncpy(macroRelLabel,"REL",32);

View file

@ -309,6 +309,14 @@ enum FurnaceGUIColors {
GUI_COLOR_MACRO_ENVELOPE,
GUI_COLOR_MACRO_GLOBAL,
GUI_COLOR_MULTI_INS_1,
GUI_COLOR_MULTI_INS_2,
GUI_COLOR_MULTI_INS_3,
GUI_COLOR_MULTI_INS_4,
GUI_COLOR_MULTI_INS_5,
GUI_COLOR_MULTI_INS_6,
GUI_COLOR_MULTI_INS_7,
GUI_COLOR_INSTR_STD,
GUI_COLOR_INSTR_FM,
GUI_COLOR_INSTR_GB,
@ -574,6 +582,7 @@ enum FurnaceGUIWindows {
GUI_WINDOW_CS_PLAYER,
GUI_WINDOW_USER_PRESETS,
GUI_WINDOW_REF_PLAYER,
GUI_WINDOW_MULTI_INS_SETUP,
GUI_WINDOW_SPOILER
};
@ -782,6 +791,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WINDOW_CS_PLAYER,
GUI_ACTION_WINDOW_USER_PRESETS,
GUI_ACTION_WINDOW_REF_PLAYER,
GUI_ACTION_WINDOW_MULTI_INS_SETUP,
GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW,
@ -1671,6 +1681,12 @@ struct CSDisAsmIns {
}
};
enum NoteInputModes: unsigned char {
GUI_NOTE_INPUT_MONO=0,
GUI_NOTE_INPUT_POLY,
GUI_NOTE_INPUT_CHORD
};
struct FurnaceCV;
class FurnaceGUI {
@ -1721,7 +1737,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, noteInputChord;
bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd;
unsigned char noteInputMode;
bool notifyWaveChange, notifySampleChange;
bool recalcTimestamps;
bool wantScrollListIns, wantScrollListWave, wantScrollListSample;
@ -2386,13 +2403,19 @@ class FurnaceGUI {
int pendingLayoutImportStep;
FixedQueue<bool*,64> pendingLayoutImportReopen;
int curIns, curWave, curSample, curOctave, curOrder, playOrder, prevIns, oldRow, editStep, editStepCoarse, soloChan, orderEditMode, orderCursor;
// do not set curIns directly! use setCurIns() instead.
int curIns, curWave, curSample;
int curOctave, curOrder, playOrder, prevIns, oldRow, editStep, editStepCoarse, soloChan, orderEditMode, orderCursor;
int isClipping, newSongCategory, latchTarget, undoOrder;
int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragSourceOrder, dragDestinationX, dragDestinationXFine, dragDestinationY, dragDestinationOrder, oldBeat, oldBar;
int curGroove, exitDisabledTimer;
int curPaletteChoice, curPaletteType;
float soloTimeout;
int multiIns[7];
int multiInsTranspose[7];
bool mobileMultiInsToggle;
int purgeYear, purgeMonth, purgeDay;
bool patExtraButtons, patChannelNames, patChannelPairs;
@ -2405,6 +2428,7 @@ class FurnaceGUI {
bool pianoOpen, notesOpen, tunerOpen, spectrumOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen;
bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen;
bool groovesOpen, xyOscOpen, memoryOpen, csPlayerOpen, cvOpen, userPresetsOpen, refPlayerOpen;
bool multiInsSetupOpen;
bool cvNotSerious;
@ -2803,13 +2827,22 @@ class FurnaceGUI {
PIANO_INPUT_PAD_MAX
};
enum PianoLabelsMode {
PIANO_LABELS_OFF=0,
PIANO_LABELS_OCTAVE,
PIANO_LABELS_NOTE,
PIANO_LABELS_NOTE_C,
PIANO_LABELS_OCTAVE_C,
PIANO_LABELS_OCTAVE_NOTE
};
int pianoOctaves, pianoOctavesEdit;
bool pianoOptions, pianoSharePosition, pianoOptionsSet;
float pianoKeyHit[180];
bool pianoKeyPressed[180];
bool pianoReadonly;
int pianoOffset, pianoOffsetEdit;
int pianoView, pianoInputPadMode;
int pianoView, pianoInputPadMode, pianoLabelsMode;
// effect sorting / searching
bool effectsShow[10];
@ -2937,6 +2970,9 @@ class FurnaceGUI {
ImVec2 calcPortSetSize(String label, int ins, int outs);
bool portSet(String label, unsigned int portSetID, int ins, int outs, int activeIns, int activeOuts, int& clickedPort, std::map<unsigned int,ImVec2>& portPos);
// piano
void pianoLabel(ImDrawList* dl, ImVec2& p0, ImVec2& p1, int note);
void updateWindowTitle();
void updateROMExportAvail();
void autoDetectSystem();
@ -3045,6 +3081,7 @@ class FurnaceGUI {
void drawXYOsc();
void drawUserPresets();
void drawRefPlayer();
void drawMultiInsSetup();
float drawSystemChannelInfo(const DivSysDef* whichDef, int keyHitOffset=-1, float width=-1.0f);
void drawSystemChannelInfoText(const DivSysDef* whichDef);
@ -3200,6 +3237,10 @@ class FurnaceGUI {
const char* getSystemName(DivSystem which);
const char* getSystemPartNumber(DivSystem sys, DivConfig& flags);
void setCurIns(int newIns);
bool setMultiIns(int newIns);
bool isMultiInsActive();
public:
void editStr(String* which);
void showWarning(String what, FurnaceGUIWarnings type);

View file

@ -675,6 +675,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_CS_PLAYER", _N("Command Stream Player"), 0),
D("WINDOW_USER_PRESETS", _N("User Presets"), 0),
D("WINDOW_REF_PLAYER", _N("Reference Music Player"), 0),
D("MULTI_INS_SETUP", _N("Multi-Instrument Setup"), 0),
D("COLLAPSE_WINDOW", _N("Collapse/expand current window"), 0),
D("CLOSE_WINDOW", _N("Close current window"), FURKMOD_SHIFT|SDLK_ESCAPE),
@ -1023,6 +1024,14 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_MACRO_ENVELOPE,"",ImVec4(0.0f,1.0f,0.5f,1.0f)),
D(GUI_COLOR_MACRO_GLOBAL,"",ImVec4(1.0f,0.1f,0.1f,1.0f)),
D(GUI_COLOR_MULTI_INS_1,"",ImVec4(0.2f,1.0f,1.0f,1.0f)),
D(GUI_COLOR_MULTI_INS_2,"",ImVec4(0.2f,1.0f,0.2f,1.0f)),
D(GUI_COLOR_MULTI_INS_3,"",ImVec4(0.7f,1.0f,0.2f,1.0f)),
D(GUI_COLOR_MULTI_INS_4,"",ImVec4(1.0f,0.7f,0.2f,1.0f)),
D(GUI_COLOR_MULTI_INS_5,"",ImVec4(1.0f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_MULTI_INS_6,"",ImVec4(1.0f,0.2f,1.0f,1.0f)),
D(GUI_COLOR_MULTI_INS_7,"",ImVec4(0.4f,0.2f,1.0f,1.0f)),
D(GUI_COLOR_INSTR_STD,"",ImVec4(0.6f,1.0f,0.5f,1.0f)),
D(GUI_COLOR_INSTR_FM,"",ImVec4(0.6f,0.9f,1.0f,1.0f)),
D(GUI_COLOR_INSTR_GB,"",ImVec4(1.0f,1.0f,0.5f,1.0f)),

View file

@ -6649,10 +6649,10 @@ void FurnaceGUI::drawInsEdit() {
for (size_t i=0; i<e->song.ins.size(); i++) {
name=fmt::sprintf("%.2X: %s##_INSS%d",i,e->song.ins[i]->name,i);
if (ImGui::Selectable(name.c_str(),curIns==(int)i)) {
curIns=i;
setCurIns(i);
wavePreviewInit=true;
updateFMPreview=true;
ins = e->song.ins[curIns];
ins=e->song.ins[curIns];
}
}
ImGui::EndCombo();
@ -6700,7 +6700,7 @@ void FurnaceGUI::drawInsEdit() {
for (size_t i=0; i<e->song.ins.size(); i++) {
name=fmt::sprintf("%.2X: %s##_INSS%d",i,e->song.ins[i]->name,i);
if (ImGui::Selectable(name.c_str(),curIns==(int)i)) {
curIns=i;
setCurIns(i);
ins=e->song.ins[curIns];
wavePreviewInit=true;
updateFMPreview=true;

102
src/gui/multiInsSetup.cpp Normal file
View file

@ -0,0 +1,102 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "gui.h"
#include "imgui.h"
#include "IconsFontAwesome4.h"
void FurnaceGUI::drawMultiInsSetup() {
if (nextWindow==GUI_WINDOW_MULTI_INS_SETUP) {
multiInsSetupOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!multiInsSetupOpen && !isMultiInsActive()) return;
if (ImGui::Begin("Multi-Ins Setup",isMultiInsActive()?NULL:&multiInsSetupOpen,globalWinFlags,_("Multi-Ins Setup"))) {
if (ImGui::BeginTable("MultiInsSlots",8,ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableNextRow();
for (int i=0; i<8; i++) {
ImGui::TableNextColumn();
ImGui::Text("%d",i+1);
}
ImGui::TableNextRow();
for (int i=0; i<8; i++) {
String id;
int tr=(i==0)?0:multiInsTranspose[i-1];
bool thisInsOn=(i==0)?true:(multiIns[i-1]!=-1);
if (tr==0) {
id=fmt::sprintf("±%d###TrAmount",tr);
} else if (tr>0) {
id=fmt::sprintf("+%d###TrAmount",tr);
} else {
id=fmt::sprintf("%d###TrAmount",tr);
}
ImGui::TableNextColumn();
ImGui::PushID(i);
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat,true);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
if (ImGui::Button(ICON_FA_CHEVRON_UP "##Up",ImVec2(ImGui::GetContentRegionAvail().x,0))) {
if (i>0) multiInsTranspose[i-1]++;
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (i>0) multiInsTranspose[i-1]+=12;
}
ImGui::PopStyleVar();
ImGui::PopItemFlag();
if (i>0) {
ImVec4 colorActive=uiColors[GUI_COLOR_MULTI_INS_1+i-1];
ImVec4 colorHover=ImVec4(colorActive.x,colorActive.y,colorActive.z,colorActive.w*0.5);
ImVec4 color=ImVec4(colorActive.x,colorActive.y,colorActive.z,colorActive.w*0.25);
ImGui::PushStyleColor(ImGuiCol_Header,color);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,colorHover);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,colorActive);
}
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign,ImVec2(0.5f,0.5f));
if (ImGui::Selectable(id.c_str(),thisInsOn,0,ImVec2(
ImGui::GetContentRegionAvail().x,
ImGui::GetContentRegionAvail().y-ImGui::GetTextLineHeightWithSpacing()
))) {
if (i>0) multiInsTranspose[i-1]=0;
}
ImGui::PopStyleVar();
if (i>0) {
ImGui::PopStyleColor(3);
}
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat,true);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
if (ImGui::Button(ICON_FA_CHEVRON_DOWN "##Down",ImVec2(ImGui::GetContentRegionAvail().x,0))) {
if (i>0) multiInsTranspose[i-1]--;
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (i>0) multiInsTranspose[i-1]-=12;
}
ImGui::PopStyleVar();
ImGui::PopItemFlag();
ImGui::PopID();
}
ImGui::EndTable();
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MULTI_INS_SETUP;
ImGui::End();
}

View file

@ -407,6 +407,43 @@ void FurnaceGUI::drawOrders() {
if (!pat->name.empty() && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s",pat->name.c_str());
}
bool findFreePat=ImGui::IsItemClicked(ImGuiMouseButton_Middle);
if (ImGui::IsItemHovered() && CHECK_LONG_HOLD) {
NOTIFY_LONG_HOLD;
findFreePat=true;
}
if (findFreePat) {
// find free pattern and assign it
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->lockSave([this,i,j]() {
bool foundOne=false;
bool available[DIV_MAX_PATTERNS];
memset(available,1,DIV_MAX_PATTERNS*sizeof(bool));
for (int k=0; k<e->curSubSong->ordersLen; k++) {
available[e->curOrders->ord[j][k]]=false;
}
for (int k=0; k<DIV_MAX_PATTERNS; k++) {
// don't accept a used pattern
if (!available[k]) continue;
// accept an unallocated pattern (guaranteed to be empty)
if (e->curPat[j].data[k]==NULL) {
e->curOrders->ord[j][i]=k;
foundOne=true;
break;
} else {
// check whether this pattern is empty and accept it if so
DivPattern* p=e->curPat[j].getPattern(k,false);
if (p->isEmpty()) {
e->curOrders->ord[j][i]=k;
foundOne=true;
break;
}
}
}
if (!foundOne) showError(_("no free patterns available on this channel!"));
});
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (curOrder==i) {
if (orderEditMode==0) {

View file

@ -33,6 +33,46 @@
} \
}
void FurnaceGUI::pianoLabel(ImDrawList* dl, ImVec2& p0, ImVec2& p1, int note) {
switch (pianoLabelsMode) {
case PIANO_LABELS_OFF:
return;
case PIANO_LABELS_OCTAVE:
case PIANO_LABELS_OCTAVE_C:
if (note%12) return;
}
String label="";
float padding=0.0f;
switch (pianoLabelsMode) {
case PIANO_LABELS_OCTAVE:
label=fmt::sprintf("%d",(note-60)/12);
padding=ImGui::GetStyle().ItemSpacing.y;
break;
case PIANO_LABELS_NOTE:
label=noteNames[60+(note%12)][0];
padding=ImGui::GetStyle().ItemSpacing.y;
break;
case PIANO_LABELS_NOTE_C:
if ((note%12)==0) {
label+=fmt::sprintf("%d\nC",(note-60)/12);
} else {
label=noteNames[60+(note%12)][0];
}
break;
case PIANO_LABELS_OCTAVE_C:
label=fmt::sprintf("C\n%d",(note-60)/12);
break;
case PIANO_LABELS_OCTAVE_NOTE:
label=fmt::sprintf("%c\n%d",noteNames[60+(note%12)][0],(note-60)/12);
break;
}
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+padding;
dl->AddText(pText,0xff404040,label.c_str());
}
void FurnaceGUI::drawPiano() {
if (nextWindow==GUI_WINDOW_PIANO) {
pianoOpen=true;
@ -114,6 +154,27 @@ void FurnaceGUI::drawPiano() {
pianoInputPadMode=PIANO_INPUT_PAD_SPLIT_VISIBLE;
}
ImGui::Unindent();
ImGui::Text(_("Key labels:"));
ImGui::Indent();
if (ImGui::RadioButton(_("Off##keyLabel0"),pianoLabelsMode==PIANO_LABELS_OFF)) {
pianoLabelsMode=PIANO_LABELS_OFF;
}
if (ImGui::RadioButton(_("Octaves##keyLabel1"),pianoLabelsMode==PIANO_LABELS_OCTAVE)) {
pianoLabelsMode=PIANO_LABELS_OCTAVE;
}
if (ImGui::RadioButton(_("Notes##keyLabel2"),pianoLabelsMode==PIANO_LABELS_NOTE)) {
pianoLabelsMode=PIANO_LABELS_NOTE;
}
if (ImGui::RadioButton(_("Notes (with octave)##keyLabel3"),pianoLabelsMode==PIANO_LABELS_NOTE_C)) {
pianoLabelsMode=PIANO_LABELS_NOTE_C;
}
if (ImGui::RadioButton(_("Octaves (with C)##keyLabel4"),pianoLabelsMode==PIANO_LABELS_OCTAVE_C)) {
pianoLabelsMode=PIANO_LABELS_OCTAVE_C;
}
if (ImGui::RadioButton(_("Notes + Octaves##keyLabel5"),pianoLabelsMode==PIANO_LABELS_OCTAVE_NOTE)) {
pianoLabelsMode=PIANO_LABELS_OCTAVE_NOTE;
}
ImGui::Unindent();
ImGui::Checkbox(_("Share play/edit offset/range"),&pianoSharePosition);
ImGui::Checkbox(_("Read-only (can't input notes)"),&pianoReadonly);
ImGui::EndPopup();
@ -251,14 +312,7 @@ void FurnaceGUI::drawPiano() {
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f));
p1.x-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
if ((i%12)==0) {
String label=fmt::sprintf("%d",(note-60)/12);
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
dl->AddText(pText,0xff404040,label.c_str());
}
if (isTopKey[i%12]) pianoLabel(dl,p0,p1,note);
}
} else {
int bottomNotes=7*oct;
@ -318,14 +372,7 @@ void FurnaceGUI::drawPiano() {
p1.x-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
if ((i%7)==0) {
String label=fmt::sprintf("%d",(note-60)/12);
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
dl->AddText(pText,0xff404040,label.c_str());
}
pianoLabel(dl,p0,p1,note);
}
for (int i=0; i<oct; i++) {
@ -413,6 +460,11 @@ void FurnaceGUI::drawPiano() {
} else {
e->synchronized([this,note]() {
if (!e->autoNoteOn(-1,curIns,note)) failedNoteOn=true;
for (int mi=0; mi<7; mi++) {
if (multiIns[mi]!=-1) {
e->autoNoteOn(-1,multiIns[mi],note,-1,multiInsTranspose[mi]);
}
}
});
if (edit && curWindow!=GUI_WINDOW_INS_LIST && curWindow!=GUI_WINDOW_INS_EDIT) noteInput(note,0);
}

View file

@ -4177,6 +4177,16 @@ void FurnaceGUI::drawSettings() {
UI_COLOR_CONFIG(GUI_COLOR_MACRO_HIGHLIGHT,_("Step Highlight"));
ImGui::TreePop();
}
if (ImGui::TreeNode(_("Multi-instrument Play"))) {
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_1,_("Second instrument"));
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_2,_("Third instrument"));
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_3,_("Fourth instrument"));
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_4,_("Fifth instrument"));
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_5,_("Sixth instrument"));
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_6,_("Seventh instrument"));
UI_COLOR_CONFIG(GUI_COLOR_MULTI_INS_7,_("Eighth instrument"));
ImGui::TreePop();
}
if (ImGui::TreeNode(_("Instrument Types"))) {
UI_COLOR_CONFIG(GUI_COLOR_INSTR_FM,_("FM (OPN)"));
UI_COLOR_CONFIG(GUI_COLOR_INSTR_STD,_("SN76489/Sega PSG"));

View file

@ -233,7 +233,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) {
for (DivInstrument* i: instruments) {
logI("got instrument from MIDI: %s",i->name);
e->addInstrumentPtr(i);
curIns=e->song.insLen-1;
setCurIns(e->song.insLen-1);
}
} catch (EndOfFileException e) {
logW("end of data already?");