Merge branch 'master' into spectrum
This commit is contained in:
commit
4f85dec801
23 changed files with 488 additions and 124 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@ an "asset" refers to an instrument, wavetable or sample.
|
|||
|
||||

|
||||
|
||||
this window displays the list of instruments. each entry contains an icon representing an instrument's type and its name.
|
||||
|
||||
the following actions can be done when hovering on an entry:
|
||||
|
||||
- left click to set it as the current instrument.
|
||||
- double click to open the instrument editor.
|
||||
- right click to open a menu with options.
|
||||
- shift-left click to start multi-instrument playback. in this mode you will be able to play more than one instrument at once. see [multi-instrument](../8-advanced/multi-ins.md) for more information.
|
||||
|
||||
buttons from left to right:
|
||||
|
||||
- **Add**: pops up a menu to select which type of instrument to add. if only one instrument type is available, the menu is skipped.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ along the top are the available channels. their abbreviations can be set in the
|
|||
|
||||
along the left are the order numbers. the highlighted row follows the order the pattern view cursor is in.
|
||||
|
||||
each entry in the table is the pattern that will play during that order. these can be changed according to the order edit mode.
|
||||
each cell in the table is the pattern that will play during that order. these can be changed according to the order edit mode.
|
||||
middle-clicking on a cell will set it to a new, unique empty pattern.
|
||||
|
||||
hovering over a pattern number will pop up a tooltip showing the name of that pattern, if it has one.
|
||||
|
||||
|
|
|
|||
11
doc/8-advanced/multi-ins.md
Normal file
11
doc/8-advanced/multi-ins.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# multi-instrument playback
|
||||
|
||||
the instrument list allows you to select more than one instrument by shift-clicking. doing so engages multi-instrument playback mode, where note input will play multiple instruments at once.
|
||||
|
||||
the following window also appears, allowing you to set the note offset (in semitones) for each instrument:
|
||||
|
||||
```
|
||||
TODO: IMAGE!
|
||||
```
|
||||
|
||||
up to 8 instruments can be played at a time.
|
||||
4
extern/adpcm-xq-s/adpcm-lib.c
vendored
4
extern/adpcm-xq-s/adpcm-lib.c
vendored
|
|
@ -352,8 +352,10 @@ int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize,
|
|||
*outbuf++ = pcmdata[0] = (int16_t) (inbuf [0] | (inbuf [1] << 8));
|
||||
index[0] = inbuf [2];
|
||||
|
||||
// tildearrow: don't return if this fails. try decoding a corrupt sample anyway.
|
||||
// thanks Architect!
|
||||
if (index [0] < 0 || index [0] > 88 || inbuf [3]) // sanitize the input a little...
|
||||
return 0;
|
||||
index[0] = 0;
|
||||
|
||||
inbufsize -= 4;
|
||||
inbuf += 4;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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
102
src/gui/multiInsSetup.cpp
Normal 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();
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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?");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue