multiple instrument playback, part 1

not implemented for MIDI yet
This commit is contained in:
tildearrow 2025-10-31 19:36:13 -05:00
parent 08a27be76f
commit 5b145b7121
15 changed files with 276 additions and 34 deletions

View file

@ -969,6 +969,7 @@ src/gui/about.cpp
src/gui/channels.cpp
src/gui/chanOsc.cpp
src/gui/clock.cpp
src/gui/commandPalette.cpp
src/gui/compatFlags.cpp
src/gui/csPlayer.cpp
src/gui/cursor.cpp
@ -988,8 +989,8 @@ src/gui/log.cpp
src/gui/memory.cpp
src/gui/mixer.cpp
src/gui/midiMap.cpp
src/gui/multiInsSetup.cpp
src/gui/newSong.cpp
src/gui/commandPalette.cpp
src/gui/orders.cpp
src/gui/osc.cpp
src/gui/patManager.cpp

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

@ -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;
@ -360,6 +362,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;
@ -471,6 +476,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_WINDOW_REF_PLAYER:
refPlayerOpen=false;
break;
case GUI_WINDOW_MULTI_INS_SETUP:
multiInsSetupOpen=false;
break;
default:
break;
}
@ -739,7 +747,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 {
@ -768,7 +776,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 {
@ -820,13 +828,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;
@ -1715,7 +1725,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

@ -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,38 @@ 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
if (curIns<0) {
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;
@ -1333,6 +1365,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]);
}
}
});
}
@ -1469,7 +1506,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;
}
@ -1977,7 +2014,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (curIns!=-2) {
prevIns=curIns;
}
curIns=-2;
setCurIns(-2);
}
}
for (DivInstrument* i: instruments) delete i;
@ -3858,6 +3895,7 @@ bool FurnaceGUI::loop() {
DECLARE_METRIC(effectList)
DECLARE_METRIC(userPresets)
DECLARE_METRIC(refPlayer)
DECLARE_METRIC(multiInsSetup)
DECLARE_METRIC(popup)
#ifdef IS_MOBILE
@ -4045,7 +4083,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;
@ -4239,8 +4277,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;
}
@ -4480,6 +4518,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) {
@ -4852,6 +4891,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();
@ -5052,6 +5092,7 @@ bool FurnaceGUI::loop() {
MEASURE(effectList,drawEffectList());
MEASURE(userPresets,drawUserPresets());
MEASURE(refPlayer,drawRefPlayer());
MEASURE(multiInsSetup,drawMultiInsSetup());
MEASURE(patManager,drawPatManager());
} else {
@ -5098,6 +5139,7 @@ bool FurnaceGUI::loop() {
MEASURE(effectList,drawEffectList());
MEASURE(userPresets,drawUserPresets());
MEASURE(refPlayer,drawRefPlayer());
MEASURE(multiInsSetup,drawMultiInsSetup());
}
@ -5167,7 +5209,7 @@ bool FurnaceGUI::loop() {
}
}
} else {
curIns=prevIns;
setCurIns(prevIns);
wavePreviewInit=true;
updateFMPreview=true;
}
@ -5673,7 +5715,7 @@ bool FurnaceGUI::loop() {
MARK_MODIFIED;
}
if (instrumentCount>=0 && settings.selectAssetOnLoad) {
curIns=instrumentCount-1;
setCurIns(instrumentCount-1);
}
}
}
@ -6550,7 +6592,7 @@ bool FurnaceGUI::loop() {
e->lockEngine([this]() {
e->song.clearInstruments();
});
curIns=-1;
setCurIns(-1);
MARK_MODIFIED;
ImGui::CloseCurrentPopup();
}
@ -6755,7 +6797,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 {
@ -8249,6 +8291,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);
@ -8414,6 +8457,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);
@ -8769,6 +8813,7 @@ FurnaceGUI::FurnaceGUI():
curPaletteChoice(0),
curPaletteType(0),
soloTimeout(0.0f),
mobileMultiInsToggle(false),
purgeYear(2021),
purgeMonth(4),
purgeDay(4),
@ -8818,6 +8863,7 @@ FurnaceGUI::FurnaceGUI():
cvOpen(false),
userPresetsOpen(false),
refPlayerOpen(false),
multiInsSetupOpen(false),
cvNotSerious(false),
shortIntro(false),
insListDir(false),
@ -9285,6 +9331,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,
@ -568,6 +576,7 @@ enum FurnaceGUIWindows {
GUI_WINDOW_CS_PLAYER,
GUI_WINDOW_USER_PRESETS,
GUI_WINDOW_REF_PLAYER,
GUI_WINDOW_MULTI_INS_SETUP,
GUI_WINDOW_SPOILER
};
@ -774,6 +783,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,
@ -2376,13 +2386,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;
@ -2395,6 +2411,7 @@ class FurnaceGUI {
bool pianoOpen, notesOpen, 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;
@ -3000,6 +3017,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);
@ -3155,6 +3173,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

@ -657,6 +657,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),
@ -1005,6 +1006,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;

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

@ -0,0 +1,94 @@
/**
* 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]++;
}
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));
ImGui::Selectable(id.c_str(),thisInsOn,0,ImVec2(
ImGui::GetContentRegionAvail().x,
ImGui::GetContentRegionAvail().y-ImGui::GetTextLineHeightWithSpacing()
));
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]--;
}
ImGui::PopStyleVar();
ImGui::PopItemFlag();
ImGui::PopID();
}
ImGui::EndTable();
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MULTI_INS_SETUP;
ImGui::End();
}

View file

@ -429,6 +429,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

@ -4166,6 +4166,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?");