Merge branch 'master' into feature/More-OPL-Patch-Support

This commit is contained in:
James Alan Nguyen 2022-04-17 15:41:18 +10:00
commit ac656f07bb
46 changed files with 397 additions and 148 deletions

View File

@ -11,6 +11,8 @@ this is a multi-system chiptune tracker.
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds.
## features
- supports the following systems:
@ -64,6 +66,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
if you can't download these artifacts (because GitHub requires you to be logged in), [go here](https://nightly.link/tildearrow/furnace/workflows/build/master) instead.
**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.**
## dependencies

View File

@ -1,7 +1,11 @@
# to-do for 0.6pre1
- panning macro
- single macro for hard-panned chips
- two macros for soft-panned ones
- pitch macro
- relative mode
- test
- piano/input pad
- note input via piano
- input pad
@ -43,3 +47,4 @@
- settings: OK/Cancel buttons should be always visible
- Apply button in settings
- better FM chip names (number and codename)
- find and replace

View File

@ -274,6 +274,19 @@ size | description
# instrument
notes:
- the entire instrument is stored, regardless of instrument type.
- the macro range varies depending on the instrument type.
- "macro open" indicates whether the macro is collapsed or not in the instrument editor.
- FM operator order is:
- 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op
- 1/2/?/? (? = unused) for OPL 2-op and OPLL
- meaning of extended macros varies depending on instrument type.
- meaning of panning macros varies depending on instrument type:
- for hard-panned chips (e.g. FM and Game Boy): left panning is 2-bit panning macro (left/right)
- otherwise both left and right panning macros are used
```
size | description
-----|------------------------------------
@ -282,17 +295,39 @@ size | description
2 | format version (see header)
1 | instrument type
| - 0: standard
| - 1: FM
| - 1: FM (OPM/OPN)
| - 2: Game Boy
| - 3: C64
| - 4: Amiga/sample
| - 5: PC Engine
| - 6: AY-3-8910
| - 7: AY8930
| - 8: TIA
| - 9: SAA1099
| - 10: VIC
| - 11: PET
| - 12: VRC6
| - 13: OPLL
| - 14: OPL
| - 15: FDS
| - 16: Virtual Boy
| - 17: Namco 163
| - 18: SCC
| - 19: OPZ
| - 20: POKEY
| - 21: PC Speaker
| - 22: WonderSwan
| - 23: Lynx
| - 24: VERA
| - 25: X1-010
| - 26: VRC6 (saw)
1 | reserved
STR | instrument name
--- | **FM instrument data**
1 | alg
1 | alg (SUS on OPLL)
1 | feedback
1 | fms
1 | ams
1 | fms (DC on OPLL)
1 | ams (DM on OPLL)
1 | operator count
| - this is either 2 or 4, and is ignored on non-OPL systems.
| - always read 4 ops regardless of this value.
@ -314,10 +349,12 @@ size | description
1 | dt
1 | d2r
1 | ssgEnv
1 | dam
1 | dvb
1 | egt
1 | ksl
| - bit 4: on (EG-S on OPLL)
| - bit 0-3: envelope type
1 | dam (for YMU759 compat; REV on OPZ)
1 | dvb (for YMU759 compat; FINE on OPZ)
1 | egt (for YMU759 compat; FixedFreq on OPZ)
1 | ksl (EGShift on OPZ)
1 | sus
1 | vib
1 | ws
@ -690,15 +727,18 @@ size | description
| - 10: A#
| - 11: B
| - 12: C (of next octave)
| - this is actually a leftover of the .dmf format.
| - 100: note off
| - 100: note release
| - 100: macro release
| - octave
| - this is an signed char stored in a short.
| - therefore octave value 255 is actually octave -1.
| - yep, another leftover of the .dmf format...
| - instrument
| - volume
| - effect and effect data...
| - effect and effect data (× effect columns)
| - for note/octave, if both values are 0 then it means empty.
| - for instrument, volume, effect and effect data, a value of -1 means empty.
STR | pattern name (>=51)
```

View File

@ -853,6 +853,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
}
if (!preserveDrift) {
ticks=1;
subticks=1;
}
skipping=false;
cmdStream.clear();
@ -1970,7 +1971,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
}
do {
if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type) && chan[finalChan].midiNote==-1) {
if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type || getIns(ins)->type==DIV_INS_AMIGA) && chan[finalChan].midiNote==-1) {
chan[finalChan].midiNote=note;
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
break;
@ -1996,6 +1997,20 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) {
}
}
void DivEngine::autoNoteOffAll() {
if (!playing) {
reset();
freelance=true;
playing=true;
}
for (int i=0; i<chans; i++) {
if (chan[i].midiNote!=-1) {
pendingNotes.push(DivNoteEvent(i,-1,-1,-1,false));
chan[i].midiNote=-1;
}
}
}
void DivEngine::setOrder(unsigned char order) {
BUSY_BEGIN_SOFT;
curOrder=order;

View File

@ -271,7 +271,7 @@ class DivEngine {
void nextRow();
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond);
// returns true if end of song.
bool nextTick(bool noAccum=false);
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal);
void recalcChans();
@ -572,6 +572,7 @@ class DivEngine {
void autoNoteOn(int chan, int ins, int note, int vol=-1);
void autoNoteOff(int chan, int note, int vol=-1);
void autoNoteOffAll();
// go to order
void setOrder(unsigned char order);

View File

@ -264,6 +264,20 @@ void DivPlatformArcade::tick(bool sysTick) {
rWrite(0x1b,chan[i].std.wave.val&3);
}
if (chan[i].std.panL.had) {
chan[i].chVolL=(chan[i].std.panL.val&2)>>1;
chan[i].chVolR=chan[i].std.panL.val&1;
if (isMuted[i]) {
rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
} else {
rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7));
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -215,6 +215,9 @@ void DivPlatformAY8910::tick(bool sysTick) {
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
oldWrites[0x08+i]=-1;

View File

@ -226,6 +226,9 @@ void DivPlatformAY8930::tick(bool sysTick) {
rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3));
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
oldWrites[0x08+i]=-1;

View File

@ -110,6 +110,9 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);

View File

@ -181,6 +181,9 @@ void DivPlatformC64::tick(bool sysTick) {
chan[i].wave=chan[i].std.wave.val;
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.ex1.had) {
filtControl=chan[i].std.ex1.val&15;
updateFilter();

View File

@ -107,21 +107,11 @@ void DivPlatformFDS::tick(bool sysTick) {
rWrite(0x4080,0x80|chan[i].outVol);
}
if (chan[i].std.arp.had) {
if (i==3) { // noise
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=chan[i].std.arp.val;
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=chan[i].note+chan[i].std.arp.val;
}
if (chan[i].baseFreq>255) chan[i].baseFreq=255;
if (chan[i].baseFreq<0) chan[i].baseFreq=0;
} else {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
@ -155,6 +145,9 @@ void DivPlatformFDS::tick(bool sysTick) {
//if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].active) {
if (ws.tick()) {
updateWave();

View File

@ -192,6 +192,9 @@ void DivPlatformGB::tick(bool sysTick) {
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -258,6 +258,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -171,6 +171,10 @@ void DivPlatformLynx::tick(bool sysTick) {
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged) {
if (chan[i].lfsr >= 0) {
WRITE_LFSR(i, (chan[i].lfsr&0xff));
@ -184,8 +188,8 @@ void DivPlatformLynx::tick(bool sysTick) {
}
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));
WRITE_BACKUP( i, chan[i].fd.backup );
}
else if (chan[i].std.duty.had) {
chan[i].freqChanged=false;
} else if (chan[i].std.duty.had) {
chan[i].duty = chan[i].std.duty.val;
WRITE_FEEDBACK(i, chan[i].duty.feedback);
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));

View File

@ -123,6 +123,9 @@ void DivPlatformMMC5::tick(bool sysTick) {
chan[i].duty=chan[i].std.duty.val;
rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6));
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].freqChanged=true;

View File

@ -261,6 +261,9 @@ void DivPlatformN163::tick(bool sysTick) {
}
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.ex1.had) {
if (chan[i].waveLen!=(chan[i].std.ex1.val&0xfc)) {
chan[i].waveLen=chan[i].std.ex1.val&0xfc;

View File

@ -195,6 +195,9 @@ void DivPlatformNES::tick(bool sysTick) {
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].sweepChanged) {
chan[i].sweepChanged=false;
if (i==0) {

View File

@ -269,6 +269,10 @@ void DivPlatformOPL::tick(bool sysTick) {
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -145,6 +145,10 @@ void DivPlatformOPLL::tick(bool sysTick) {
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -196,6 +196,9 @@ void DivPlatformPCE::tick(bool sysTick) {
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
updateWave(i);

View File

@ -186,6 +186,9 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1+chan[i].std.pitch.val;
if (chan[i].freq<0) chan[i].freq=0;

View File

@ -112,6 +112,9 @@ void DivPlatformPET::tick(bool sysTick) {
rWrite(10,chan.wave);
}
}
if (chan.std.pitch.had) {
chan.freqChanged=true;
}
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true)+chan.std.pitch.val;
if (chan.freq>257) chan.freq=257;

View File

@ -326,6 +326,9 @@ void DivPlatformQSound::tick(bool sysTick) {
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false)+chan[i].std.pitch.val;

View File

@ -166,6 +166,9 @@ void DivPlatformSAA1099::tick(bool sysTick) {
if (chan[i].std.wave.had) {
chan[i].psgMode=chan[i].std.wave.val&3;
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.ex1.had) {
saaEnv[i/3]=chan[i].std.ex1.val;
rWrite(0x18+(i/3),saaEnv[i/3]);

View File

@ -99,6 +99,10 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
/*if (chan[i].keyOn || chan[i].keyOff) {
chan[i].keyOff=false;
}*/

View File

@ -98,6 +98,9 @@ void DivPlatformSMS::tick(bool sysTick) {
}
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
}
for (int i=0; i<3; i++) {
if (chan[i].freqChanged) {

View File

@ -173,6 +173,9 @@ void DivPlatformSwan::tick(bool sysTick) {
chan[i].ws.changeWave1(chan[i].wave);
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].active) {
sndCtrl|=(1<<i);
if (chan[i].ws.tick()) {

View File

@ -116,6 +116,9 @@ void DivPlatformTIA::tick(bool sysTick) {
rWrite(0x15+i,chan[i].shape);
chan[i].freqChanged=true;
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (chan[i].insChanged) {
if (!chan[i].std.wave.will) {

View File

@ -228,6 +228,10 @@ void DivPlatformTX81Z::tick(bool sysTick) {
rWrite(0x1b,chan[i].std.wave.val&3);
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -184,6 +184,9 @@ void DivPlatformVERA::tick(bool sysTick) {
if (chan[i].std.wave.had) {
rWriteHi(i,3,chan[i].std.wave.val);
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8)+chan[i].std.pitch.val;
if (chan[i].freq>65535) chan[i].freq=65535;

View File

@ -119,6 +119,9 @@ void DivPlatformVIC20::tick(bool sysTick) {
chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].std.pitch.val;
if (i<3) {

View File

@ -178,6 +178,9 @@ void DivPlatformVRC6::tick(bool sysTick) {
chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4));
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (i==2) { // sawtooth
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1+chan[i].std.pitch.val;

View File

@ -372,6 +372,9 @@ void DivPlatformX1_010::tick(bool sysTick) {
}
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.ex1.had) {
bool nextEnable=(chan[i].std.ex1.val&1);
if (nextEnable!=(chan[i].env.flag.envEnable)) {

View File

@ -408,6 +408,10 @@ void DivPlatformYM2610::tick(bool sysTick) {
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -472,6 +472,10 @@ void DivPlatformYM2610B::tick(bool sysTick) {
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;

View File

@ -1472,19 +1472,21 @@ void DivEngine::nextRow() {
firstTick=true;
}
bool DivEngine::nextTick(bool noAccum) {
bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
bool ret=false;
if (divider<10) divider=10;
if (lowLatency) {
if (lowLatency && !skipping && !inhibitLowLat) {
tickMult=1000/divider;
if (tickMult<1) tickMult=1;
} else {
tickMult=1;
}
cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/(divider*tickMult);
clockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)divider);
if (clockDrift>=divider) {
clockDrift-=divider;
clockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(divider*tickMult));
if (clockDrift>=(divider*tickMult)) {
clockDrift-=(divider*tickMult);
cycles++;
}

View File

@ -1384,7 +1384,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
writeLoop=true;
}
}
if (nextTick() || !playing) {
if (nextTick(false,true) || !playing) {
done=true;
if (!loop) {
for (int i=0; i<song.systemLen; i++) {

View File

@ -226,6 +226,26 @@ void FurnaceGUI::drawDebug() {
}
ImGui::TreePop();
}
if (ImGui::TreeNode("ADSR Test Area")) {
static int tl, ar, dr, d2r, sl, rr, sus, egt, algOrGlobalSus, instType;
static float maxArDr, maxTl;
ImGui::Text("This window was done out of frustration");
drawFMEnv(tl,ar,dr,d2r,rr,sl,sus,egt,algOrGlobalSus,maxTl,maxArDr,ImVec2(200.0f*dpiScale,100.0f*dpiScale),instType);
ImGui::InputInt("tl",&tl);
ImGui::InputInt("ar",&ar);
ImGui::InputInt("dr",&dr);
ImGui::InputInt("d2r",&d2r);
ImGui::InputInt("sl",&sl);
ImGui::InputInt("rr",&rr);
ImGui::InputInt("sus",&sus);
ImGui::InputInt("egt",&egt);
ImGui::InputInt("algOrGlobalSus",&algOrGlobalSus);
ImGui::InputInt("instType",&instType);
ImGui::InputFloat("maxArDr",&maxArDr);
ImGui::InputFloat("maxTl",&maxTl);
ImGui::TreePop();
}
if (ImGui::TreeNode("User Interface")) {
if (ImGui::Button("Inspect")) {
inspectorOpen=!inspectorOpen;

View File

@ -99,20 +99,14 @@ void FurnaceGUI::doAction(int what) {
if (++curOctave>7) {
curOctave=7;
} else {
for (size_t i=0; i<activeNotes.size(); i++) {
e->noteOff(activeNotes[i].chan);
}
activeNotes.clear();
e->autoNoteOffAll();
}
break;
case GUI_ACTION_OCTAVE_DOWN:
if (--curOctave<-5) {
curOctave=-5;
} else {
for (size_t i=0; i<activeNotes.size(); i++) {
e->noteOff(activeNotes[i].chan);
}
activeNotes.clear();
e->autoNoteOffAll();
}
break;
case GUI_ACTION_INS_UP:
@ -565,12 +559,14 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_ADD:
curSample=e->addSample();
updateSampleTex=true;
MARK_MODIFIED;
break;
case GUI_ACTION_SAMPLE_LIST_DUPLICATE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
DivSample* prevSample=e->getSample(curSample);
curSample=e->addSample();
updateSampleTex=true;
e->lockEngine([this,prevSample]() {
DivSample* sample=e->getSample(curSample);
if (sample!=NULL) {
@ -597,16 +593,23 @@ void FurnaceGUI::doAction(int what) {
if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE);
break;
case GUI_ACTION_SAMPLE_LIST_MOVE_UP:
if (e->moveSampleUp(curSample)) curSample--;
if (e->moveSampleUp(curSample)) {
curSample--;
updateSampleTex=true;
}
break;
case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN:
if (e->moveSampleDown(curSample)) curSample++;
if (e->moveSampleDown(curSample)) {
curSample++;
updateSampleTex=true;
}
break;
case GUI_ACTION_SAMPLE_LIST_DELETE:
e->delSample(curSample);
MARK_MODIFIED;
if (curSample>=(int)e->song.sample.size()) {
curSample--;
updateSampleTex=true;
}
break;
case GUI_ACTION_SAMPLE_LIST_EDIT:
@ -614,9 +617,11 @@ void FurnaceGUI::doAction(int what) {
break;
case GUI_ACTION_SAMPLE_LIST_UP:
if (--curSample<0) curSample=0;
updateSampleTex=true;
break;
case GUI_ACTION_SAMPLE_LIST_DOWN:
if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1;
updateSampleTex=true;
break;
case GUI_ACTION_SAMPLE_LIST_PREVIEW:
e->previewSample(curSample);

View File

@ -35,10 +35,7 @@ void FurnaceGUI::drawEditControls() {
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
for (size_t i=0; i<activeNotes.size(); i++) {
e->noteOff(activeNotes[i].chan);
}
activeNotes.clear();
e->autoNoteOffAll();
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
nextWindow=GUI_WINDOW_PATTERN;
@ -139,10 +136,7 @@ void FurnaceGUI::drawEditControls() {
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
for (size_t i=0; i<activeNotes.size(); i++) {
e->noteOff(activeNotes[i].chan);
}
activeNotes.clear();
e->autoNoteOffAll();
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
nextWindow=GUI_WINDOW_PATTERN;
@ -213,10 +207,7 @@ void FurnaceGUI::drawEditControls() {
if (ImGui::InputInt("##Octave",&curOctave,0,0)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
for (size_t i=0; i<activeNotes.size(); i++) {
e->noteOff(activeNotes[i].chan);
}
activeNotes.clear();
e->autoNoteOffAll();
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
nextWindow=GUI_WINDOW_PATTERN;
@ -314,10 +305,7 @@ void FurnaceGUI::drawEditControls() {
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
for (size_t i=0; i<activeNotes.size(); i++) {
e->noteOff(activeNotes[i].chan);
}
activeNotes.clear();
e->autoNoteOffAll();
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
nextWindow=GUI_WINDOW_PATTERN;

View File

@ -1078,9 +1078,6 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
if (edit) {
noteInput(num,key);
}
if (key!=100 && key!=101 && key!=102) {
previewNote(cursor.xCoarse,num);
}
} catch (std::out_of_range& e) {
}
} else if (edit) { // value
@ -1182,75 +1179,10 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
}
} catch (std::out_of_range& e) {
}
// PER-WINDOW PREVIEW KEYS
switch (curWindow) {
case GUI_WINDOW_INS_EDIT:
case GUI_WINDOW_INS_LIST:
case GUI_WINDOW_EDIT_CONTROLS:
case GUI_WINDOW_SONG_INFO:
if (!ev.key.repeat) {
try {
int key=noteKeys.at(ev.key.keysym.scancode);
int num=12*curOctave+key;
if (key!=100 && key!=101 && key!=102) {
previewNote(cursor.xCoarse,num);
}
} catch (std::out_of_range& e) {
}
}
break;
case GUI_WINDOW_SAMPLE_EDIT:
case GUI_WINDOW_SAMPLE_LIST:
if (!ev.key.repeat) {
try {
int key=noteKeys.at(ev.key.keysym.scancode);
int num=12*curOctave+key;
if (key!=100 && key!=101 && key!=102) {
e->previewSample(curSample,num);
samplePreviewOn=true;
samplePreviewKey=ev.key.keysym.scancode;
samplePreviewNote=num;
}
} catch (std::out_of_range& e) {
}
}
break;
case GUI_WINDOW_WAVE_LIST:
case GUI_WINDOW_WAVE_EDIT:
if (!ev.key.repeat) {
try {
int key=noteKeys.at(ev.key.keysym.scancode);
int num=12*curOctave+key;
if (key!=100 && key!=101 && key!=102) {
e->previewWave(curWave,num);
wavePreviewOn=true;
wavePreviewKey=ev.key.keysym.scancode;
wavePreviewNote=num;
}
} catch (std::out_of_range& e) {
}
}
break;
default:
break;
}
}
void FurnaceGUI::keyUp(SDL_Event& ev) {
stopPreviewNote(ev.key.keysym.scancode,true);
if (wavePreviewOn) {
if (ev.key.keysym.scancode==wavePreviewKey) {
wavePreviewOn=false;
e->stopWavePreview();
}
}
if (samplePreviewOn) {
if (ev.key.keysym.scancode==samplePreviewKey) {
samplePreviewOn=false;
e->stopSamplePreview();
}
}
// nothing for now
}
bool dirExists(String what) {
@ -2021,10 +1953,100 @@ void FurnaceGUI::editOptions(bool topMenu) {
}
}
int _processEvent(void* instance, SDL_Event* event) {
return ((FurnaceGUI*)instance)->processEvent(event);
}
int FurnaceGUI::processEvent(SDL_Event* ev) {
if (ev->type==SDL_KEYDOWN) {
if (!ev->key.repeat && !wantCaptureKeyboard && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) {
if (settings.notePreviewBehavior==0) return 1;
switch (curWindow) {
case GUI_WINDOW_SAMPLE_EDIT:
case GUI_WINDOW_SAMPLE_LIST:
try {
int key=noteKeys.at(ev->key.keysym.scancode);
int num=12*curOctave+key;
if (key!=100 && key!=101 && key!=102) {
e->previewSample(curSample,num);
samplePreviewOn=true;
samplePreviewKey=ev->key.keysym.scancode;
samplePreviewNote=num;
}
} catch (std::out_of_range& e) {
}
break;
case GUI_WINDOW_WAVE_LIST:
case GUI_WINDOW_WAVE_EDIT:
try {
int key=noteKeys.at(ev->key.keysym.scancode);
int num=12*curOctave+key;
if (key!=100 && key!=101 && key!=102) {
e->previewWave(curWave,num);
wavePreviewOn=true;
wavePreviewKey=ev->key.keysym.scancode;
wavePreviewNote=num;
}
} catch (std::out_of_range& e) {
}
break;
case GUI_WINDOW_ORDERS: // ignore here
break;
case GUI_WINDOW_PATTERN:
if (settings.notePreviewBehavior==1) {
if (cursor.xFine!=0) break;
} else if (settings.notePreviewBehavior==2) {
if (edit && cursor.xFine!=0) break;
}
// fall-through
default:
try {
int key=noteKeys.at(ev->key.keysym.scancode);
int num=12*curOctave+key;
if (num<-60) num=-60; // C-(-5)
if (num>119) num=119; // B-9
if (key!=100 && key!=101 && key!=102) {
previewNote(cursor.xCoarse,num);
}
} catch (std::out_of_range& e) {
}
break;
}
}
} else if (ev->type==SDL_KEYUP) {
stopPreviewNote(ev->key.keysym.scancode,true);
if (wavePreviewOn) {
if (ev->key.keysym.scancode==wavePreviewKey) {
wavePreviewOn=false;
e->stopWavePreview();
}
}
if (samplePreviewOn) {
if (ev->key.keysym.scancode==samplePreviewKey) {
samplePreviewOn=false;
e->stopSamplePreview();
}
}
}
return 1;
}
bool FurnaceGUI::loop() {
SDL_SetEventFilter(_processEvent,this);
while (!quit) {
SDL_Event ev;
if (e->isPlaying()) {
WAKE_UP;
}
if (--drawHalt<=0) {
drawHalt=0;
if (settings.powerSave) SDL_WaitEventTimeout(NULL,500);
}
while (SDL_PollEvent(&ev)) {
WAKE_UP;
ImGui_ImplSDL2_ProcessEvent(&ev);
switch (ev.type) {
case SDL_MOUSEMOTION: {
@ -2131,23 +2153,7 @@ bool FurnaceGUI::loop() {
}
break;
case SDL_KEYUP:
if (!ImGui::GetIO().WantCaptureKeyboard) {
keyUp(ev);
} else {
stopPreviewNote(ev.key.keysym.scancode,true);
if (wavePreviewOn) {
if (ev.key.keysym.scancode==wavePreviewKey) {
wavePreviewOn=false;
e->stopWavePreview();
}
}
if (samplePreviewOn) {
if (ev.key.keysym.scancode==samplePreviewKey) {
samplePreviewOn=false;
e->stopSamplePreview();
}
}
}
// for now
break;
case SDL_DROPFILE:
if (ev.drop.file!=NULL) {
@ -2172,6 +2178,8 @@ bool FurnaceGUI::loop() {
break;
}
}
wantCaptureKeyboard=ImGui::GetIO().WantCaptureKeyboard;
while (true) {
midiLock.lock();
@ -3422,8 +3430,10 @@ FurnaceGUI::FurnaceGUI():
displayError(false),
displayExporting(false),
vgmExportLoop(true),
wantCaptureKeyboard(false),
displayNew(false),
vgmExportVersion(0x171),
drawHalt(10),
curFileDialog(GUI_FILE_OPEN),
warnAction(GUI_WARN_OPEN),
postWarnAction(GUI_WARN_GENERIC),

View File

@ -41,6 +41,7 @@
#define unimportant(x) if (x) {handleUnimportant}
#define MARK_MODIFIED modified=true;
#define WAKE_UP drawHalt=16;
#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF])
@ -711,10 +712,11 @@ class FurnaceGUI {
String mmlString[17];
String mmlStringW;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard;
bool displayNew;
bool willExport[32];
int vgmExportVersion;
int drawHalt;
FurnaceGUIFileDialogs curFileDialog;
FurnaceGUIWarnings warnAction;
@ -819,6 +821,8 @@ class FurnaceGUI {
int oplStandardWaveNames;
int cursorMoveNoScroll;
int lowLatency;
int notePreviewBehavior;
int powerSave;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -891,6 +895,8 @@ class FurnaceGUI {
oplStandardWaveNames(0),
cursorMoveNoScroll(0),
lowLatency(0),
notePreviewBehavior(1),
powerSave(1),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1215,6 +1221,7 @@ class FurnaceGUI {
void addScroll(int amount);
void setFileName(String name);
void runBackupThread();
int processEvent(SDL_Event* ev);
bool loop();
bool finish();
bool init();

View File

@ -175,6 +175,10 @@ const char* n163UpdateBits[8]={
"now", "every waveform changed", NULL
};
const char* panBits[3]={
"left", "right", NULL
};
const char* oneBit[2]={
"on", NULL
};
@ -2717,8 +2721,10 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_SAA1099) ex1Max=8;
int panMax=0;
bool panSingle=false;
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_GB || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_VERA) {
panMax=1;
panSingle=true;
}
if (ins->type==DIV_INS_AMIGA) {
panMax=127;
@ -2746,8 +2752,12 @@ void FurnaceGUI::drawInsEdit() {
NORMAL_MACRO(ins->std.waveMacro,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacro.open,bitMode,waveNames,false,NULL,0,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false);
}
if (panMax>0) {
NORMAL_MACRO(ins->std.panLMacro,0,panMax,"panL","Panning (left)",(31+panMax),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
NORMAL_MACRO(ins->std.panRMacro,0,panMax,"panR","Panning (right)",(31+panMax),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,panMax,NULL,false);
if (panSingle) {
NORMAL_MACRO(ins->std.panLMacro,0,2,"panL","Panning",32,ins->std.panLMacro.open,true,panBits,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
} else {
NORMAL_MACRO(ins->std.panLMacro,0,panMax,"panL","Panning (left)",(31+panMax),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
NORMAL_MACRO(ins->std.panRMacro,0,panMax,"panR","Panning (right)",(31+panMax),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,panMax,NULL,false);
}
}
NORMAL_MACRO(ins->std.pitchMacro,pitchMacroScroll,pitchMacroScroll+160,"pitch","Pitch",160,ins->std.pitchMacro.open,false,NULL,true,&pitchMacroScroll,-2048,2047,0,0,true,1,macroAbsoluteMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[15],-2048,2047,NULL,!ins->std.pitchMacro.mode);
if (ins->type==DIV_INS_FM ||

View File

@ -49,12 +49,19 @@ void FurnaceGUI::readOsc() {
for (int i=0; i<512; i++) {
int pos=(readPos+(i*total/512))&0x7fff;
oscValues[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f;
if (oscValues[i]>0.001f || oscValues[i]<-0.001f) {
WAKE_UP;
}
}
float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime;
for (int i=0; i<2; i++) {
peak[i]*=1.0-peakDecay;
if (peak[i]<0.0001) peak[i]=0.0;
if (peak[i]<0.0001) {
peak[i]=0.0;
} else {
WAKE_UP;
}
float newPeak=peak[i];
for (int j=0; j<total; j++) {
int pos=(readPos+j)&0x7fff;

View File

@ -890,6 +890,7 @@ void FurnaceGUI::drawPattern() {
// particle simulation
ImDrawList* fdl=ImGui::GetForegroundDrawList();
if (!particles.empty()) WAKE_UP;
for (size_t i=0; i<particles.size(); i++) {
Particle& part=particles[i];
if (part.update(frameTime)) {

View File

@ -31,6 +31,13 @@
#define DEFAULT_NOTE_KEYS "5:7;6:4;7:3;8:16;10:6;11:8;12:24;13:10;16:11;17:9;18:26;19:28;20:12;21:17;22:1;23:19;24:23;25:5;26:14;27:2;28:21;29:0;30:100;31:13;32:15;34:18;35:20;36:22;38:25;39:27;43:100;46:101;47:29;48:31;53:102;"
#if defined(_WIN32) || defined(__APPLE__)
#define POWER_SAVE_DEFAULT 1
#else
// currently off on Linux/other due to Mesa catch-up behavior.
#define POWER_SAVE_DEFAULT 0
#endif
const char* mainFonts[]={
"IBM Plex Sans",
"Liberation Sans",
@ -301,6 +308,28 @@ void FurnaceGUI::drawSettings() {
settings.sysFileDialog=sysFileDialogB;
}
bool powerSaveB=settings.powerSave;
if (ImGui::Checkbox("Power-saving mode",&powerSaveB)) {
settings.powerSave=powerSaveB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("saves power by lowering the frame rate to 2fps when idle.\nmay cause issues under Mesa drivers!");
}
ImGui::Text("Note preview behavior:");
if (ImGui::RadioButton("Never##npb0",settings.notePreviewBehavior==0)) {
settings.notePreviewBehavior=0;
}
if (ImGui::RadioButton("When cursor is in Note column##npb1",settings.notePreviewBehavior==1)) {
settings.notePreviewBehavior=1;
}
if (ImGui::RadioButton("When cursor is in Note column or not in edit mode##npb2",settings.notePreviewBehavior==2)) {
settings.notePreviewBehavior=2;
}
if (ImGui::RadioButton("Always##npb3",settings.notePreviewBehavior==3)) {
settings.notePreviewBehavior=3;
}
ImGui::Text("Wrap pattern cursor horizontally:");
if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) {
settings.wrapHorizontal=0;
@ -1609,6 +1638,8 @@ void FurnaceGUI::syncSettings() {
settings.oplStandardWaveNames=e->getConfInt("oplStandardWaveNames",0);
settings.cursorMoveNoScroll=e->getConfInt("cursorMoveNoScroll",0);
settings.lowLatency=e->getConfInt("lowLatency",0);
settings.notePreviewBehavior=e->getConfInt("notePreviewBehavior",1);
settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -1670,6 +1701,8 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.oplStandardWaveNames,0,1);
clampSetting(settings.cursorMoveNoScroll,0,1);
clampSetting(settings.lowLatency,0,1);
clampSetting(settings.notePreviewBehavior,0,3);
clampSetting(settings.powerSave,0,1);
// keybinds
for (int i=0; i<GUI_ACTION_MAX; i++) {
@ -1758,6 +1791,8 @@ void FurnaceGUI::commitSettings() {
e->setConf("oplStandardWaveNames",settings.oplStandardWaveNames);
e->setConf("cursorMoveNoScroll",settings.cursorMoveNoScroll);
e->setConf("lowLatency",settings.lowLatency);
e->setConf("notePreviewBehavior",settings.notePreviewBehavior);
e->setConf("powerSave",settings.powerSave);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {