diff --git a/CMakeLists.txt b/CMakeLists.txt index 62a1452f..33f501b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ src/engine/platform/swan.cpp src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp src/engine/platform/n163.cpp +src/engine/platform/pet.cpp src/engine/platform/dummy.cpp ) @@ -359,12 +360,33 @@ src/gui/fileDialog.cpp src/gui/intConst.cpp src/gui/guiConst.cpp +src/gui/about.cpp +src/gui/channels.cpp +src/gui/compatFlags.cpp +src/gui/cursor.cpp +src/gui/dataList.cpp +src/gui/debugWindow.cpp +src/gui/doAction.cpp +src/gui/editing.cpp +src/gui/editControls.cpp src/gui/insEdit.cpp +src/gui/mixer.cpp +src/gui/newSong.cpp src/gui/orders.cpp +src/gui/osc.cpp src/gui/pattern.cpp +src/gui/piano.cpp +src/gui/presets.cpp +src/gui/regView.cpp src/gui/sampleEdit.cpp src/gui/settings.cpp +src/gui/songInfo.cpp +src/gui/songNotes.cpp +src/gui/stats.cpp +src/gui/sysConf.cpp src/gui/util.cpp +src/gui/waveEdit.cpp +src/gui/volMeter.cpp src/gui/gui.cpp ) diff --git a/README.md b/README.md index a1b64854..b5e28cf6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![screenshot](papers/screenshot1.png) -this is a work-in-progress chiptune tracker compatible with DefleMask modules (.dmf). +a multi-system chiptune tracker. [downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) @@ -23,6 +23,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Amiga - TIA (Atari 2600/7800) - multiple sound chips in a single song! +- DefleMask compatibility - loads .dmf modules, .dmp instruments and .dmw wavetables - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy - VGM and audio file export @@ -64,7 +65,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li ## Nix -(TODO) +[package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. # developer info diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 27e87924..8c64ea57 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -4,6 +4,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Sega Genesis/Mega Drive](genesis.md) - [Sega Master System](sms.md) +- [Yamaha OPLL](opll.md) - [Game Boy](game-boy.md) - [PC Engine/TurboGrafx-16](pce.md) - [NES](nes.md) @@ -15,12 +16,17 @@ this is a list of systems that Furnace supports, including each system's effects - [Amiga](amiga.md) - [Yamaha YM2612 standalone](ym2612.md) - [Yamaha YM2151 standalone](ym2151.md) +- [SegaPCM](segapcm.md) - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) -- [Seta/Allumer X1-010](x1_010.md) +- [VERA](vera.md) +- [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) - [Namco 163](n163.md) +- [Yamaha OPL](opl.md) +- [PC Speaker](pcspkr.md) +- [Commodore PET](pet.md) -Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. +Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... diff --git a/papers/doc/7-systems/arcade.md b/papers/doc/7-systems/arcade.md index fd17f91c..dc9496e6 100644 --- a/papers/doc/7-systems/arcade.md +++ b/papers/doc/7-systems/arcade.md @@ -1,7 +1,7 @@ # Arcade (Yamaha YM2151/PCM) this chip combination was used in the Sega OutRun, X and Y arcade boards, and perhaps a few others. -the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons. a system with all 16 channels may be coming soon. +the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons. # effects diff --git a/papers/doc/7-systems/ay8930.md b/papers/doc/7-systems/ay8930.md index 7344b8f0..6d9db10a 100644 --- a/papers/doc/7-systems/ay8930.md +++ b/papers/doc/7-systems/ay8930.md @@ -5,7 +5,7 @@ a backwards-compatible successor to the AY-3-8910, with increased volume resolut sadly, this soundchip has only ever observed minimal success, and has remained rather obscure since. it is known for being used in the Covox Sound Master, which didn't sell well either. -while emulation of this chip is mostly complete, the additional noise setup registers are not emulated (yet). whether it ever has been emulated at some point in a now-abandoned tracker with similar goal as Furnace is unknown. +while emulation of this chip is mostly complete, hardware comparison hasn't been performed yet due to its scarcity. it also was emulated in a now-abandoned tracker with similar goal as Furnace, which sparked interest on the chip. # effects diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md index 835fd622..0c363163 100644 --- a/papers/doc/7-systems/bubblesystem.md +++ b/papers/doc/7-systems/bubblesystem.md @@ -8,7 +8,7 @@ Also known as K005289, but that's just part of the logic used for pitch and wave Waveform select and Volume control are tied with single AY-3-8910 IO for both channels. Another AY-3-8910 IO is used for reading sound hardware status. -furnace emulates this configurations as single system, waveform format is 15 level and 32 width. +Furnace emulates this configurations as single system, waveform format is 15 level and 32 width. # effects diff --git a/papers/doc/7-systems/lynx.md b/papers/doc/7-systems/lynx.md index 2e2f3e3c..a92b2580 100644 --- a/papers/doc/7-systems/lynx.md +++ b/papers/doc/7-systems/lynx.md @@ -2,7 +2,7 @@ The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990. -The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failiure, and ending up as one of the things that contributed to the downfall of Atari. +The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failure, and ending up as one of the things that contributed to the downfall of Atari. Although the Lynx is still getting (rather impressive) homebrew developed for it, it does not mean that the Lynx is a popular system at all. @@ -17,4 +17,6 @@ The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU run - The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari. ## Effect commands - - `3xxx`: Load LFSR (0 to FFF). For it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits. + - `3xxx`: Load LFSR (0 to FFF). + - this is a bitmask. + - for it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits. diff --git a/papers/doc/7-systems/opl.md b/papers/doc/7-systems/opl.md new file mode 100644 index 00000000..1459eee4 --- /dev/null +++ b/papers/doc/7-systems/opl.md @@ -0,0 +1,47 @@ +# Yamaha OPL + +a series of FM sound chips which were very popular in DOS land. it was so popular that even Yamaha made a logo for it! + +essentially a downgraded version of Yamaha's other FM chips, with only 2 operators per channel. +however, it also had a drums mode, and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode. + +the original OPL was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 channels and drums mode. + +its successor, the OPL2, added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC. +later Creative would borrow the chip to make the Sound Blaster, and totally destroyed AdLib's dominance. + +the OPL3 added 9 more channels, 4 more waveforms, 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things. +it was overkill. + +afterwards everyone moved to Windows and software mixing... + +# effects + +- 10xx: set AM depth. the following values are accepted: + - 0: 1dB (shallow) + - 1: 4.8dB (deep) + - this effect applies to all channels. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. + - only in 4-op mode (OPL3). +- `15xx`: set operator 4 level. + - only in 4-op mode (OPL3). +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). + - `y` is the mutliplier. +- 17xx: set vibrato depth. the following values are accepted: + - 0: normal + - 1: double + - this effect applies to all channels. +- `18xx`: toggle drums mode. + - 0 disables it and 1 enables it. + - only in drums system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. + - only in 4-op mode (OPL3). +- `1Dxx`: set attack of operator 4. + - only in 4-op mode (OPL3). diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index d760d413..880750b3 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -1,21 +1,34 @@ # Yamaha YM2413/OPLL -The YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2). -As of Furnace version 0.5.7pre4, the OPLL sound chip is not yet emulated. It is, however, emulated in Deflemask as of version 1.1.0. Support for loading .DMFs which contain the YM2413 was added in Furnace version 0.5.7pre4. +the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2). thought OPL was downgraded enough? :p -## Technical specifications -The YM2413 is equipped with the following features: - - 9 channels of 2 operator FM synthesis - - A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels - - 1 user-definable patch (this patch can be changed throughout the course of the song) - - 15 pre-defined patches which can all be used at the same time - - Support for ADSR on both the modulator and the carrier - - Sine and half-sine based FM synthesis - - 9 octave note control - - 4096 different frequencies for channels - - 16 unique volume levels (NOTE: Volume 0 is NOT silent.) - - Modulator and carrier key scaling - - Built-in hardware vibrato support +# technical specifications -## Effect commands -TODO: Add effect commands here when YM2413 emulation is added. +the YM2413 is equipped with the following features: + +- 9 channels of 2 operator FM synthesis +- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels +- 1 user-definable patch (this patch can be changed throughout the course of the song) +- 15 pre-defined patches which can all be used at the same time +- Support for ADSR on both the modulator and the carrier +- Sine and half-sine based FM synthesis +- 9 octave note control +- 4096 different frequencies for channels +- 16 unique volume levels (NOTE: Volume 0 is NOT silent.) +- Modulator and carrier key scaling +- Built-in hardware vibrato support + +# effects + +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1 or 2). + - `y` is the mutliplier. +- `18xx`: toggle drums mode. + - 0 disables it and 1 enables it. + - only in drums system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. diff --git a/papers/doc/7-systems/pcspkr.md b/papers/doc/7-systems/pcspkr.md new file mode 100644 index 00000000..afaa4f82 --- /dev/null +++ b/papers/doc/7-systems/pcspkr.md @@ -0,0 +1,7 @@ +# PC Speaker + +40 years of one square beep - and still going! + +# effects + +ha! effects... diff --git a/papers/doc/7-systems/pet.md b/papers/doc/7-systems/pet.md new file mode 100644 index 00000000..f2c2c1c6 --- /dev/null +++ b/papers/doc/7-systems/pet.md @@ -0,0 +1,11 @@ +# Commodore PET + +a computer from 1977 which was leader on US schools back then. subsequently the Apple II took its throne. + +maybe no better than a computer terminal, but somebody discovered a way to update the screen at turbo rate - and eventually its sound "chip" (it was nothing more than an 8-bit shift register) was abused as well. + +some of these didn't even have sound... + +# effects + +- 10xx: set waveform. `xx` is a bitmask. diff --git a/papers/doc/7-systems/segapcm.md b/papers/doc/7-systems/segapcm.md new file mode 100644 index 00000000..1b3ed63f --- /dev/null +++ b/papers/doc/7-systems/segapcm.md @@ -0,0 +1,12 @@ +# SegaPCM + +16 channels of PCM? no way! + +yep, that's right! 16 channels of PCM! + +a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang. + +# effects +- `20xx`: set PCM frequency. + - `xx` is a 256th fraction of 31250Hz. + - this effect exists for mostly DefleMask compatibility - it is otherwise recommended to use Sample type instruments. diff --git a/papers/doc/7-systems/vera.md b/papers/doc/7-systems/vera.md new file mode 100644 index 00000000..be610a80 --- /dev/null +++ b/papers/doc/7-systems/vera.md @@ -0,0 +1,15 @@ +# VERA + +this is a video and sound generator chip used in the Commander X16, a modern 8-bit computer created by The 8-Bit Guy. +it has 16 channels of pulse/triangle/saw/noise and one stereo PCM channel. + +currently Furnace does not support the PCM channel's stereo mode, though (except for panning). + +# effects + +- `20xx`: set waveform. the following values are accepted: + - 0: pulse + - 1: saw + - 2: triangle + - 3: noise +- `22xx`: set duty cycle. `xx` may go from 0 to 3F. diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1-010.md similarity index 94% rename from papers/doc/7-systems/x1_010.md rename to papers/doc/7-systems/x1-010.md index 759d519c..1fde55e0 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1-010.md @@ -2,7 +2,7 @@ One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. It has 2 output channels, but no known hardware using this feature for stereo sound. -Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +Later hardware paired this with external bankswitching logic, but this isn't emulated yet. Allumer one is just rebadged Seta's thing for use in their arcade hardwares. It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. @@ -26,9 +26,9 @@ In furnace, You can set envelope shape split mode. When it sets, its waveform wi # effects - `10xx`: change wave. -- `11xx`: change envelope shape. (also wavetable) +- `11xx`: change envelope shape (also wavetable). - `17xx`: toggle PCM mode. -- `20xx`: set PCM frequency. (1 to FF)* +- `20xx`: set PCM frequency (1 to FF). - `22xx`: set envelope mode. - bit 0 sets whether envelope will affect this channel. - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index ff5a789d..bf8b0d6b 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -1,6 +1,8 @@ # Yamaha YM2151 -the sound chip powering the Arcade system, available for standalone use if you want to make X68000 music or pair it with a 16-channel Sega PCM when it comes. +the sound chip powering several arcade boards and the Sharp X1/X68000. + +it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z. # effects diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 2b2e5fea..e251122c 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -1,6 +1,7 @@ # Taito Arcade/Yamaha YM2610B -YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares, it's backward compatible with non-B chip. +YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito arcade hardware. +it is backward compatible with the original chip. # effects @@ -54,4 +55,4 @@ YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arca - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file + - if `x` or `y` are 0 this will disable auto-envelope mode. diff --git a/papers/doc/7-systems/ymu759.md b/papers/doc/7-systems/ymu759.md index 1ad80939..33984832 100644 --- a/papers/doc/7-systems/ymu759.md +++ b/papers/doc/7-systems/ymu759.md @@ -4,7 +4,8 @@ the Yamaha YMU759 is a sound chip designed for feature phones during the early 2 it is also known as MA-2. sadly Yamaha didn't care about these chips too much, and the register specs were completely unavailable, which means the YMU759 is totally unsupported and unemulated besides Yamaha's official emulator for it built into MidRadio. -hence Furnace does not emulate the chip (and doesn't even let you add it to a song). + +Furnace 0.6 loads DefleMask modules written for this system; however, it doesn't support any of its effects and is simulated using the OPL core. # effects diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 6978aa28..d2c925e2 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -47,6 +47,7 @@ #include "platform/lynx.h" #include "platform/bubsyswsg.h" #include "platform/n163.h" +#include "platform/pet.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -279,6 +280,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_N163: dispatch=new DivPlatformN163; break; + case DIV_SYSTEM_PET: + dispatch=new DivPlatformPET; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e3cf9f33..fb11f1df 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -584,6 +584,7 @@ void DivEngine::renderSamples() { void DivEngine::createNew(const int* description) { quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=DivSong(); if (description!=NULL) { @@ -602,6 +603,7 @@ void DivEngine::createNew(const int* description) { } recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); initDispatch(); isBusy.lock(); @@ -612,9 +614,11 @@ void DivEngine::createNew(const int* description) { void DivEngine::changeSystem(int index, DivSystem which) { quitDispatch(); isBusy.lock(); + saveLock.lock(); song.system[index]=which; song.systemFlags[index]=0; recalcChans(); + saveLock.unlock(); isBusy.unlock(); initDispatch(); isBusy.lock(); @@ -635,11 +639,13 @@ bool DivEngine::addSystem(DivSystem which) { } quitDispatch(); isBusy.lock(); + saveLock.lock(); song.system[song.systemLen]=which; song.systemVol[song.systemLen]=64; song.systemPan[song.systemLen]=0; song.systemFlags[song.systemLen++]=0; recalcChans(); + saveLock.unlock(); isBusy.unlock(); initDispatch(); isBusy.lock(); @@ -660,12 +666,14 @@ bool DivEngine::removeSystem(int index) { } quitDispatch(); isBusy.lock(); + saveLock.lock(); song.system[index]=DIV_SYSTEM_NULL; song.systemLen--; for (int i=index; icenterRate/8363.0)); if (rate<=0) rate=song.sample[sample]->rate; } + if (rate<100) rate=100; blip_set_rates(samp_bb,rate,got.rate); samp_prevSample=0; sPreview.pos=0; @@ -1057,7 +1066,9 @@ void DivEngine::previewWave(int wave, int note) { return; } blip_clear(samp_bb); - blip_set_rates(samp_bb,song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)),got.rate); + double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)); + if (rate<100) rate=100; + blip_set_rates(samp_bb,rate,got.rate); samp_prevSample=0; sPreview.pos=0; sPreview.sample=-1; @@ -1211,8 +1222,10 @@ int DivEngine::addInstrument(int refChan) { int insCount=(int)song.ins.size(); ins->name=fmt::sprintf("Instrument %d",insCount); ins->type=getPreferInsType(refChan); + saveLock.lock(); song.ins.push_back(ins); song.insLen=insCount+1; + saveLock.unlock(); isBusy.unlock(); return insCount; } @@ -1227,6 +1240,9 @@ enum DivInsFormats { DIV_INSFORMAT_SBI, }; +// TODO: re-organize this function to: +// - support replacing instruments +// - support instrument formats which contain multiple instruments bool DivEngine::addInstrumentFromFile(const char* path) { warnings=""; @@ -1351,7 +1367,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } } - // TDOO these really should be refactored to separate functions/cpp files per instrument file type. + // TDOO these really should be re-organized to separate functions per instrument file type. switch (format) { case DIV_INSFORMAT_DMP: { // this is a ridiculous mess @@ -1934,15 +1950,18 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } isBusy.lock(); + saveLock.lock(); int insCount=(int)song.ins.size(); song.ins.push_back(ins); song.insLen=insCount+1; + saveLock.unlock(); isBusy.unlock(); return true; } void DivEngine::delInstrument(int index) { isBusy.lock(); + saveLock.lock(); if (index>=0 && index<(int)song.ins.size()) { for (int i=0; inotifyInsDeletion(song.ins[index]); @@ -1961,15 +1980,18 @@ void DivEngine::delInstrument(int index) { } } } + saveLock.unlock(); isBusy.unlock(); } int DivEngine::addWave() { isBusy.lock(); + saveLock.lock(); DivWavetable* wave=new DivWavetable; int waveCount=(int)song.wave.size(); song.wave.push_back(wave); song.waveLen=waveCount+1; + saveLock.unlock(); isBusy.unlock(); return waveCount; } @@ -2085,30 +2107,36 @@ bool DivEngine::addWaveFromFile(const char* path) { } isBusy.lock(); + saveLock.lock(); int waveCount=(int)song.wave.size(); song.wave.push_back(wave); song.waveLen=waveCount+1; + saveLock.unlock(); isBusy.unlock(); return true; } void DivEngine::delWave(int index) { isBusy.lock(); + saveLock.lock(); if (index>=0 && index<(int)song.wave.size()) { delete song.wave[index]; song.wave.erase(song.wave.begin()+index); song.waveLen=song.wave.size(); } + saveLock.unlock(); isBusy.unlock(); } int DivEngine::addSample() { isBusy.lock(); + saveLock.lock(); DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); sample->name=fmt::sprintf("Sample %d",sampleCount); song.sample.push_back(sample); song.sampleLen=sampleCount+1; + saveLock.unlock(); renderSamples(); isBusy.unlock(); return sampleCount; @@ -2186,8 +2214,10 @@ bool DivEngine::addSampleFromFile(const char* path) { if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate>64000) sample->centerRate=64000; sf_close(f); + saveLock.lock(); song.sample.push_back(sample); song.sampleLen=sampleCount+1; + saveLock.unlock(); renderSamples(); isBusy.unlock(); return sampleCount; @@ -2195,12 +2225,14 @@ bool DivEngine::addSampleFromFile(const char* path) { void DivEngine::delSample(int index) { isBusy.lock(); + saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; song.sample.erase(song.sample.begin()+index); song.sampleLen=song.sample.size(); renderSamples(); } + saveLock.unlock(); isBusy.unlock(); } @@ -2229,11 +2261,14 @@ void DivEngine::addOrder(bool duplicate, bool where) { } } if (where) { // at the end + saveLock.lock(); for (int i=0; icurOrder; j--) { song.orders.ord[i][j]=song.orders.ord[i][j-1]; @@ -2241,6 +2276,7 @@ void DivEngine::addOrder(bool duplicate, bool where) { song.orders.ord[i][curOrder+1]=order[i]; } song.ordersLen++; + saveLock.unlock(); curOrder++; if (playing && !freelance) { playSub(false); @@ -2277,11 +2313,14 @@ void DivEngine::deepCloneOrder(bool where) { } } if (where) { // at the end + saveLock.lock(); for (int i=0; icurOrder; j--) { song.orders.ord[i][j]=song.orders.ord[i][j-1]; @@ -2289,6 +2328,7 @@ void DivEngine::deepCloneOrder(bool where) { song.orders.ord[i][curOrder+1]=order[i]; } song.ordersLen++; + saveLock.unlock(); curOrder++; if (playing && !freelance) { playSub(false); @@ -2300,12 +2340,14 @@ void DivEngine::deepCloneOrder(bool where) { void DivEngine::deleteOrder() { if (song.ordersLen<=1) return; isBusy.lock(); + saveLock.lock(); for (int i=0; i=song.ordersLen) curOrder=song.ordersLen-1; if (playing && !freelance) { playSub(false); @@ -2319,11 +2361,13 @@ void DivEngine::moveOrderUp() { isBusy.unlock(); return; } + saveLock.lock(); for (int i=0; i=(int)song.ins.size()) return false; isBusy.lock(); DivInstrument* prev=song.ins[which]; + saveLock.lock(); song.ins[which]=song.ins[which-1]; song.ins[which-1]=prev; exchangeIns(which,which-1); + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2379,8 +2427,10 @@ bool DivEngine::moveWaveUp(int which) { if (which<1 || which>=(int)song.wave.size()) return false; isBusy.lock(); DivWavetable* prev=song.wave[which]; + saveLock.lock(); song.wave[which]=song.wave[which-1]; song.wave[which-1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2389,8 +2439,10 @@ bool DivEngine::moveSampleUp(int which) { if (which<1 || which>=(int)song.sample.size()) return false; isBusy.lock(); DivSample* prev=song.sample[which]; + saveLock.lock(); song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2399,9 +2451,11 @@ bool DivEngine::moveInsDown(int which) { if (which<0 || which>=((int)song.ins.size())-1) return false; isBusy.lock(); DivInstrument* prev=song.ins[which]; + saveLock.lock(); song.ins[which]=song.ins[which+1]; song.ins[which+1]=prev; exchangeIns(which,which+1); + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2410,8 +2464,10 @@ bool DivEngine::moveWaveDown(int which) { if (which<0 || which>=((int)song.wave.size())-1) return false; isBusy.lock(); DivWavetable* prev=song.wave[which]; + saveLock.lock(); song.wave[which]=song.wave[which+1]; song.wave[which+1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2420,8 +2476,10 @@ bool DivEngine::moveSampleDown(int which) { if (which<0 || which>=((int)song.sample.size())-1) return false; isBusy.lock(); DivSample* prev=song.sample[which]; + saveLock.lock(); song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2462,7 +2520,9 @@ void DivEngine::setOrder(unsigned char order) { void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { isBusy.lock(); + saveLock.lock(); song.systemFlags[system]=flags; + saveLock.unlock(); disCont[system].dispatch->setFlags(song.systemFlags[system]); disCont[system].setRates(got.rate); if (restart && isPlaying()) { @@ -2473,6 +2533,7 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { void DivEngine::setSongRate(float hz, bool pal) { isBusy.lock(); + saveLock.lock(); song.pal=!pal; song.hz=hz; // what? @@ -2487,6 +2548,7 @@ void DivEngine::setSongRate(float hz, bool pal) { divider=50; } } + saveLock.unlock(); isBusy.unlock(); } @@ -2536,6 +2598,20 @@ void DivEngine::synchronized(const std::function& what) { isBusy.unlock(); } +void DivEngine::lockSave(const std::function& what) { + saveLock.lock(); + what(); + saveLock.unlock(); +} + +void DivEngine::lockEngine(const std::function& what) { + isBusy.lock(); + saveLock.lock(); + what(); + saveLock.unlock(); + isBusy.unlock(); +} + TAAudioDesc& DivEngine::getAudioDescWant() { return want; } diff --git a/src/engine/engine.h b/src/engine/engine.h index ba6a770e..9f7464e3 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -201,7 +201,7 @@ class DivEngine { std::map conf; std::queue pendingNotes; bool isMuted[DIV_MAX_CHANS]; - std::mutex isBusy; + std::mutex isBusy, saveLock; String configPath; String configFile; String lastError; @@ -278,7 +278,8 @@ class DivEngine { // save as .dmf. SafeWriter* saveDMF(unsigned char version); // save as .fur. - SafeWriter* saveFur(); + // if notPrimary is true then the song will not be altered + SafeWriter* saveFur(bool notPrimary=false); // build a ROM file (TODO). // specify system to build ROM for. SafeWriter* buildROM(int sys); @@ -620,6 +621,12 @@ class DivEngine { // perform secure/sync operation void synchronized(const std::function& what); + // perform secure/sync song operation + void lockSave(const std::function& what); + + // perform secure/sync song operation (and lock audio too) + void lockEngine(const std::function& what); + // get audio desc want TAAudioDesc& getAudioDescWant(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 23570c47..b50eb74a 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -733,10 +733,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (active) quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=ds; recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); if (active) { initDispatch(); @@ -1028,6 +1030,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(samplePtr,ds.sampleLen*4); for (int i=0; ireadInsData(reader,ds.version)!=DIV_DATA_SUCCESS) { @@ -1082,6 +1086,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read wavetables for (int i=0; ireadWaveData(reader,ds.version)!=DIV_DATA_SUCCESS) { @@ -1109,6 +1114,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } reader.readI(); DivSample* sample=new DivSample; + logD("reading sample %d at %x...\n",i,samplePtr[i]); sample->name=reader.readString(); sample->samples=reader.readI(); @@ -1182,6 +1188,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i: patPtr) { reader.seek(i,SEEK_SET); reader.read(magic,4); + logD("reading pattern in %x...\n",i); if (strcmp(magic,"PATR")!=0) { logE("%x: invalid pattern header!\n",i); lastError="invalid pattern header!"; @@ -1194,6 +1201,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int index=reader.readS(); reader.readI(); + logD("- %d, %d\n",chan,index); + DivPattern* pat=ds.pat[chan].getPattern(index,true); for (int j=0; jdata[j][0]=reader.readS(); @@ -1219,10 +1228,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (active) quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=ds; recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); if (active) { initDispatch(); @@ -1583,10 +1594,12 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { if (active) quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=ds; recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); if (active) { initDispatch(); @@ -1728,7 +1741,8 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } -SafeWriter* DivEngine::saveFur() { +SafeWriter* DivEngine::saveFur(bool notPrimary) { + saveLock.lock(); int insPtr[256]; int wavePtr[256]; int samplePtr[256]; @@ -1736,8 +1750,10 @@ SafeWriter* DivEngine::saveFur() { size_t ptrSeek; warnings=""; - song.isDMF=false; - song.version=DIV_ENGINE_VERSION; + if (!notPrimary) { + song.isDMF=false; + song.version=DIV_ENGINE_VERSION; + } SafeWriter* w=new SafeWriter; w->init(); @@ -1967,6 +1983,7 @@ SafeWriter* DivEngine::saveFur() { w->writeI(i); } + saveLock.unlock(); return w; } @@ -2026,6 +2043,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="this system is not possible on .dmf"; return NULL; } + saveLock.lock(); warnings=""; song.version=version; song.isDMF=true; @@ -2257,7 +2275,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(16); w->write(i->data16,i->length16); } - + + saveLock.unlock(); return w; } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 49b5b59a..d160d112 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -75,7 +75,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le if (s->samples>0) { chan[i].audDat=s->data8[chan[i].audPos++]; if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].audPos=s->loopStart; } else { chan[i].sample=-1; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 75c54af6..46e24333 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -95,7 +95,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; @@ -162,7 +162,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 06fbc725..d04edf4d 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -81,7 +81,7 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) rWrite(0x4011,((unsigned char)s->data8[dacPos]+0x80)>>1); } if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 42ce1158..b0894fe7 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -189,9 +189,6 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) { case 0x1d: return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; break; - case 0x20: - return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; - break; } return NULL; } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 1af1741c..bf7d0d8d 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -91,7 +91,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3)); chan[i].dacPos++; if (chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].dacPos=s->loopStart; } else { chan[i].dacSample=-1; diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp new file mode 100644 index 00000000..b89f9510 --- /dev/null +++ b/src/engine/platform/pet.cpp @@ -0,0 +1,285 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "pet.h" +#include "../engine.h" +#include + +#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}} + +#define CHIP_DIVIDER 16 +#define SAMP_DIVIDER 4 + +const char* regCheatSheet6522[]={ + "T2L", "08", + "SR", "0A", + "ACR", "0B", + NULL +}; + +const char** DivPlatformPET::getRegisterSheet() { + return regCheatSheet6522; +} + +const char* DivPlatformPET::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + } + return NULL; +} + +void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) { + // high-level emulation of 6522 shift register for now + int t2=regPool[8]*2+4; + if (((regPool[11]>>2)&7)==4) { + for (size_t h=start; h0) { + int adv=MIN(cycs,chan.cnt); + chan.cnt-=adv; + cycs-=adv; + if (chan.cnt==0) { + chan.out=(chan.sreg&1)*32767; + chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7); + chan.cnt=t2; + } + } + bufL[h]=chan.out; + bufR[h]=chan.out; + } + } else { + chan.out=0; + for (size_t h=start; h0) { + if (regPool[11]!=16) { + rWrite(11,16); + rWrite(10,chan.wave); + } + } else { + rWrite(11,0); + } +} + +void DivPlatformPET::tick() { + chan.std.next(); + if (chan.std.hadVol) { + chan.outVol=chan.std.vol&chan.vol; + writeOutVol(); + } + if (chan.std.hadArp) { + if (!chan.inPorta) { + if (chan.std.arpMode) { + chan.baseFreq=NOTE_PERIODIC(chan.std.arp); + } else { + chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp); + } + } + chan.freqChanged=true; + } else { + if (chan.std.arpMode && chan.std.finishedArp) { + chan.baseFreq=NOTE_PERIODIC(chan.note); + chan.freqChanged=true; + } + } + if (chan.std.hadWave) { + if (chan.wave!=chan.std.wave) { + chan.wave=chan.std.wave; + rWrite(10,chan.wave); + } + } + if (chan.freqChanged || chan.keyOn || chan.keyOff) { + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true); + if (chan.freq>257) chan.freq=257; + if (chan.freq<2) chan.freq=2; + rWrite(8,chan.freq-2); + if (chan.keyOn) { + if (!chan.std.willVol) { + chan.outVol=chan.vol; + writeOutVol(); + } + chan.keyOn=false; + } + if (chan.keyOff) { + rWrite(11,0); + chan.keyOff=false; + } + chan.freqChanged=false; + } +} + +int DivPlatformPET::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan.ins); + if (c.value!=DIV_NOTE_NULL) { + chan.baseFreq=NOTE_PERIODIC(c.value); + chan.freqChanged=true; + chan.note=c.value; + } + chan.active=true; + chan.keyOn=true; + chan.std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan.active=false; + chan.keyOff=true; + chan.std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan.std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan.ins!=c.value || c.value2==1) { + chan.ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan.vol!=c.value) { + chan.vol=c.value; + if (!chan.std.hadVol) { + chan.outVol=chan.vol; + writeOutVol(); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan.vol; + break; + case DIV_CMD_PITCH: + chan.pitch=c.value; + chan.freqChanged=true; + break; + case DIV_CMD_WAVE: + chan.wave=c.value; + rWrite(10,chan.wave); + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan.baseFreq) { + chan.baseFreq+=c.value; + if (chan.baseFreq>=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } else { + chan.baseFreq-=c.value; + if (chan.baseFreq<=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } + chan.freqChanged=true; + if (return2) { + chan.inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan.baseFreq=NOTE_PERIODIC(c.value+((chan.std.willArp && !chan.std.arpMode)?(chan.std.arp):(0))); + chan.freqChanged=true; + chan.note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan.active && c.value2) { + if (parent->song.resetMacroOnPorta) chan.std.init(parent->getIns(chan.ins)); + } + chan.inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPET::muteChannel(int ch, bool mute) { + isMuted=mute; + writeOutVol(); +} + +void DivPlatformPET::forceIns() { + chan.insChanged=true; + chan.freqChanged=true; + writeOutVol(); +} + +void* DivPlatformPET::getChanState(int ch) { + return &chan; +} + +unsigned char* DivPlatformPET::getRegisterPool() { + return regPool; +} + +int DivPlatformPET::getRegisterPoolSize() { + return 16; +} + +void DivPlatformPET::reset() { + memset(regPool,0,16); + chan=Channel(); +} + +bool DivPlatformPET::isStereo() { + return false; +} + +void DivPlatformPET::notifyInsDeletion(void* ins) { + chan.std.notifyInsDeletion((DivInstrument*)ins); +} + +void DivPlatformPET::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformPET::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformPET::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=1000000; + rate=chipClock/SAMP_DIVIDER; // = 250000kHz + isMuted=false; + reset(); + return 1; +} + +DivPlatformPET::~DivPlatformPET() { +} diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h new file mode 100644 index 00000000..3b6af48d --- /dev/null +++ b/src/engine/platform/pet.h @@ -0,0 +1,82 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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. + */ + +#ifndef _PET_H +#define _PET_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformPET: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + int vol, outVol, wave; + unsigned char sreg; + int cnt; + short out; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(1), + outVol(1), + wave(0b00001111), + sreg(0), + cnt(0), + out(0) {} + }; + Channel chan; + bool isMuted; + + unsigned char regPool[16]; + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + ~DivPlatformPET(); + private: + void writeOutVol(); +}; + +#endif diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 7872e0b6..c0366a18 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -54,7 +54,7 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t } chan[i].pcm.pos+=chan[i].pcm.freq; if (chan[i].pcm.pos>=(s->samples<<8)) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].pcm.pos=s->loopStart<<8; } else { chan[i].pcm.sample=-1; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index de623647..0f32e9a0 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -84,7 +84,7 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len } rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); if (dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index a4d76aa0..8965719f 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -57,7 +57,7 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { return "20xx: Change waveform"; break; case 0x22: - return "22xx: Set duty cycle (0 to 63)"; + return "22xx: Set duty cycle (0 to 3F)"; break; } return NULL; @@ -97,7 +97,7 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len } chan[16].pcm.pos++; if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[16].pcm.pos=s->loopStart; } else { chan[16].pcm.sample=-1; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index e68188a8..7a3baff6 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -371,6 +371,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; case DIV_SYSTEM_BUBSYS_WSG: + case DIV_SYSTEM_PET: switch (effect) { case 0x10: // select waveform dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); @@ -1617,7 +1618,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi return; } - logD("attempts: %d\n",attempts); + //logD("attempts: %d\n",attempts); if (attempts>=100) { logE("hang detected! stopping! at %d seconds %d micro\n",totalSeconds,totalTicks); freelance=false; diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 8b0c78e5..33532d43 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -179,6 +179,121 @@ bool DivSample::resize(unsigned int count) { return false; } +bool DivSample::strip(unsigned int begin, unsigned int end) { + if (begin>samples) begin=samples; + if (end>samples) end=samples; + int count=samples-(end-begin); + if (count<=0) return resize(0); + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + if (begin>0) { + memcpy(data8,oldData8,begin); + } + if (samples-end>0) { + memcpy(data8+begin,oldData8+end,samples-end); + } + delete[] oldData8; + } else { + // do nothing + return true; + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + if (begin>0) { + memcpy(data16,oldData16,sizeof(short)*begin); + } + if (samples-end>0) { + memcpy(&(data16[begin]),&(oldData16[end]),sizeof(short)*(samples-end)); + } + delete[] oldData16; + } else { + // do nothing + return true; + } + samples=count; + return true; + } + return false; +} + +bool DivSample::trim(unsigned int begin, unsigned int end) { + int count=end-begin; + if (count==0) return true; + if (begin==0 && end==samples) return true; + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + memcpy(data8,oldData8+begin,count); + delete[] oldData8; + } else { + // do nothing + return true; + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + memcpy(data16,&(oldData16[begin]),sizeof(short)*count); + delete[] oldData16; + } else { + // do nothing + return true; + } + samples=count; + return true; + } + return false; +} + +// TODO: for clipboard +bool DivSample::insert(unsigned int pos, unsigned int length) { + unsigned int count=samples+length; + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + if (pos>0) { + memcpy(data8,oldData8,pos); + } + if (count-pos-length>0) { + memcpy(data8+pos+length,oldData8+pos,count-pos-length); + } + delete[] oldData8; + } else { + initInternal(8,count); + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + memcpy(data16,oldData16,sizeof(short)*count); + delete[] oldData16; + } else { + initInternal(16,count); + } + samples=count; + return true; + } + return false; +} + #define RESAMPLE_BEGIN \ if (samples<1) return true; \ int finalCount=(double)samples*(r/(double)rate); \ @@ -199,6 +314,8 @@ bool DivSample::resize(unsigned int count) { } #define RESAMPLE_END \ + if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ + rate=r; \ samples=finalCount; \ if (depth==16) { \ delete[] oldData16; \ @@ -229,8 +346,6 @@ bool DivSample::resampleNone(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -270,8 +385,6 @@ bool DivSample::resampleLinear(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -326,8 +439,6 @@ bool DivSample::resampleCubic(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -380,7 +491,7 @@ bool DivSample::resampleBlep(double r) { } } } else if (depth==8) { - memset(data8,0,finalCount*sizeof(short)); + memset(data8,0,finalCount); for (int i=0; i + +const char* aboutLine[]={ + "tildearrow", + "is proud to present", + "", + ("Furnace " DIV_VERSION), + "", + "the free software chiptune tracker,", + "compatible with DefleMask modules.", + "", + "zero disassembly.", + "just clean-room design,", + "time and dedication.", + "", + "> CREDITS <", + "", + "-- program --", + "tildearrow", + "akumanatt", + "cam900", + "djtuBIG-MaliceX", + "laoo", + "superctr", + "", + "-- graphics/UI design --", + "tildearrow", + "BlastBrothers", + "", + "-- documentation --", + "tildearrow", + "freq-mod", + "nicco1690", + "DeMOSic", + "cam900", + "", + "-- demo songs --", + "0x5066", + "ActualNK358", + "breakthetargets", + "CaptainMalware", + "kleeder", + "Mahbod Karamoozian", + "nicco1690", + "NikonTeen", + "SuperJet Spade", + "TheDuccinator", + "TheRealHedgehogSonic", + "tildearrow", + "Ultraprogramer", + "", + "-- additional feedback/fixes --", + "fd", + "OPNA2608", + "plane", + "TheEssem", + "", + "powered by:", + "Dear ImGui by Omar Cornut", + "SDL2 by Sam Lantinga", + "zlib by Jean-loup Gailly", + "and Mark Adler", + "libsndfile by Erik de Castro Lopo", + "Nuked-OPM & Nuked-OPN2 by Nuke.YKT", + "ymfm by Aaron Giles", + "MAME SN76496 by Nicola Salmoria", + "MAME AY-3-8910 by Couriersud", + "with AY8930 fixes by Eulous", + "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", + "SAASound", + "SameBoy by Lior Halphon", + "Mednafen PCE", + "puNES by FHorse", + "reSID by Dag Lem", + "Stella by Stella Team", + "QSound emulator by Ian Karlsson and Valley Bell", + "", + "greetings to:", + "Delek", + "fd", + "ILLUMIDARO", + "all members of Deflers of Noice!", + "", + "copyright © 2021-2022 tildearrow", + "(and contributors).", + "licensed under GPLv2+! see", + "LICENSE for more information.", + "", + "help Furnace grow:", + "https://github.com/tildearrow/furnace", + "", + "contact tildearrow at:", + "https://tildearrow.org/?p=contact", + "", + "disclaimer:", + "despite the fact this program works", + "with the .dmf file format, it is NOT", + "affiliated with Delek or DefleMask in", + "any way, nor it is a replacement for", + "the original program.", + "", + "it also comes with ABSOLUTELY NO WARRANTY.", + "", + "thanks to all contributors/bug reporters!" +}; + +const size_t aboutCount=sizeof(aboutLine)/sizeof(aboutLine[0]); + +void FurnaceGUI::drawAbout() { + // do stuff + if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) { + ImGui::SetWindowPos(ImVec2(0,0)); + ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::PushFont(bigFont); + ImDrawList* dl=ImGui::GetWindowDrawList(); + float r=0; + float g=0; + float b=0; + float peakMix=settings.partyTime?((peak[0]+peak[1])*0.5):0.3; + ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.25+MIN(0.75f,peakMix*0.75f),r,g,b); + dl->AddRectFilled(ImVec2(0,0),ImVec2(scrW*dpiScale,scrH*dpiScale),0xff000000); + bool skip=false; + bool skip2=false; + for (int i=(-80-sin(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.25,g*0.25,b*0.25,1.0))); + } + } + + skip=false; + skip2=false; + for (int i=(-80-cos(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.5,g*0.5,b*0.5,1.0))); + } + } + + skip=false; + skip2=false; + for (int i=(-160+fmod(aboutSin*2,160))*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.75,g*0.75,b*0.75,1.0))); + } + } + + for (size_t i=0; iscrH*dpiScale) continue; + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX+dpiScale,posY+dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX+dpiScale,posY-dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX-dpiScale,posY+dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX-dpiScale,posY-dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX,posY), + 0xffffffff,aboutLine[i]); + } + ImGui::PopFont(); + + float timeScale=60.0f*ImGui::GetIO().DeltaTime; + + aboutHue+=(0.001+peakMix*0.004)*timeScale; + aboutScroll+=(2+(peakMix>0.78)*3)*timeScale; + aboutSin+=(1+(peakMix>0.75)*2)*timeScale; + + while (aboutHue>1) aboutHue--; + while (aboutSin>=2400) aboutSin-=2400; + if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20; + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT; + ImGui::End(); +} diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h new file mode 100644 index 00000000..752a5442 --- /dev/null +++ b/src/gui/actionUtil.h @@ -0,0 +1,41 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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. + */ + +#define DETERMINE_FIRST \ + int firstChannel=0; \ + for (int i=0; igetTotalChannelCount(); i++) { \ + if (e->song.chanShow[i]) { \ + firstChannel=i; \ + break; \ + } \ + } + +#define DETERMINE_LAST \ + int lastChannel=0; \ + for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ + if (e->song.chanShow[i]) { \ + lastChannel=i+1; \ + break; \ + } \ + } + +#define DETERMINE_FIRST_LAST \ + DETERMINE_FIRST \ + DETERMINE_LAST + diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp new file mode 100644 index 00000000..5b2a7e0c --- /dev/null +++ b/src/gui/channels.cpp @@ -0,0 +1,53 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "misc/cpp/imgui_stdlib.h" + +void FurnaceGUI::drawChannels() { + if (nextWindow==GUI_WINDOW_CHANNELS) { + channelsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!channelsOpen) return; + if (ImGui::Begin("Channels",&channelsOpen)) { + if (ImGui::BeginTable("ChannelList",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); + for (int i=0; igetTotalChannelCount(); i++) { + ImGui::PushID(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Visible",&e->song.chanShow[i]); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); + ImGui::PopID(); + } + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHANNELS; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp new file mode 100644 index 00000000..46424fc5 --- /dev/null +++ b/src/gui/compatFlags.cpp @@ -0,0 +1,136 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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" + +void FurnaceGUI::drawCompatFlags() { + if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { + compatFlagsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!compatFlagsOpen) return; + if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { + ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); + ImGui::Checkbox("Limit slide range",&e->song.limitSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); + } + ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); + } + ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); + } + ImGui::Checkbox("Game Boy instrument duty is wave volume",&e->song.waveDutyIsVol); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if enabled, an instrument with duty macro in the wave channel will be mapped to wavetable volume."); + } + + ImGui::Checkbox("Restart macro on portamento",&e->song.resetMacroOnPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a portamento effect will reset the channel's macro if used in combination with a note."); + } + ImGui::Checkbox("Legacy volume slides",&e->song.legacyVolumeSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulate glitchy volume slide behavior by silently overflowing the volume when the slide goes below 0."); + } + ImGui::Checkbox("Compatible arpeggio",&e->song.compatibleArpeggio); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("delay arpeggio by one tick on every new note."); + } + ImGui::Checkbox("Reset slides after note off",&e->song.noteOffResetsSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, note off will reset the channel's slide effect."); + } + ImGui::Checkbox("Reset portamento after reaching target",&e->song.targetResetsSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the slide effect is disabled after it reaches its target."); + } + ImGui::Checkbox("Ignore duplicate slide effects",&e->song.ignoreDuplicateSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, only the first slide of a row in a channel will be considered."); + } + ImGui::Checkbox("Continuous vibrato",&e->song.continuousVibrato); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); + } + ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); + } + ImGui::Checkbox("Auto-insert one tick gap between notes",&e->song.oneTickCut); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); + } + + ImGui::Text("Loop modality:"); + if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { + e->song.loopModality=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to reset channels on loop. may trigger a voltage click on every loop!"); + } + if (ImGui::RadioButton("Soft reset channels",e->song.loopModality==1)) { + e->song.loopModality=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to turn channels off on loop."); + } + if (ImGui::RadioButton("Do nothing",e->song.loopModality==2)) { + e->song.loopModality=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to not reset channels on loop."); + } + + ImGui::Separator(); + + ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); + + ImGui::Checkbox("Arpeggio inhibits non-porta slides",&e->song.arpNonPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.5.5"); + } + ImGui::Checkbox("Wack FM algorithm macro",&e->song.algMacroBehavior); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.5.5"); + } + ImGui::Checkbox("Broken shortcut slides (E1xy/E2xy)",&e->song.brokenShortcutSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.5.7"); + } + ImGui::Checkbox("Stop portamento on note off",&e->song.stopPortaOnNoteOff); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } + ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } + ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp new file mode 100644 index 00000000..c783856a --- /dev/null +++ b/src/gui/cursor.cpp @@ -0,0 +1,285 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "actionUtil.h" + +void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { + if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) { + curNibble=false; + } + cursor.xCoarse=xCoarse; + cursor.xFine=xFine; + cursor.y=y; + selStart.xCoarse=xCoarse; + selStart.xFine=xFine; + selStart.y=y; + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + selecting=true; +} + +void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { + if (!selecting) return; + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; +} + +void FurnaceGUI::finishSelection() { + // swap points if needed + if (selEnd.ygetTotalChannelCount(); + + if (selStart.xCoarse<0) selStart.xCoarse=0; + if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; + if (selStart.y<0) selStart.y=0; + if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; + if (selEnd.xCoarse<0) selEnd.xCoarse=0; + if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; + if (selEnd.y<0) selEnd.y=0; + if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; + if (cursor.xCoarse<0) cursor.xCoarse=0; + if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; + if (cursor.y<0) cursor.y=0; + if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + + if (e->song.chanCollapse[selEnd.xCoarse]) { + selStart.xFine=0; + } + if (e->song.chanCollapse[selEnd.xCoarse]) { + selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + } +} + +void FurnaceGUI::moveCursor(int x, int y, bool select) { + if (!select) { + finishSelection(); + } + + DETERMINE_FIRST_LAST; + + curNibble=false; + if (x!=0) { + demandScrollX=true; + if (x>0) { + for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?1:(3+e->song.pat[cursor.xCoarse].effectRows*2))) { + cursor.xFine=0; + if (++cursor.xCoarse>=lastChannel) { + if (settings.wrapHorizontal!=0 && !select) { + cursor.xCoarse=firstChannel; + if (settings.wrapHorizontal==2) y++; + } else { + cursor.xCoarse=lastChannel-1; + cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?0:(2+e->song.pat[cursor.xCoarse].effectRows*2); + } + } else { + while (!e->song.chanShow[cursor.xCoarse]) { + cursor.xCoarse++; + if (cursor.xCoarse>=e->getTotalChannelCount()) break; + } + } + } + } + } else { + for (int i=0; i<-x; i++) { + if (--cursor.xFine<0) { + if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectRows*2; + if (settings.wrapHorizontal==2) y--; + } else { + cursor.xCoarse=firstChannel; + cursor.xFine=0; + } + } else { + while (!e->song.chanShow[cursor.xCoarse]) { + cursor.xCoarse--; + if (cursor.xCoarse<0) break; + } + if (e->song.chanCollapse[cursor.xCoarse]) { + cursor.xFine=0; + } else { + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + } + } + } + } + } + } + if (y!=0) { + if (y>0) { + for (int i=0; i=e->song.patLen) { + if (settings.wrapVertical!=0 && !select) { + cursor.y=0; + if (settings.wrapVertical==2) { + if (!e->isPlaying() && e->getOrder()<(e->song.ordersLen-1)) { + e->setOrder(e->getOrder()+1); + } else { + cursor.y=e->song.patLen-1; + } + } + } else { + cursor.y=e->song.patLen-1; + } + } + } + } else { + for (int i=0; i<-y; i++) { + cursor.y--; + if (cursor.y<0) { + if (settings.wrapVertical!=0 && !select) { + cursor.y=e->song.patLen-1; + if (settings.wrapVertical==2) { + if (!e->isPlaying() && e->getOrder()>0) { + e->setOrder(e->getOrder()-1); + } else { + cursor.y=0; + } + } + } else { + cursor.y=0; + } + } + } + } + } + if (!select) { + selStart=cursor; + } + selEnd=cursor; + updateScroll(cursor.y); +} + +void FurnaceGUI::moveCursorPrevChannel(bool overflow) { + finishSelection(); + curNibble=false; + + DETERMINE_FIRST_LAST; + + do { + cursor.xCoarse--; + if (cursor.xCoarse<0) break; + } while (!e->song.chanShow[cursor.xCoarse]); + if (cursor.xCoarse=e->getTotalChannelCount()) break; + } while (!e->song.chanShow[cursor.xCoarse]); + if (cursor.xCoarse>=lastChannel) { + if (overflow) { + cursor.xCoarse=firstChannel; + } else { + cursor.xCoarse=lastChannel-1; + } + } + + selStart=cursor; + selEnd=cursor; + demandScrollX=true; +} + +void FurnaceGUI::moveCursorTop(bool select) { + finishSelection(); + curNibble=false; + if (cursor.y==0) { + DETERMINE_FIRST; + cursor.xCoarse=firstChannel; + cursor.xFine=0; + demandScrollX=true; + } else { + cursor.y=0; + } + selStart=cursor; + if (!select) { + selEnd=cursor; + } + updateScroll(cursor.y); +} + +void FurnaceGUI::moveCursorBottom(bool select) { + finishSelection(); + curNibble=false; + if (cursor.y==e->song.patLen-1) { + DETERMINE_LAST; + cursor.xCoarse=lastChannel-1; + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + demandScrollX=true; + } else { + cursor.y=e->song.patLen-1; + } + if (!select) { + selStart=cursor; + } + selEnd=cursor; + updateScroll(cursor.y); +} + +void FurnaceGUI::editAdvance() { + finishSelection(); + cursor.y+=editStep; + if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + selStart=cursor; + selEnd=cursor; + updateScroll(cursor.y); +} \ No newline at end of file diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp new file mode 100644 index 00000000..d6c88a78 --- /dev/null +++ b/src/gui/dataList.cpp @@ -0,0 +1,367 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "IconsFontAwesome4.h" +#include "misc/cpp/imgui_stdlib.h" +#include "plot_nolerp.h" +#include "guiConst.h" +#include + +const char* sampleNote[12]={ + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" +}; + +void FurnaceGUI::drawInsList() { + if (nextWindow==GUI_WINDOW_INS_LIST) { + insListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!insListOpen) return; + if (ImGui::Begin("Instruments",&insListOpen)) { + if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { + doAction(GUI_ACTION_INS_LIST_ADD); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) { + doAction(GUI_ACTION_INS_LIST_DUPLICATE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { + doAction(GUI_ACTION_INS_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { + doAction(GUI_ACTION_INS_LIST_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_INS_LIST_MOVE_UP); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##InsDelete")) { + doAction(GUI_ACTION_INS_LIST_DELETE); + } + ImGui::Separator(); + if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { + if (settings.unifiedDataView) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_TASKS " Instruments"); + ImGui::Indent(); + } + + for (int i=0; i<(int)e->song.ins.size(); i++) { + DivInstrument* ins=e->song.ins[i]; + String name; + switch (ins->type) { + case DIV_INS_FM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_STD: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_GB: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_C64: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AMIGA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_PCE: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); + name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AY8930: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_TIA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SAA1099: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VIC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_PET: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VRC6: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPLL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_FDS: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); + name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VBOY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); + name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_N163: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SCC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPZ: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_POKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_BEEPER: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SWAN: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_MIKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VERA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + default: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); + name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); + break; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(name.c_str(),curIns==i)) { + curIns=i; + } + if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { + nextWindow=GUI_WINDOW_PATTERN; + curIns=i; + } + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + insEditOpen=true; + nextWindow=GUI_WINDOW_INS_EDIT; + } + } + } + + if (settings.unifiedDataView) { + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); + ImGui::Indent(); + actualWaveList(); + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_VOLUME_UP " Samples"); + ImGui::Indent(); + actualSampleList(); + ImGui::Unindent(); + } + + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; + ImGui::End(); +} + +void FurnaceGUI::drawWaveList() { + if (nextWindow==GUI_WINDOW_WAVE_LIST) { + waveListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!waveListOpen) return; + if (ImGui::Begin("Wavetables",&waveListOpen)) { + if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { + doAction(GUI_ACTION_WAVE_LIST_ADD); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FILES_O "##WaveClone")) { + doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { + doAction(GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_WAVE_LIST_UP); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_WAVE_LIST_DOWN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##WaveDelete")) { + doAction(GUI_ACTION_WAVE_LIST_DELETE); + } + ImGui::Separator(); + if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { + actualWaveList(); + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; + ImGui::End(); +} + +void FurnaceGUI::drawSampleList() { + if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { + sampleListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!sampleListOpen) return; + if (ImGui::Begin("Samples",&sampleListOpen)) { + if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { + doAction(GUI_ACTION_SAMPLE_LIST_ADD); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { + doAction(GUI_ACTION_SAMPLE_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { + doAction(GUI_ACTION_SAMPLE_LIST_DELETE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSampleL")) { + doAction(GUI_ACTION_SAMPLE_LIST_PREVIEW); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSampleL")) { + doAction(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); + } + ImGui::Separator(); + if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { + actualSampleList(); + ImGui::EndTable(); + } + ImGui::Unindent(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; + ImGui::End(); +} + +void FurnaceGUI::actualWaveList() { + float wavePreview[256]; + for (int i=0; i<(int)e->song.wave.size(); i++) { + DivWavetable* wave=e->song.wave[i]; + for (int i=0; ilen; i++) { + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { + curWave=i; + } + if (ImGui::IsItemHovered()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + waveEditOpen=true; + } + } + ImGui::SameLine(); + PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); + } +} + +void FurnaceGUI::actualSampleList() { + for (int i=0; i<(int)e->song.sample.size(); i++) { + DivSample* sample=e->song.sample[i]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { + curSample=i; + samplePos=0; + updateSampleTex=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleEditOpen=true; + } + } + } +} \ No newline at end of file diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp new file mode 100644 index 00000000..31830681 --- /dev/null +++ b/src/gui/debugWindow.cpp @@ -0,0 +1,250 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "debug.h" +#include "IconsFontAwesome4.h" +#include + +void FurnaceGUI::drawDebug() { + static int bpOrder; + static int bpRow; + static int bpTick; + static bool bpOn; + if (nextWindow==GUI_WINDOW_DEBUG) { + debugOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!debugOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Debug",&debugOpen,ImGuiWindowFlags_NoDocking)) { + ImGui::Text("NOTE: use with caution."); + if (ImGui::TreeNode("Debug Controls")) { + if (e->isHalted()) { + if (ImGui::Button("Resume")) e->resume(); + } else { + if (ImGui::Button("Pause")) e->halt(); + } + ImGui::SameLine(); + if (ImGui::Button("Frame Advance")) e->haltWhen(DIV_HALT_TICK); + ImGui::SameLine(); + if (ImGui::Button("Row Advance")) e->haltWhen(DIV_HALT_ROW); + ImGui::SameLine(); + if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); + + if (ImGui::Button("Panic")) e->syncReset(); + ImGui::SameLine(); + if (ImGui::Button("Abort")) { + abort(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Breakpoint")) { + ImGui::InputInt("Order",&bpOrder); + ImGui::InputInt("Row",&bpRow); + ImGui::InputInt("Tick",&bpTick); + ImGui::Checkbox("Enable",&bpOn); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Dispatch Status")) { + ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); + ImGui::Columns(e->getTotalChannelCount()); + for (int i=0; igetTotalChannelCount(); i++) { + void* ch=e->getDispatchChanState(i); + ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->dispatchOfChan[i],e->dispatchChanOfChan[i]); + if (ch==NULL) { + ImGui::Text("NULL"); + } else { + putDispatchChan(ch,e->dispatchChanOfChan[i],e->sysOfChan[i]); + } + ImGui::NextColumn(); + } + ImGui::Columns(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Playback Status")) { + ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); + ImGui::Columns(e->getTotalChannelCount()); + for (int i=0; igetTotalChannelCount(); i++) { + DivChannelState* ch=e->getChanState(i); + ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Channel %d:",i); + if (ch==NULL) { + ImGui::Text("NULL"); + } else { + ImGui::Text("* General:"); + ImGui::Text("- note = %d",ch->note); + ImGui::Text("- oldNote = %d",ch->oldNote); + ImGui::Text("- pitch = %d",ch->pitch); + ImGui::Text("- portaSpeed = %d",ch->portaSpeed); + ImGui::Text("- portaNote = %d",ch->portaNote); + ImGui::Text("- volume = %.4x",ch->volume); + ImGui::Text("- volSpeed = %d",ch->volSpeed); + ImGui::Text("- cut = %d",ch->cut); + ImGui::Text("- rowDelay = %d",ch->rowDelay); + ImGui::Text("- volMax = %.4x",ch->volMax); + ImGui::Text("- delayOrder = %d",ch->delayOrder); + ImGui::Text("- delayRow = %d",ch->delayRow); + ImGui::Text("- retrigSpeed = %d",ch->retrigSpeed); + ImGui::Text("- retrigTick = %d",ch->retrigTick); + ImGui::PushStyleColor(ImGuiCol_Text,(ch->vibratoDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("* Vibrato:"); + ImGui::Text("- depth = %d",ch->vibratoDepth); + ImGui::Text("- rate = %d",ch->vibratoRate); + ImGui::Text("- pos = %d",ch->vibratoPos); + ImGui::Text("- dir = %d",ch->vibratoDir); + ImGui::Text("- fine = %d",ch->vibratoFine); + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Text,(ch->tremoloDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("* Tremolo:"); + ImGui::Text("- depth = %d",ch->tremoloDepth); + ImGui::Text("- rate = %d",ch->tremoloRate); + ImGui::Text("- pos = %d",ch->tremoloPos); + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Text,(ch->arp>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("* Arpeggio:"); + ImGui::Text("- arp = %.2X",ch->arp); + ImGui::Text("- stage = %d",ch->arpStage); + ImGui::Text("- ticks = %d",ch->arpTicks); + ImGui::PopStyleColor(); + ImGui::Text("* Miscellaneous:"); + ImGui::TextColored(ch->doNote?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Do Note"); + ImGui::TextColored(ch->legato?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Legato"); + ImGui::TextColored(ch->portaStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> PortaStop"); + ImGui::TextColored(ch->keyOn?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key On"); + ImGui::TextColored(ch->keyOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key Off"); + ImGui::TextColored(ch->nowYouCanStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> NowYouCanStop"); + ImGui::TextColored(ch->stopOnOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Stop on Off"); + ImGui::TextColored(ch->arpYield?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Arp Yield"); + ImGui::TextColored(ch->delayLocked?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> DelayLocked"); + ImGui::TextColored(ch->inPorta?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> InPorta"); + ImGui::TextColored(ch->scheduledSlideReset?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> SchedSlide"); + } + ImGui::NextColumn(); + } + ImGui::Columns(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Playground")) { + if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; + if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { + for (int i=0; isong.systemLen; i++) { + if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { + pgSys=i; + break; + } + } + ImGui::EndCombo(); + } + ImGui::Text("Program"); + if (pgProgram.empty()) { + ImGui::Text("-nothing here-"); + } else { + char id[32]; + for (size_t index=0; indexpoke(pgSys,pgProgram); + } + ImGui::SameLine(); + if (ImGui::Button("Clear")) { + pgProgram.clear(); + } + + ImGui::Text("Address"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(100.0f*dpiScale); + ImGui::InputInt("##PAddress",&pgAddr,0,0,ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + ImGui::Text("Value"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(100.0f*dpiScale); + ImGui::InputInt("##PValue",&pgVal,0,0,ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + if (ImGui::Button("Write")) { + e->poke(pgSys,pgAddr,pgVal); + } + ImGui::SameLine(); + if (ImGui::Button("Add")) { + pgProgram.push_back(DivRegWrite(pgAddr,pgVal)); + } + if (ImGui::TreeNode("Register Cheatsheet")) { + const char** sheet=e->getRegisterSheet(pgSys); + if (sheet==NULL) { + ImGui::Text("no cheatsheet available for this system."); + } else { + if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::Text("Address"); + for (int i=0; sheet[i]!=NULL; i+=2) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s",sheet[i]); + ImGui::TableNextColumn(); + ImGui::Text("$%s",sheet[i+1]); + } + ImGui::EndTable(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("User Interface")) { + if (ImGui::Button("Inspect")) { + inspectorOpen=!inspectorOpen; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Settings")) { + if (ImGui::Button("Sync")) syncSettings(); + ImGui::SameLine(); + if (ImGui::Button("Commit")) commitSettings(); + ImGui::SameLine(); + if (ImGui::Button("Force Load")) e->loadConf(); + ImGui::SameLine(); + if (ImGui::Button("Force Save")) e->saveConf(); + ImGui::TreePop(); + } + ImGui::Text("Song format version %d",e->song.version); + ImGui::Text("Furnace version " DIV_VERSION " (%d)",DIV_ENGINE_VERSION); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_DEBUG; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp new file mode 100644 index 00000000..51dad892 --- /dev/null +++ b/src/gui/doAction.cpp @@ -0,0 +1,1053 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 +#include + +#include "actionUtil.h" +#include "sampleUtil.h" + +void FurnaceGUI::doAction(int what) { + switch (what) { + case GUI_ACTION_OPEN: + if (modified) { + showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); + } else { + openFileDialog(GUI_FILE_OPEN); + } + break; + case GUI_ACTION_OPEN_BACKUP: + if (modified) { + showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN_BACKUP); + } else { + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + } + break; + case GUI_ACTION_SAVE: + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } + } + break; + case GUI_ACTION_SAVE_AS: + openFileDialog(GUI_FILE_SAVE); + break; + case GUI_ACTION_UNDO: + doUndo(); + break; + case GUI_ACTION_REDO: + doRedo(); + break; + case GUI_ACTION_PLAY_TOGGLE: + if (e->isPlaying() && !e->isStepping()) { + stop(); + } else { + play(); + } + break; + case GUI_ACTION_PLAY: + play(); + break; + case GUI_ACTION_STOP: + stop(); + break; + case GUI_ACTION_PLAY_REPEAT: + play(); + e->setRepeatPattern(true); + break; + case GUI_ACTION_PLAY_CURSOR: + if (e->isPlaying() && !e->isStepping()) { + stop(); + } else { + play(cursor.y); + } + break; + case GUI_ACTION_STEP_ONE: + e->stepOne(cursor.y); + break; + case GUI_ACTION_OCTAVE_UP: + if (++curOctave>7) { + curOctave=7; + } else { + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + } + break; + case GUI_ACTION_OCTAVE_DOWN: + if (--curOctave<-5) { + curOctave=-5; + } else { + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + } + break; + case GUI_ACTION_INS_UP: + if (--curIns<-1) curIns=-1; + break; + case GUI_ACTION_INS_DOWN: + if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; + break; + case GUI_ACTION_STEP_UP: + if (++editStep>64) editStep=64; + break; + case GUI_ACTION_STEP_DOWN: + if (--editStep<0) editStep=0; + break; + case GUI_ACTION_TOGGLE_EDIT: + edit=!edit; + break; + case GUI_ACTION_METRONOME: + e->setMetronome(!e->getMetronome()); + break; + case GUI_ACTION_REPEAT_PATTERN: + e->setRepeatPattern(!e->getRepeatPattern()); + break; + case GUI_ACTION_FOLLOW_ORDERS: + followOrders=!followOrders; + break; + case GUI_ACTION_FOLLOW_PATTERN: + followPattern=!followPattern; + break; + case GUI_ACTION_PANIC: + e->syncReset(); + break; + + case GUI_ACTION_WINDOW_EDIT_CONTROLS: + nextWindow=GUI_WINDOW_EDIT_CONTROLS; + break; + case GUI_ACTION_WINDOW_ORDERS: + nextWindow=GUI_WINDOW_ORDERS; + break; + case GUI_ACTION_WINDOW_INS_LIST: + nextWindow=GUI_WINDOW_INS_LIST; + break; + case GUI_ACTION_WINDOW_INS_EDIT: + nextWindow=GUI_WINDOW_INS_EDIT; + break; + case GUI_ACTION_WINDOW_SONG_INFO: + nextWindow=GUI_WINDOW_SONG_INFO; + break; + case GUI_ACTION_WINDOW_PATTERN: + nextWindow=GUI_WINDOW_PATTERN; + break; + case GUI_ACTION_WINDOW_WAVE_LIST: + nextWindow=GUI_WINDOW_WAVE_LIST; + break; + case GUI_ACTION_WINDOW_WAVE_EDIT: + nextWindow=GUI_WINDOW_WAVE_EDIT; + break; + case GUI_ACTION_WINDOW_SAMPLE_LIST: + nextWindow=GUI_WINDOW_SAMPLE_LIST; + break; + case GUI_ACTION_WINDOW_SAMPLE_EDIT: + nextWindow=GUI_WINDOW_SAMPLE_EDIT; + break; + case GUI_ACTION_WINDOW_ABOUT: + nextWindow=GUI_WINDOW_ABOUT; + break; + case GUI_ACTION_WINDOW_SETTINGS: + nextWindow=GUI_WINDOW_SETTINGS; + break; + case GUI_ACTION_WINDOW_MIXER: + nextWindow=GUI_WINDOW_MIXER; + break; + case GUI_ACTION_WINDOW_DEBUG: + nextWindow=GUI_WINDOW_DEBUG; + break; + case GUI_ACTION_WINDOW_OSCILLOSCOPE: + nextWindow=GUI_WINDOW_OSCILLOSCOPE; + break; + case GUI_ACTION_WINDOW_VOL_METER: + nextWindow=GUI_WINDOW_VOL_METER; + break; + case GUI_ACTION_WINDOW_STATS: + nextWindow=GUI_WINDOW_STATS; + break; + case GUI_ACTION_WINDOW_COMPAT_FLAGS: + nextWindow=GUI_WINDOW_COMPAT_FLAGS; + break; + case GUI_ACTION_WINDOW_PIANO: + nextWindow=GUI_WINDOW_PIANO; + break; + case GUI_ACTION_WINDOW_NOTES: + nextWindow=GUI_WINDOW_NOTES; + break; + case GUI_ACTION_WINDOW_CHANNELS: + nextWindow=GUI_WINDOW_CHANNELS; + break; + case GUI_ACTION_WINDOW_REGISTER_VIEW: + nextWindow=GUI_WINDOW_REGISTER_VIEW; + break; + + case GUI_ACTION_COLLAPSE_WINDOW: + collapseWindow=true; + break; + case GUI_ACTION_CLOSE_WINDOW: + switch (curWindow) { + case GUI_WINDOW_EDIT_CONTROLS: + editControlsOpen=false; + break; + case GUI_WINDOW_SONG_INFO: + songInfoOpen=false; + break; + case GUI_WINDOW_ORDERS: + ordersOpen=false; + break; + case GUI_WINDOW_INS_LIST: + insListOpen=false; + break; + case GUI_WINDOW_PATTERN: + patternOpen=false; + break; + case GUI_WINDOW_INS_EDIT: + insEditOpen=false; + break; + case GUI_WINDOW_WAVE_LIST: + waveListOpen=false; + break; + case GUI_WINDOW_WAVE_EDIT: + waveEditOpen=false; + break; + case GUI_WINDOW_SAMPLE_LIST: + sampleListOpen=false; + break; + case GUI_WINDOW_SAMPLE_EDIT: + sampleEditOpen=false; + break; + case GUI_WINDOW_MIXER: + mixerOpen=false; + break; + case GUI_WINDOW_ABOUT: + aboutOpen=false; + break; + case GUI_WINDOW_SETTINGS: + settingsOpen=false; + break; + case GUI_WINDOW_DEBUG: + debugOpen=false; + break; + case GUI_WINDOW_OSCILLOSCOPE: + oscOpen=false; + break; + case GUI_WINDOW_VOL_METER: + volMeterOpen=false; + break; + case GUI_WINDOW_STATS: + statsOpen=false; + break; + case GUI_WINDOW_COMPAT_FLAGS: + compatFlagsOpen=false; + break; + case GUI_WINDOW_PIANO: + pianoOpen=false; + break; + case GUI_WINDOW_NOTES: + notesOpen=false; + break; + case GUI_WINDOW_CHANNELS: + channelsOpen=false; + break; + case GUI_WINDOW_REGISTER_VIEW: + regViewOpen=false; + break; + default: + break; + } + curWindow=GUI_WINDOW_NOTHING; + break; + + case GUI_ACTION_PAT_NOTE_UP: + doTranspose(1); + break; + case GUI_ACTION_PAT_NOTE_DOWN: + doTranspose(-1); + break; + case GUI_ACTION_PAT_OCTAVE_UP: + doTranspose(12); + break; + case GUI_ACTION_PAT_OCTAVE_DOWN: + doTranspose(-12); + break; + case GUI_ACTION_PAT_SELECT_ALL: + doSelectAll(); + break; + case GUI_ACTION_PAT_CUT: + doCopy(true); + break; + case GUI_ACTION_PAT_COPY: + doCopy(false); + break; + case GUI_ACTION_PAT_PASTE: + doPaste(); + break; + case GUI_ACTION_PAT_CURSOR_UP: + moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),false); + break; + case GUI_ACTION_PAT_CURSOR_DOWN: + moveCursor(0,MAX(1,settings.scrollStep?editStep:1),false); + break; + case GUI_ACTION_PAT_CURSOR_LEFT: + moveCursor(-1,0,false); + break; + case GUI_ACTION_PAT_CURSOR_RIGHT: + moveCursor(1,0,false); + break; + case GUI_ACTION_PAT_CURSOR_UP_ONE: + moveCursor(0,-1,false); + break; + case GUI_ACTION_PAT_CURSOR_DOWN_ONE: + moveCursor(0,1,false); + break; + case GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL: + moveCursorPrevChannel(false); + break; + case GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL: + moveCursorNextChannel(false); + break; + case GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL: + moveCursorNextChannel(true); + break; + case GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL: + moveCursorPrevChannel(true); + break; + case GUI_ACTION_PAT_CURSOR_BEGIN: + moveCursorTop(false); + break; + case GUI_ACTION_PAT_CURSOR_END: + moveCursorBottom(false); + break; + case GUI_ACTION_PAT_CURSOR_UP_COARSE: + moveCursor(0,-16,false); + break; + case GUI_ACTION_PAT_CURSOR_DOWN_COARSE: + moveCursor(0,16,false); + break; + case GUI_ACTION_PAT_SELECTION_UP: + moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),true); + break; + case GUI_ACTION_PAT_SELECTION_DOWN: + moveCursor(0,MAX(1,settings.scrollStep?editStep:1),true); + break; + case GUI_ACTION_PAT_SELECTION_LEFT: + moveCursor(-1,0,true); + break; + case GUI_ACTION_PAT_SELECTION_RIGHT: + moveCursor(1,0,true); + break; + case GUI_ACTION_PAT_SELECTION_UP_ONE: + moveCursor(0,-1,true); + break; + case GUI_ACTION_PAT_SELECTION_DOWN_ONE: + moveCursor(0,1,true); + break; + case GUI_ACTION_PAT_SELECTION_BEGIN: + moveCursorTop(true); + break; + case GUI_ACTION_PAT_SELECTION_END: + moveCursorBottom(true); + break; + case GUI_ACTION_PAT_SELECTION_UP_COARSE: + moveCursor(0,-16,true); + break; + case GUI_ACTION_PAT_SELECTION_DOWN_COARSE: + moveCursor(0,16,true); + break; + case GUI_ACTION_PAT_DELETE: + doDelete(); + if (settings.stepOnDelete) { + moveCursor(0,editStep,false); + } + break; + case GUI_ACTION_PAT_PULL_DELETE: + doPullDelete(); + break; + case GUI_ACTION_PAT_INSERT: + doInsert(); + if (settings.stepOnInsert) { + moveCursor(0,editStep,false); + } + break; + case GUI_ACTION_PAT_MUTE_CURSOR: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->toggleMute(cursor.xCoarse); + break; + case GUI_ACTION_PAT_SOLO_CURSOR: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->toggleSolo(cursor.xCoarse); + break; + case GUI_ACTION_PAT_UNMUTE_ALL: + e->unmuteAll(); + break; + case GUI_ACTION_PAT_NEXT_ORDER: + if (e->getOrder()song.ordersLen-1) { + e->setOrder(e->getOrder()+1); + } + break; + case GUI_ACTION_PAT_PREV_ORDER: + if (e->getOrder()>0) { + e->setOrder(e->getOrder()-1); + } + break; + case GUI_ACTION_PAT_COLLAPSE: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse]; + break; + case GUI_ACTION_PAT_INCREASE_COLUMNS: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->song.pat[cursor.xCoarse].effectRows++; + if (e->song.pat[cursor.xCoarse].effectRows>8) e->song.pat[cursor.xCoarse].effectRows=8; + break; + case GUI_ACTION_PAT_DECREASE_COLUMNS: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->song.pat[cursor.xCoarse].effectRows--; + if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; + break; + case GUI_ACTION_PAT_INTERPOLATE: + doInterpolate(); + break; + case GUI_ACTION_PAT_INVERT_VALUES: + doInvertValues(); + break; + case GUI_ACTION_PAT_FLIP_SELECTION: + doFlip(); + break; + case GUI_ACTION_PAT_COLLAPSE_ROWS: + doCollapse(2); + break; + case GUI_ACTION_PAT_EXPAND_ROWS: + doExpand(2); + break; + case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO + break; + case GUI_ACTION_PAT_EXPAND_PAT: // TODO + break; + case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + break; + case GUI_ACTION_PAT_EXPAND_SONG: // TODO + break; + case GUI_ACTION_PAT_LATCH: // TODO + break; + + case GUI_ACTION_INS_LIST_ADD: + curIns=e->addInstrument(cursor.xCoarse); + MARK_MODIFIED; + break; + case GUI_ACTION_INS_LIST_DUPLICATE: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + int prevIns=curIns; + curIns=e->addInstrument(cursor.xCoarse); + (*e->song.ins[curIns])=(*e->song.ins[prevIns]); + MARK_MODIFIED; + } + break; + case GUI_ACTION_INS_LIST_OPEN: + openFileDialog(GUI_FILE_INS_OPEN); + break; + case GUI_ACTION_INS_LIST_SAVE: + if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); + break; + case GUI_ACTION_INS_LIST_MOVE_UP: + if (e->moveInsUp(curIns)) curIns--; + break; + case GUI_ACTION_INS_LIST_MOVE_DOWN: + if (e->moveInsDown(curIns)) curIns++; + break; + case GUI_ACTION_INS_LIST_DELETE: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + e->delInstrument(curIns); + MARK_MODIFIED; + if (curIns>=(int)e->song.ins.size()) { + curIns--; + } + } + break; + case GUI_ACTION_INS_LIST_EDIT: + insEditOpen=true; + break; + case GUI_ACTION_INS_LIST_UP: + if (--curIns<0) curIns=0; + break; + case GUI_ACTION_INS_LIST_DOWN: + if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; + break; + + case GUI_ACTION_WAVE_LIST_ADD: + curWave=e->addWave(); + MARK_MODIFIED; + break; + case GUI_ACTION_WAVE_LIST_DUPLICATE: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + int prevWave=curWave; + curWave=e->addWave(); + (*e->song.wave[curWave])=(*e->song.wave[prevWave]); + MARK_MODIFIED; + } + break; + case GUI_ACTION_WAVE_LIST_OPEN: + openFileDialog(GUI_FILE_WAVE_OPEN); + break; + case GUI_ACTION_WAVE_LIST_SAVE: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); + break; + case GUI_ACTION_WAVE_LIST_MOVE_UP: + if (e->moveWaveUp(curWave)) curWave--; + break; + case GUI_ACTION_WAVE_LIST_MOVE_DOWN: + if (e->moveWaveDown(curWave)) curWave++; + break; + case GUI_ACTION_WAVE_LIST_DELETE: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->delWave(curWave); + MARK_MODIFIED; + if (curWave>=(int)e->song.wave.size()) { + curWave--; + } + } + break; + case GUI_ACTION_WAVE_LIST_EDIT: + waveEditOpen=true; + break; + case GUI_ACTION_WAVE_LIST_UP: + if (--curWave<0) curWave=0; + break; + case GUI_ACTION_WAVE_LIST_DOWN: + if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1; + break; + + case GUI_ACTION_SAMPLE_LIST_ADD: + curSample=e->addSample(); + MARK_MODIFIED; + break; + case GUI_ACTION_SAMPLE_LIST_OPEN: + openFileDialog(GUI_FILE_SAMPLE_OPEN); + break; + case GUI_ACTION_SAMPLE_LIST_SAVE: + 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--; + break; + case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN: + if (e->moveSampleDown(curSample)) curSample++; + break; + case GUI_ACTION_SAMPLE_LIST_DELETE: + e->delSample(curSample); + MARK_MODIFIED; + if (curSample>=(int)e->song.sample.size()) { + curSample--; + } + break; + case GUI_ACTION_SAMPLE_LIST_EDIT: + sampleEditOpen=true; + break; + case GUI_ACTION_SAMPLE_LIST_UP: + if (--curSample<0) curSample=0; + break; + case GUI_ACTION_SAMPLE_LIST_DOWN: + if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1; + break; + case GUI_ACTION_SAMPLE_LIST_PREVIEW: + e->previewSample(curSample); + break; + case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW: + e->stopSamplePreview(); + break; + + case GUI_ACTION_SAMPLE_SELECT: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + sampleDragMode=false; + break; + case GUI_ACTION_SAMPLE_DRAW: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + sampleDragMode=true; + break; + case GUI_ACTION_SAMPLE_CUT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + SAMPLE_OP_BEGIN; + + if (end-start<1) break; + + if (sampleClipboard!=NULL) { + delete[] sampleClipboard; + } + sampleClipboard=new short[end-start]; + sampleClipboardLen=end-start; + memcpy(sampleClipboard,&(sample->data16[start]),end-start); + + e->lockEngine([this,sample,start,end]() { + sample->strip(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + + break; + } + case GUI_ACTION_SAMPLE_COPY: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + SAMPLE_OP_BEGIN; + + if (end-start<1) break; + + if (sampleClipboard!=NULL) { + delete[] sampleClipboard; + } + sampleClipboard=new short[end-start]; + sampleClipboardLen=end-start; + memcpy(sampleClipboard,&(sample->data16[start]),end-start); + break; + } + case GUI_ACTION_SAMPLE_PASTE: // TODO!!! + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + break; + case GUI_ACTION_SAMPLE_PASTE_REPLACE: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + break; + case GUI_ACTION_SAMPLE_PASTE_MIX: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + break; + case GUI_ACTION_SAMPLE_SELECT_ALL: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + sampleDragActive=false; + sampleSelStart=0; + sampleSelEnd=sample->samples; + break; + } + case GUI_ACTION_SAMPLE_RESIZE: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleResizeOpt=true; + break; + case GUI_ACTION_SAMPLE_RESAMPLE: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleResampleOpt=true; + break; + case GUI_ACTION_SAMPLE_AMPLIFY: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleAmplifyOpt=true; + break; + case GUI_ACTION_SAMPLE_NORMALIZE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float maxVal=0.0f; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]/32767.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]/127.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_FADE_IN: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*float(i-start)/float(end-start); + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*float(i-start)/float(end-start); + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_FADE_OUT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*float(end-i)/float(end-start); + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*float(end-i)/float(end-start); + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_SILENCE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]=0; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]=0; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_DELETE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->strip(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_TRIM: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->trim(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_REVERSE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]^=sample->data16[ri]; + sample->data16[ri]^=sample->data16[i]; + sample->data16[i]^=sample->data16[ri]; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]^=sample->data8[ri]; + sample->data8[ri]^=sample->data8[i]; + sample->data8[i]^=sample->data8[ri]; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_INVERT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]=-sample->data16[i]; + if (sample->data16[i]==-32768) sample->data16[i]=32767; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]=-sample->data8[i]; + if (sample->data16[i]==-128) sample->data16[i]=127; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_SIGN: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]^=0x8000; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]^=0x80; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_FILTER: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleFilterOpt=true; + break; + case GUI_ACTION_SAMPLE_PREVIEW: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + e->previewSample(curSample); + break; + case GUI_ACTION_SAMPLE_STOP_PREVIEW: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + e->stopSamplePreview(); + break; + case GUI_ACTION_SAMPLE_ZOOM_IN: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + double zoomPercent=100.0/sampleZoom; + zoomPercent+=10.0; + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + updateSampleTex=true; + break; + } + case GUI_ACTION_SAMPLE_ZOOM_OUT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + double zoomPercent=100.0/sampleZoom; + zoomPercent-=10.0; + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + updateSampleTex=true; + break; + } + case GUI_ACTION_SAMPLE_ZOOM_AUTO: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + if (sampleZoomAuto) { + sampleZoom=1.0; + sampleZoomAuto=false; + updateSampleTex=true; + } else { + sampleZoomAuto=true; + updateSampleTex=true; + } + break; + + case GUI_ACTION_ORDERS_UP: + if (e->getOrder()>0) { + e->setOrder(e->getOrder()-1); + } + break; + case GUI_ACTION_ORDERS_DOWN: + if (e->getOrder()song.ordersLen-1) { + e->setOrder(e->getOrder()+1); + } + break; + case GUI_ACTION_ORDERS_LEFT: { + DETERMINE_FIRST; + + do { + orderCursor--; + if (orderCursorsong.chanShow[orderCursor]); + break; + } + case GUI_ACTION_ORDERS_RIGHT: { + DETERMINE_LAST; + + do { + orderCursor++; + if (orderCursor>=lastChannel) { + orderCursor=lastChannel-1; + break; + } + } while (!e->song.chanShow[orderCursor]); + break; + } + case GUI_ACTION_ORDERS_INCREASE: { + if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; + int curOrder=e->getOrder(); + if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { + e->song.orders.ord[orderCursor][curOrder]++; + } + break; + } + case GUI_ACTION_ORDERS_DECREASE: { + if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; + int curOrder=e->getOrder(); + if (e->song.orders.ord[orderCursor][curOrder]>0) { + e->song.orders.ord[orderCursor][curOrder]--; + } + break; + } + case GUI_ACTION_ORDERS_EDIT_MODE: + orderEditMode++; + if (orderEditMode>3) orderEditMode=0; + break; + case GUI_ACTION_ORDERS_LINK: + changeAllOrders=!changeAllOrders; + break; + case GUI_ACTION_ORDERS_ADD: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->addOrder(false,false); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_DUPLICATE: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->addOrder(true,false); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_DEEP_CLONE: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->deepCloneOrder(false); + makeUndo(GUI_UNDO_CHANGE_ORDER); + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + break; + case GUI_ACTION_ORDERS_DUPLICATE_END: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->addOrder(true,true); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_DEEP_CLONE_END: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->deepCloneOrder(true); + makeUndo(GUI_UNDO_CHANGE_ORDER); + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + break; + case GUI_ACTION_ORDERS_REMOVE: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->deleteOrder(); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_MOVE_UP: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->moveOrderUp(); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_MOVE_DOWN: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->moveOrderDown(); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_REPLAY: + e->setOrder(e->getOrder()); + break; + } +} \ No newline at end of file diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp new file mode 100644 index 00000000..5e8d7257 --- /dev/null +++ b/src/gui/editControls.cpp @@ -0,0 +1,338 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "IconsFontAwesome4.h" + +void FurnaceGUI::drawEditControls() { + if (nextWindow==GUI_WINDOW_EDIT_CONTROLS) { + editControlsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!editControlsOpen) return; + switch (settings.controlLayout) { + case 0: // classic + if (ImGui::Begin("Play/Edit Controls",&editControlsOpen)) { + ImGui::Text("Octave"); + ImGui::SameLine(); + if (ImGui::InputInt("##Octave",&curOctave,1,1)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Edit Step"); + ImGui::SameLine(); + if (ImGui::InputInt("##EditStep",&editStep,1,1)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + ImGui::SameLine(); + ImGui::Checkbox("Edit",&edit); + ImGui::SameLine(); + bool metro=e->getMetronome(); + if (ImGui::Checkbox("Metronome",&metro)) { + e->setMetronome(metro); + } + + ImGui::Text("Follow"); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Orders",&followOrders)); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); + + bool repeatPattern=e->getRepeatPattern(); + if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { + e->setRepeatPattern(repeatPattern); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + case 1: // compact + if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + + ImGui::SameLine(); + bool repeatPattern=e->getRepeatPattern(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { + e->setRepeatPattern(!repeatPattern); + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { + edit=!edit; + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + bool metro=e->getMetronome(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { + e->setMetronome(!metro); + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::Text("Octave"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(96.0f*dpiScale); + if (ImGui::InputInt("##Octave",&curOctave,1,1)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::SameLine(); + ImGui::Text("Edit Step"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(96.0f*dpiScale); + if (ImGui::InputInt("##EditStep",&editStep,1,1)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::SameLine(); + ImGui::Text("Follow"); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Orders",&followOrders)); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + case 2: // compact vertical + if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + ImGui::PopStyleColor(); + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + + bool repeatPattern=e->getRepeatPattern(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { + e->setRepeatPattern(!repeatPattern); + } + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { + edit=!edit; + } + ImGui::PopStyleColor(); + + bool metro=e->getMetronome(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { + e->setMetronome(!metro); + } + ImGui::PopStyleColor(); + + ImGui::Text("Oct."); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##Octave",&curOctave,0,0)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Step"); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##EditStep",&editStep,0,0)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Foll."); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders)); + if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant + followOrders=!followOrders; + } + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern)); + if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant + followPattern=!followPattern; + } + ImGui::PopStyleColor(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + case 3: // split + if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (e->isPlaying()) { + ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]); + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + ImGui::PopStyleColor(); + } else { + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PLAY_CIRCLE "##PlayAgain")) { + play(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); + if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { + edit=!edit; + } + ImGui::PopStyleColor(); + + bool metro=e->getMetronome(); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); + if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { + e->setMetronome(!metro); + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + bool repeatPattern=e->getRepeatPattern(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); + if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { + e->setRepeatPattern(!repeatPattern); + } + ImGui::PopStyleColor(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + + if (ImGui::Begin("Edit Controls",&editControlsOpen)) { + ImGui::Columns(2); + ImGui::Text("Octave"); + ImGui::SameLine(); + float cursor=ImGui::GetCursorPosX(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##Octave",&curOctave,1,1)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Step"); + ImGui::SameLine(); + ImGui::SetCursorPosX(cursor); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##EditStep",&editStep,1,1)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + ImGui::NextColumn(); + + unimportant(ImGui::Checkbox("Follow orders",&followOrders)); + unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + } +} \ No newline at end of file diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp new file mode 100644 index 00000000..59c1fc34 --- /dev/null +++ b/src/gui/editing.cpp @@ -0,0 +1,1015 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "../ta-log.h" +#include "guiConst.h" +#include + +#include "actionUtil.h" + +const char* noteNameNormal(short note, short octave) { + if (note==100) { // note cut + return "OFF"; + } else if (note==101) { // note off and envelope release + return "==="; + } else if (note==102) { // envelope release only + return "REL"; + } else if (octave==0 && note==0) { + return "..."; + } + int seek=(note+(signed char)octave*12)+60; + if (seek<0 || seek>=180) { + return "???"; + } + return noteNames[seek]; +} + +void FurnaceGUI::prepareUndo(ActionType action) { + int order=e->getOrder(); + switch (action) { + case GUI_UNDO_CHANGE_ORDER: + oldOrders=e->song.orders; + oldOrdersLen=e->song.ordersLen; + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (int i=0; igetTotalChannelCount(); i++) { + e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); + } + break; + } +} + +void FurnaceGUI::makeUndo(ActionType action) { + bool doPush=false; + UndoStep s; + s.type=action; + s.cursor=cursor; + s.selStart=selStart; + s.selEnd=selEnd; + int order=e->getOrder(); + s.order=order; + s.nibble=curNibble; + switch (action) { + case GUI_UNDO_CHANGE_ORDER: + for (int i=0; isong.orders.ord[i][j]) { + s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); + } + } + } + s.oldOrdersLen=oldOrdersLen; + s.newOrdersLen=e->song.ordersLen; + if (oldOrdersLen!=e->song.ordersLen) { + doPush=true; + } + if (!s.ord.empty()) { + doPush=true; + } + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (int i=0; igetTotalChannelCount(); i++) { + DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); + for (int j=0; jsong.patLen; j++) { + for (int k=0; k<32; k++) { + if (p->data[j][k]!=oldPat[i]->data[j][k]) { + s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][order],j,k,oldPat[i]->data[j][k],p->data[j][k])); + } + } + } + } + if (!s.pat.empty()) { + doPush=true; + } + break; + } + if (doPush) { + MARK_MODIFIED; + undoHist.push_back(s); + redoHist.clear(); + if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); + } +} + +void FurnaceGUI::doSelectAll() { + finishSelection(); + curNibble=false; + if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectRows*2) { + if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern + selStart.xCoarse=0; + selStart.xFine=0; + selEnd.xCoarse=e->getTotalChannelCount()-1; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; + } else { // select entire column + selStart.y=0; + selEnd.y=e->song.patLen-1; + } + } else { + int selStartX=0; + int selEndX=0; + // find row position + for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { + i.xFine++; + if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { + i.xFine=0; + i.xCoarse++; + } + } + for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { + i.xFine++; + if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { + i.xFine=0; + i.xCoarse++; + } + } + + float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); + if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down + selStart.y=0; + selEnd.y=e->song.patLen-1; + } else { // left-right + selStart.xFine=0; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; + } + } +} + +#define maskOut(x) \ + if (x==0) { \ + if (!opMaskNote) continue; \ + } else if (x==1) { \ + if (!opMaskIns) continue; \ + } else if (x==2) { \ + if (!opMaskVol) continue; \ + } else if (((x)&1)==0) { \ + if (!opMaskEffectVal) continue; \ + } else if (((x)&1)==1) { \ + if (!opMaskEffect) continue; \ + } + +void FurnaceGUI::doDelete() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_DELETE); + curNibble=false; + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine]=0; + if (selStart.y==selEnd.y) pat->data[j][2]=-1; + } + pat->data[j][iFine+1]=(iFine<1)?0:-1; + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_DELETE); +} + +void FurnaceGUI::doPullDelete() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_PULL); + curNibble=false; + + if (settings.pullDeleteBehavior) { + if (--selStart.y<0) selStart.y=0; + if (--selEnd.y<0) selEnd.y=0; + if (--cursor.y<0) cursor.y=0; + updateScroll(cursor.y); + } + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen; j++) { + if (jsong.patLen-1) { + if (iFine==0) { + pat->data[j][iFine]=pat->data[j+1][iFine]; + } + pat->data[j][iFine+1]=pat->data[j+1][iFine+1]; + } else { + if (iFine==0) { + pat->data[j][iFine]=0; + } + pat->data[j][iFine+1]=(iFine<1)?0:-1; + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_PULL); +} + +void FurnaceGUI::doInsert() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_PUSH); + curNibble=false; + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { + if (j==selStart.y) { + if (iFine==0) { + pat->data[j][iFine]=0; + } + pat->data[j][iFine+1]=(iFine<1)?0:-1; + } else { + if (iFine==0) { + pat->data[j][iFine]=pat->data[j-1][iFine]; + } + pat->data[j][iFine+1]=pat->data[j-1][iFine+1]; + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_PUSH); +} + +void FurnaceGUI::doTranspose(int amount) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_DELETE); + curNibble=false; + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + int origOctave=(signed char)pat->data[j][1]; + if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) { + origNote+=amount; + while (origNote>12) { + origNote-=12; + origOctave++; + } + while (origNote<1) { + origNote+=12; + origOctave--; + } + if (origOctave==9 && origNote>11) { + origNote=11; + origOctave=9; + } + if (origOctave>9) { + origNote=11; + origOctave=9; + } + if (origOctave<-5) { + origNote=1; + origOctave=-5; + } + pat->data[j][0]=origNote; + pat->data[j][1]=(unsigned char)origOctave; + } + } else { + int top=255; + if (iFine==1) { + if (e->song.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_DELETE); +} + +void FurnaceGUI::doCopy(bool cut) { + finishSelection(); + if (cut) { + curNibble=false; + prepareUndo(GUI_UNDO_PATTERN_CUT); + } + clipboard=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,selStart.xFine); + + for (int j=selStart.y; j<=selEnd.y; j++) { + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + if (iFine>3 && !(iFine&1)) { + iFine--; + } + int ord=e->getOrder(); + clipboard+='\n'; + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0],pat->data[j][1]); + if (cut) { + pat->data[j][0]=0; + pat->data[j][1]=0; + } + } else { + if (pat->data[j][iFine+1]==-1) { + clipboard+=".."; + } else { + clipboard+=fmt::sprintf("%.2X",pat->data[j][iFine+1]); + } + if (cut) { + pat->data[j][iFine+1]=-1; + } + } + } + clipboard+='|'; + iFine=0; + } + } + SDL_SetClipboardText(clipboard.c_str()); + + if (cut) { + makeUndo(GUI_UNDO_PATTERN_CUT); + } +} + +void FurnaceGUI::doPaste(PasteMode mode) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_PASTE); + char* clipText=SDL_GetClipboardText(); + if (clipText!=NULL) { + if (clipText[0]) { + clipboard=clipText; + } + SDL_free(clipText); + } + std::vector data; + String tempS; + for (char i: clipboard) { + if (i=='\r') continue; + if (i=='\n') { + data.push_back(tempS); + tempS=""; + continue; + } + tempS+=i; + } + data.push_back(tempS); + + int startOff=-1; + bool invalidData=false; + if (data.size()<2) return; + if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; + if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; + if (startOff<0) return; + + DETERMINE_LAST; + + int j=cursor.y; + char note[4]; + int ord=e->getOrder(); + for (size_t i=2; isong.patLen; i++) { + size_t charPos=0; + int iCoarse=cursor.xCoarse; + int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; + + String& line=data[i]; + + while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + if (line[charPos]=='|') { + iCoarse++; + if (iCoarsesong.chanShow[iCoarse]) { + iCoarse++; + if (iCoarse>=lastChannel) break; + } + iFine=0; + charPos++; + continue; + } + if (iFine==0) { + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[0]=line[charPos++]; + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[1]=line[charPos++]; + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[2]=line[charPos++]; + note[3]=0; + + if (iFine==0 && !opMaskNote) { + iFine++; + continue; + } + + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { + // do nothing. + } else { + if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { + if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { + invalidData=true; + break; + } + } + } + } else { + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[0]=line[charPos++]; + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[1]=line[charPos++]; + note[2]=0; + + if (iFine==1) { + if (!opMaskIns) { + iFine++; + continue; + } + } else if (iFine==2) { + if (!opMaskVol) { + iFine++; + continue; + } + } else if ((iFine&1)==0) { + if (!opMaskEffectVal) { + iFine++; + continue; + } + } else if ((iFine&1)==1) { + if (!opMaskEffect) { + iFine++; + continue; + } + } + + if (strcmp(note,"..")==0) { + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { + pat->data[j][iFine+1]=-1; + } + } else { + unsigned int val=0; + if (sscanf(note,"%2X",&val)!=1) { + invalidData=true; + break; + } + if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { + if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + } + } + } + iFine++; + } + + if (invalidData) { + logW("invalid clipboard data! failed at line %d char %d\n",i,charPos); + logW("%s\n",line.c_str()); + break; + } + j++; + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { + j=0; + ord++; + } + + if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { + i=1; + } + } + + makeUndo(GUI_UNDO_PATTERN_PASTE); +} + +void FurnaceGUI::doChangeIns(int ins) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); + + int iCoarse=selStart.xCoarse; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { + pat->data[j][2]=ins; + } + } + } + + makeUndo(GUI_UNDO_PATTERN_CHANGE_INS); +} + +void FurnaceGUI::doInterpolate() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + + std::vector> points; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine+1]!=-1) { + points.emplace(points.end(),j,pat->data[j][iFine+1]); + } + } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + } + } + } else { + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { + if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { + points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); + } + } + } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + pat->data[k+curPoint.first][0]=val%12; + pat->data[k+curPoint.first][1]=val/12; + if (pat->data[k+curPoint.first][0]==0) { + pat->data[k+curPoint.first][0]=12; + pat->data[k+curPoint.first][1]--; + } + pat->data[k+curPoint.first][1]&=255; + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); +} + +void FurnaceGUI::doFade(int p0, int p1, bool mode) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_FADE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + if (selEnd.y-selStart.y<1) continue; + for (int j=selStart.y; j<=selEnd.y; j++) { + double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); + int value=p0+double(p1-p0)*fraction; + if (mode) { // nibble + value&=15; + pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); + } else { // byte + pat->data[j][iFine+1]=MIN(absoluteTop,value); + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_FADE); +} + +void FurnaceGUI::doInvertValues() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); +} + +void FurnaceGUI::doScale(float top) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_SCALE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_SCALE); +} + +void FurnaceGUI::doRandomize(int bottom, int top, bool mode) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + int value=0; + int value2=0; + if (top-bottom<=0) { + value=MIN(absoluteTop,bottom); + value2=MIN(absoluteTop,bottom); + } else { + value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + } + if (mode) { + value&=15; + value2&=15; + pat->data[j][iFine+1]=value|(value2<<4); + } else { + pat->data[j][iFine+1]=value; + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); +} + +void FurnaceGUI::doFlip() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_FLIP); + + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (iFine==0) { + pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; + } + pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_FLIP); +} + +void FurnaceGUI::doCollapse(int divider) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); + + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=selEnd.y-selStart.y; j++) { + if (j*divider>=selEnd.y-selStart.y) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + } else { + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; + + if (iFine==0) { + for (int k=1; k=selEnd.y-selStart.y) break; + if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; + pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + } + } else { + for (int k=1; k=selEnd.y-selStart.y) break; + if (pat->data[j+selStart.y][iFine+1]!=-1) break; + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + } + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_COLLAPSE); +} + +void FurnaceGUI::doExpand(int multiplier) { + if (multiplier<1) return; + + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_EXPAND); + + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { + if ((j+selStart.y)>=e->song.patLen) break; + if ((j%multiplier)!=0) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + continue; + } + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_EXPAND); +} + +void FurnaceGUI::doUndo() { + if (undoHist.empty()) return; + UndoStep& us=undoHist.back(); + redoHist.push_back(us); + MARK_MODIFIED; + + switch (us.type) { + case GUI_UNDO_CHANGE_ORDER: + e->song.ordersLen=us.oldOrdersLen; + for (UndoOrderData& i: us.ord) { + e->song.orders.ord[i.chan][i.ord]=i.oldVal; + } + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (UndoPatternData& i: us.pat) { + DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + p->data[i.row][i.col]=i.oldVal; + } + if (!e->isPlaying()) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + e->setOrder(us.order); + } + break; + } + + undoHist.pop_back(); +} + +void FurnaceGUI::doRedo() { + if (redoHist.empty()) return; + UndoStep& us=redoHist.back(); + undoHist.push_back(us); + MARK_MODIFIED; + + switch (us.type) { + case GUI_UNDO_CHANGE_ORDER: + e->song.ordersLen=us.newOrdersLen; + for (UndoOrderData& i: us.ord) { + e->song.orders.ord[i.chan][i.ord]=i.newVal; + } + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (UndoPatternData& i: us.pat) { + DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + p->data[i.row][i.col]=i.newVal; + } + if (!e->isPlaying()) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + e->setOrder(us.order); + } + + break; + } + + redoHist.pop_back(); +} \ No newline at end of file diff --git a/src/gui/fonts.h b/src/gui/fonts.h index 8f808a06..a0a42d25 100644 --- a/src/gui/fonts.h +++ b/src/gui/fonts.h @@ -46,3 +46,8 @@ extern const unsigned int builtinFontLen[]; extern const unsigned int* builtinFontM[]; extern const unsigned int builtinFontMLen[]; #endif + +// "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"" +// not just that. somebody rewrite it already so I can load these glyphs at run-time and support +// all languages at once, instead of having to load Unifont's 65k+ characters and blow the GPU up in the +// process! \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ddf9a55a..f32e6eba 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -20,15 +20,12 @@ #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" -#include "debug.h" -#include "fonts.h" #include "icon.h" #include "../ta-log.h" #include "../fileutils.h" #include "imgui.h" #include "imgui_impl_sdl.h" #include "imgui_impl_sdlrenderer.h" -#include "imgui_internal.h" #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" @@ -52,13 +49,17 @@ extern "C" { #include #include "../utfutils.h" #define LAYOUT_INI "\\layout.ini" +#define BACKUP_FUR "\\backup.fur" #else #include #include #include #define LAYOUT_INI "/layout.ini" +#define BACKUP_FUR "/backup.fur" #endif +#include "actionUtil.h" + bool Particle::update(float frameTime) { pos.x+=speed.x*frameTime; pos.y+=speed.y*frameTime; @@ -73,23 +74,6 @@ void FurnaceGUI::bindEngine(DivEngine* eng) { e=eng; } -const char* noteNameNormal(short note, short octave) { - if (note==100) { // note cut - return "OFF"; - } else if (note==101) { // note off and envelope release - return "==="; - } else if (note==102) { // envelope release only - return "REL"; - } else if (octave==0 && note==0) { - return "..."; - } - int seek=(note+(signed char)octave*12)+60; - if (seek<0 || seek>=180) { - return "???"; - } - return noteNames[seek]; -} - const char* FurnaceGUI::noteName(short note, short octave) { if (note==100) { return "OFF"; @@ -676,2917 +660,6 @@ float FurnaceGUI::calcBPM(int s1, int s2, float hz) { return 120.0f*hz/(timeBase*hl*speedSum); } -void FurnaceGUI::drawEditControls() { - if (nextWindow==GUI_WINDOW_EDIT_CONTROLS) { - editControlsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!editControlsOpen) return; - switch (settings.controlLayout) { - case 0: // classic - if (ImGui::Begin("Play/Edit Controls",&editControlsOpen)) { - ImGui::Text("Octave"); - ImGui::SameLine(); - if (ImGui::InputInt("##Octave",&curOctave,1,1)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Edit Step"); - ImGui::SameLine(); - if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - ImGui::SameLine(); - ImGui::Checkbox("Edit",&edit); - ImGui::SameLine(); - bool metro=e->getMetronome(); - if (ImGui::Checkbox("Metronome",&metro)) { - e->setMetronome(metro); - } - - ImGui::Text("Follow"); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Orders",&followOrders)); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Pattern",&followPattern)); - - bool repeatPattern=e->getRepeatPattern(); - if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { - e->setRepeatPattern(repeatPattern); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - case 1: // compact - if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - - ImGui::SameLine(); - bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { - e->setRepeatPattern(!repeatPattern); - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { - edit=!edit; - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { - e->setMetronome(!metro); - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - ImGui::Text("Octave"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(96.0f*dpiScale); - if (ImGui::InputInt("##Octave",&curOctave,1,1)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::SameLine(); - ImGui::Text("Edit Step"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(96.0f*dpiScale); - if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::SameLine(); - ImGui::Text("Follow"); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Orders",&followOrders)); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Pattern",&followPattern)); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - case 2: // compact vertical - if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - - bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { - e->setRepeatPattern(!repeatPattern); - } - ImGui::PopStyleColor(); - - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { - edit=!edit; - } - ImGui::PopStyleColor(); - - bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { - e->setMetronome(!metro); - } - ImGui::PopStyleColor(); - - ImGui::Text("Oct."); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##Octave",&curOctave,0,0)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Step"); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##EditStep",&editStep,0,0)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Foll."); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant - followOrders=!followOrders; - } - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant - followPattern=!followPattern; - } - ImGui::PopStyleColor(); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - case 3: // split - if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (e->isPlaying()) { - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - } else { - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_PLAY_CIRCLE "##PlayAgain")) { - play(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { - edit=!edit; - } - ImGui::PopStyleColor(); - - bool metro=e->getMetronome(); - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { - e->setMetronome(!metro); - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { - e->setRepeatPattern(!repeatPattern); - } - ImGui::PopStyleColor(); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - - if (ImGui::Begin("Edit Controls",&editControlsOpen)) { - ImGui::Columns(2); - ImGui::Text("Octave"); - ImGui::SameLine(); - float cursor=ImGui::GetCursorPosX(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##Octave",&curOctave,1,1)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Step"); - ImGui::SameLine(); - ImGui::SetCursorPosX(cursor); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - ImGui::NextColumn(); - - unimportant(ImGui::Checkbox("Follow orders",&followOrders)); - unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - } -} - -void FurnaceGUI::drawSongInfo() { - if (nextWindow==GUI_WINDOW_SONG_INFO) { - songInfoOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!songInfoOpen) return; - if (ImGui::Begin("Song Information",&songInfoOpen)) { - if (ImGui::BeginTable("NameAuthor",2,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Name"); - ImGui::TableNextColumn(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Name",&e->song.name)) updateWindowTitle(); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Author"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - ImGui::InputText("##Author",&e->song.author); - ImGui::EndTable(); - } - - if (ImGui::BeginTable("OtherProps",3,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("TimeBase"); - ImGui::TableNextColumn(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - unsigned char realTB=e->song.timeBase+1; - if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { - if (realTB<1) realTB=1; - if (realTB>16) realTB=16; - e->song.timeBase=realTB-1; - } - ImGui::TableNextColumn(); - ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz)); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Speed"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { - if (e->song.speed1<1) e->song.speed1=1; - if (e->isPlaying()) play(); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { - if (e->song.speed2<1) e->song.speed2=1; - if (e->isPlaying()) play(); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Highlight"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Pattern Length"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - int patLen=e->song.patLen; - if (ImGui::InputInt("##PatLength",&patLen,1,3)) { - if (patLen<1) patLen=1; - if (patLen>256) patLen=256; - e->song.patLen=patLen; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Song Length"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - int ordLen=e->song.ordersLen; - if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { - if (ordLen<1) ordLen=1; - if (ordLen>127) ordLen=127; - e->song.ordersLen=ordLen; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { - tempoView=!tempoView; - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - float setHz=tempoView?e->song.hz*2.5:e->song.hz; - if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { - if (tempoView) setHz/=2.5; - if (setHz<10) setHz=10; - if (setHz>999) setHz=999; - e->setSongRate(setHz,setHz<52); - } - if (tempoView) { - ImGui::TableNextColumn(); - ImGui::Text("= %gHz",e->song.hz); - } else { - if (e->song.hz>=49.98 && e->song.hz<=50.02) { - ImGui::TableNextColumn(); - ImGui::Text("PAL"); - } - if (e->song.hz>=59.9 && e->song.hz<=60.11) { - ImGui::TableNextColumn(); - ImGui::Text("NTSC"); - } - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Tuning (A-4)"); - ImGui::TableNextColumn(); - float tune=e->song.tuning; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { - if (tune<220.0f) tune=220.0f; - if (tune>880.0f) tune=880.0f; - e->song.tuning=tune; - } - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; - ImGui::End(); -} - -void FurnaceGUI::drawInsList() { - if (nextWindow==GUI_WINDOW_INS_LIST) { - insListOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!insListOpen) return; - if (ImGui::Begin("Instruments",&insListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { - doAction(GUI_ACTION_INS_LIST_ADD); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) { - doAction(GUI_ACTION_INS_LIST_DUPLICATE); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { - doAction(GUI_ACTION_INS_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { - doAction(GUI_ACTION_INS_LIST_SAVE); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_INS_LIST_MOVE_UP); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##InsDelete")) { - doAction(GUI_ACTION_INS_LIST_DELETE); - } - ImGui::Separator(); - if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { - if (settings.unifiedDataView) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_TASKS " Instruments"); - ImGui::Indent(); - } - - for (int i=0; i<(int)e->song.ins.size(); i++) { - DivInstrument* ins=e->song.ins[i]; - String name; - switch (ins->type) { - case DIV_INS_FM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_STD: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_GB: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_C64: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AMIGA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_PCE: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); - name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AY8930: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_TIA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SAA1099: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VIC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_PET: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VRC6: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPLL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_FDS: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); - name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VBOY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); - name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_N163: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SCC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPZ: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_POKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_BEEPER: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SWAN: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_MIKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VERA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_X1_010: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - default: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); - name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); - break; - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(name.c_str(),curIns==i)) { - curIns=i; - } - if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { - nextWindow=GUI_WINDOW_PATTERN; - curIns=i; - } - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - insEditOpen=true; - nextWindow=GUI_WINDOW_INS_EDIT; - } - } - } - - if (settings.unifiedDataView) { - ImGui::Unindent(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); - ImGui::Indent(); - actualWaveList(); - ImGui::Unindent(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_VOLUME_UP " Samples"); - ImGui::Indent(); - actualSampleList(); - ImGui::Unindent(); - } - - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; - ImGui::End(); -} - -const char* sampleNote[12]={ - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" -}; - - -void FurnaceGUI::actualWaveList() { - float wavePreview[256]; - for (int i=0; i<(int)e->song.wave.size(); i++) { - DivWavetable* wave=e->song.wave[i]; - for (int i=0; ilen; i++) { - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { - curWave=i; - } - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - waveEditOpen=true; - } - } - ImGui::SameLine(); - PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); - } -} - -void FurnaceGUI::actualSampleList() { - for (int i=0; i<(int)e->song.sample.size(); i++) { - DivSample* sample=e->song.sample[i]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { - curSample=i; - updateSampleTex=true; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - sampleEditOpen=true; - } - } - } -} - -void FurnaceGUI::drawSampleList() { - if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { - sampleListOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!sampleListOpen) return; - if (ImGui::Begin("Samples",&sampleListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { - doAction(GUI_ACTION_SAMPLE_LIST_ADD); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { - doAction(GUI_ACTION_SAMPLE_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { - doAction(GUI_ACTION_SAMPLE_LIST_SAVE); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { - doAction(GUI_ACTION_SAMPLE_LIST_DELETE); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSampleL")) { - doAction(GUI_ACTION_SAMPLE_LIST_PREVIEW); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSampleL")) { - doAction(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); - } - ImGui::Separator(); - if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { - actualSampleList(); - ImGui::EndTable(); - } - ImGui::Unindent(); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; - ImGui::End(); -} - -void FurnaceGUI::drawMixer() { - if (nextWindow==GUI_WINDOW_MIXER) { - mixerOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!mixerOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - char id[32]; - if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { - if (e->song.masterVol<0) e->song.masterVol=0; - if (e->song.masterVol>3) e->song.masterVol=3; - } rightClickable - for (int i=0; isong.systemLen; i++) { - snprintf(id,31,"MixS%d",i); - bool doInvert=e->song.systemVol[i]&128; - signed char vol=e->song.systemVol[i]&127; - ImGui::PushID(id); - ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); - ImGui::SameLine(ImGui::GetWindowWidth()-(82.0f*dpiScale)); - if (ImGui::Checkbox("Invert",&doInvert)) { - e->song.systemVol[i]^=128; - } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { - e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol; - } rightClickable - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable - - ImGui::PopID(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MIXER; - ImGui::End(); -} - -void FurnaceGUI::drawOsc() { - if (nextWindow==GUI_WINDOW_OSCILLOSCOPE) { - oscOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!oscOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); - if (ImGui::Begin("Oscilloscope",&oscOpen)) { - float values[512]; - for (int i=0; i<512; i++) { - int pos=i*e->oscSize/512; - values[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; - } - //ImGui::SetCursorPos(ImVec2(0,0)); - ImGui::BeginDisabled(); - ImGui::PlotLines("##SingleOsc",values,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); - ImGui::EndDisabled(); - } - ImGui::PopStyleVar(3); - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE; - ImGui::End(); -} - -void FurnaceGUI::drawVolMeter() { - if (nextWindow==GUI_WINDOW_VOL_METER) { - volMeterOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!volMeterOpen) return; - if (--isClipping<0) isClipping=0; - ImGui::SetNextWindowSizeConstraints(ImVec2(6.0f*dpiScale,6.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); - if (ImGui::Begin("Volume Meter",&volMeterOpen)) { - ImDrawList* dl=ImGui::GetWindowDrawList(); - bool aspectRatio=(ImGui::GetWindowSize().x/ImGui::GetWindowSize().y)>1.0; - - ImVec2 minArea=ImVec2( - ImGui::GetWindowPos().x+ImGui::GetCursorPos().x, - ImGui::GetWindowPos().y+ImGui::GetCursorPos().y - ); - ImVec2 maxArea=ImVec2( - ImGui::GetWindowPos().x+ImGui::GetCursorPos().x+ImGui::GetContentRegionAvail().x, - ImGui::GetWindowPos().y+ImGui::GetCursorPos().y+ImGui::GetContentRegionAvail().y - ); - ImRect rect=ImRect(minArea,maxArea); - ImGuiStyle& style=ImGui::GetStyle(); - ImGui::ItemSize(ImVec2(4.0f,4.0f),style.FramePadding.y); - ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_LOW]); - float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; - if (ImGui::ItemAdd(rect,ImGui::GetID("volMeter"))) { - ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); - for (int i=0; i<2; i++) { - peak[i]*=1.0-peakDecay; - if (peak[i]<0.0001) peak[i]=0.0; - for (int j=0; joscSize; j++) { - if (fabs(e->oscBuf[i][j])>peak[i]) { - peak[i]=fabs(e->oscBuf[i][j]); - } - } - float logPeak=(20*log10(peak[i])/36.0); - if (logPeak==NAN) logPeak=0.0; - if (logPeak<-1.0) logPeak=-1.0; - if (logPeak>0.0) { - isClipping=8; - logPeak=0.0; - } - logPeak+=1.0; - ImU32 highColor=ImGui::GetColorU32( - ImLerp(uiColors[GUI_COLOR_VOLMETER_LOW],uiColors[GUI_COLOR_VOLMETER_HIGH],logPeak) - ); - ImRect s; - if (aspectRatio) { - s=ImRect( - ImLerp(rect.Min,rect.Max,ImVec2(0,float(i)*0.5)), - ImLerp(rect.Min,rect.Max,ImVec2(logPeak,float(i+1)*0.5)) - ); - if (i==0) s.Max.y-=dpiScale; - if (isClipping) { - dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); - } else { - dl->AddRectFilledMultiColor(s.Min,s.Max,lowColor,highColor,highColor,lowColor); - } - } else { - s=ImRect( - ImLerp(rect.Min,rect.Max,ImVec2(float(i)*0.5,1.0-logPeak)), - ImLerp(rect.Min,rect.Max,ImVec2(float(i+1)*0.5,1.0)) - ); - if (i==0) s.Max.x-=dpiScale; - if (isClipping) { - dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); - } else { - dl->AddRectFilledMultiColor(s.Min,s.Max,highColor,highColor,lowColor,lowColor); - } - } - } - if (ImGui::IsItemHovered()) { - if (aspectRatio) { - ImGui::SetTooltip("%.1fdB",36*((ImGui::GetMousePos().x-ImGui::GetItemRectMin().x)/(rect.Max.x-rect.Min.x)-1.0)); - } else { - ImGui::SetTooltip("%.1fdB",-(36+36*((ImGui::GetMousePos().y-ImGui::GetItemRectMin().y)/(rect.Max.y-rect.Min.y)-1.0))); - } - } - } - } - ImGui::PopStyleVar(4); - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_VOL_METER; - ImGui::End(); -} - -const char* aboutLine[]={ - "tildearrow", - "is proud to present", - "", - ("Furnace " DIV_VERSION), - "", - "the free software chiptune tracker,", - "compatible with DefleMask modules.", - "", - "zero disassembly.", - "just clean-room design,", - "time and dedication.", - "", - "> CREDITS <", - "", - "-- program --", - "tildearrow", - "akumanatt", - "cam900", - "djtuBIG-MaliceX", - "laoo", - "superctr", - "", - "-- graphics/UI design --", - "tildearrow", - "BlastBrothers", - "", - "-- documentation --", - "tildearrow", - "freq-mod", - "nicco1690", - "DeMOSic", - "cam900", - "", - "-- demo songs --", - "0x5066", - "ActualNK358", - "breakthetargets", - "CaptainMalware", - "kleeder", - "Mahbod Karamoozian", - "nicco1690", - "NikonTeen", - "SuperJet Spade", - "TheDuccinator", - "TheRealHedgehogSonic", - "tildearrow", - "Ultraprogramer", - "", - "-- additional feedback/fixes --", - "fd", - "OPNA2608", - "plane", - "TheEssem", - "", - "powered by:", - "Dear ImGui by Omar Cornut", - "SDL2 by Sam Lantinga", - "zlib by Jean-loup Gailly", - "and Mark Adler", - "libsndfile by Erik de Castro Lopo", - "Nuked-OPM & Nuked-OPN2 by Nuke.YKT", - "ymfm by Aaron Giles", - "MAME SN76496 by Nicola Salmoria", - "MAME AY-3-8910 by Couriersud", - "with AY8930 fixes by Eulous", - "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", - "SAASound", - "SameBoy by Lior Halphon", - "Mednafen PCE", - "puNES by FHorse", - "reSID by Dag Lem", - "Stella by Stella Team", - "QSound emulator by Ian Karlsson and Valley Bell", - "", - "greetings to:", - "Delek", - "fd", - "ILLUMIDARO", - "all members of Deflers of Noice!", - "", - "copyright © 2021-2022 tildearrow", - "(and contributors).", - "licensed under GPLv2+! see", - "LICENSE for more information.", - "", - "help Furnace grow:", - "https://github.com/tildearrow/furnace", - "", - "contact tildearrow at:", - "https://tildearrow.org/?p=contact", - "", - "disclaimer:", - "despite the fact this program works", - "with the .dmf file format, it is NOT", - "affiliated with Delek or DefleMask in", - "any way, nor it is a replacement for", - "the original program.", - "", - "it also comes with ABSOLUTELY NO WARRANTY.", - "", - "thanks to all contributors/bug reporters!" -}; - -const size_t aboutCount = sizeof(aboutLine)/sizeof(aboutLine[0]); - -void FurnaceGUI::drawAbout() { - // do stuff - if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) { - ImGui::SetWindowPos(ImVec2(0,0)); - ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushFont(bigFont); - ImDrawList* dl=ImGui::GetWindowDrawList(); - float r=0; - float g=0; - float b=0; - float peakMix=settings.partyTime?((peak[0]+peak[1])*0.5):0.3; - ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.25+MIN(0.75f,peakMix*0.75f),r,g,b); - dl->AddRectFilled(ImVec2(0,0),ImVec2(scrW*dpiScale,scrH*dpiScale),0xff000000); - bool skip=false; - bool skip2=false; - for (int i=(-80-sin(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.25,g*0.25,b*0.25,1.0))); - } - } - - skip=false; - skip2=false; - for (int i=(-80-cos(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.5,g*0.5,b*0.5,1.0))); - } - } - - skip=false; - skip2=false; - for (int i=(-160+fmod(aboutSin*2,160))*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.75,g*0.75,b*0.75,1.0))); - } - } - - for (size_t i=0; iscrH*dpiScale) continue; - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX+dpiScale,posY+dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX+dpiScale,posY-dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX-dpiScale,posY+dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX-dpiScale,posY-dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX,posY), - 0xffffffff,aboutLine[i]); - } - ImGui::PopFont(); - - float timeScale=60.0f*ImGui::GetIO().DeltaTime; - - aboutHue+=(0.001+peakMix*0.004)*timeScale; - aboutScroll+=(2+(peakMix>0.78)*3)*timeScale; - aboutSin+=(1+(peakMix>0.75)*2)*timeScale; - - while (aboutHue>1) aboutHue--; - while (aboutSin>=2400) aboutSin-=2400; - if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20; - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT; - ImGui::End(); -} - -void FurnaceGUI::drawDebug() { - static int bpOrder; - static int bpRow; - static int bpTick; - static bool bpOn; - if (nextWindow==GUI_WINDOW_DEBUG) { - debugOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!debugOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Debug",&debugOpen,ImGuiWindowFlags_NoDocking)) { - ImGui::Text("NOTE: use with caution."); - if (ImGui::TreeNode("Debug Controls")) { - if (e->isHalted()) { - if (ImGui::Button("Resume")) e->resume(); - } else { - if (ImGui::Button("Pause")) e->halt(); - } - ImGui::SameLine(); - if (ImGui::Button("Frame Advance")) e->haltWhen(DIV_HALT_TICK); - ImGui::SameLine(); - if (ImGui::Button("Row Advance")) e->haltWhen(DIV_HALT_ROW); - ImGui::SameLine(); - if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); - - if (ImGui::Button("Panic")) e->syncReset(); - ImGui::SameLine(); - if (ImGui::Button("Abort")) { - abort(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Breakpoint")) { - ImGui::InputInt("Order",&bpOrder); - ImGui::InputInt("Row",&bpRow); - ImGui::InputInt("Tick",&bpTick); - ImGui::Checkbox("Enable",&bpOn); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Dispatch Status")) { - ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); - ImGui::Columns(e->getTotalChannelCount()); - for (int i=0; igetTotalChannelCount(); i++) { - void* ch=e->getDispatchChanState(i); - ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->dispatchOfChan[i],e->dispatchChanOfChan[i]); - if (ch==NULL) { - ImGui::Text("NULL"); - } else { - putDispatchChan(ch,e->dispatchChanOfChan[i],e->sysOfChan[i]); - } - ImGui::NextColumn(); - } - ImGui::Columns(); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Playback Status")) { - ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); - ImGui::Columns(e->getTotalChannelCount()); - for (int i=0; igetTotalChannelCount(); i++) { - DivChannelState* ch=e->getChanState(i); - ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Channel %d:",i); - if (ch==NULL) { - ImGui::Text("NULL"); - } else { - ImGui::Text("* General:"); - ImGui::Text("- note = %d",ch->note); - ImGui::Text("- oldNote = %d",ch->oldNote); - ImGui::Text("- pitch = %d",ch->pitch); - ImGui::Text("- portaSpeed = %d",ch->portaSpeed); - ImGui::Text("- portaNote = %d",ch->portaNote); - ImGui::Text("- volume = %.4x",ch->volume); - ImGui::Text("- volSpeed = %d",ch->volSpeed); - ImGui::Text("- cut = %d",ch->cut); - ImGui::Text("- rowDelay = %d",ch->rowDelay); - ImGui::Text("- volMax = %.4x",ch->volMax); - ImGui::Text("- delayOrder = %d",ch->delayOrder); - ImGui::Text("- delayRow = %d",ch->delayRow); - ImGui::Text("- retrigSpeed = %d",ch->retrigSpeed); - ImGui::Text("- retrigTick = %d",ch->retrigTick); - ImGui::PushStyleColor(ImGuiCol_Text,(ch->vibratoDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); - ImGui::Text("* Vibrato:"); - ImGui::Text("- depth = %d",ch->vibratoDepth); - ImGui::Text("- rate = %d",ch->vibratoRate); - ImGui::Text("- pos = %d",ch->vibratoPos); - ImGui::Text("- dir = %d",ch->vibratoDir); - ImGui::Text("- fine = %d",ch->vibratoFine); - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Text,(ch->tremoloDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); - ImGui::Text("* Tremolo:"); - ImGui::Text("- depth = %d",ch->tremoloDepth); - ImGui::Text("- rate = %d",ch->tremoloRate); - ImGui::Text("- pos = %d",ch->tremoloPos); - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Text,(ch->arp>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); - ImGui::Text("* Arpeggio:"); - ImGui::Text("- arp = %.2X",ch->arp); - ImGui::Text("- stage = %d",ch->arpStage); - ImGui::Text("- ticks = %d",ch->arpTicks); - ImGui::PopStyleColor(); - ImGui::Text("* Miscellaneous:"); - ImGui::TextColored(ch->doNote?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Do Note"); - ImGui::TextColored(ch->legato?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Legato"); - ImGui::TextColored(ch->portaStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> PortaStop"); - ImGui::TextColored(ch->keyOn?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key On"); - ImGui::TextColored(ch->keyOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key Off"); - ImGui::TextColored(ch->nowYouCanStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> NowYouCanStop"); - ImGui::TextColored(ch->stopOnOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Stop on Off"); - ImGui::TextColored(ch->arpYield?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Arp Yield"); - ImGui::TextColored(ch->delayLocked?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> DelayLocked"); - ImGui::TextColored(ch->inPorta?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> InPorta"); - ImGui::TextColored(ch->scheduledSlideReset?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> SchedSlide"); - } - ImGui::NextColumn(); - } - ImGui::Columns(); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Playground")) { - if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; - if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { - for (int i=0; isong.systemLen; i++) { - if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { - pgSys=i; - break; - } - } - ImGui::EndCombo(); - } - ImGui::Text("Program"); - if (pgProgram.empty()) { - ImGui::Text("-nothing here-"); - } else { - char id[32]; - for (size_t index=0; indexpoke(pgSys,pgProgram); - } - ImGui::SameLine(); - if (ImGui::Button("Clear")) { - pgProgram.clear(); - } - - ImGui::Text("Address"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(100.0f*dpiScale); - ImGui::InputInt("##PAddress",&pgAddr,0,0,ImGuiInputTextFlags_CharsHexadecimal); - ImGui::SameLine(); - ImGui::Text("Value"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(100.0f*dpiScale); - ImGui::InputInt("##PValue",&pgVal,0,0,ImGuiInputTextFlags_CharsHexadecimal); - ImGui::SameLine(); - if (ImGui::Button("Write")) { - e->poke(pgSys,pgAddr,pgVal); - } - ImGui::SameLine(); - if (ImGui::Button("Add")) { - pgProgram.push_back(DivRegWrite(pgAddr,pgVal)); - } - if (ImGui::TreeNode("Register Cheatsheet")) { - const char** sheet=e->getRegisterSheet(pgSys); - if (sheet==NULL) { - ImGui::Text("no cheatsheet available for this system."); - } else { - if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Name"); - ImGui::TableNextColumn(); - ImGui::Text("Address"); - for (int i=0; sheet[i]!=NULL; i+=2) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s",sheet[i]); - ImGui::TableNextColumn(); - ImGui::Text("$%s",sheet[i+1]); - } - ImGui::EndTable(); - } - } - ImGui::TreePop(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Settings")) { - if (ImGui::Button("Sync")) syncSettings(); - ImGui::SameLine(); - if (ImGui::Button("Commit")) commitSettings(); - ImGui::SameLine(); - if (ImGui::Button("Force Load")) e->loadConf(); - ImGui::SameLine(); - if (ImGui::Button("Force Save")) e->saveConf(); - ImGui::TreePop(); - } - ImGui::Text("Song format version %d",e->song.version); - ImGui::Text("Furnace version " DIV_VERSION " (%d)",DIV_ENGINE_VERSION); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_DEBUG; - ImGui::End(); -} - -void FurnaceGUI::drawNewSong() { - bool accepted=false; - - ImGui::PushFont(bigFont); - ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Choose a System!").x)*0.5); - ImGui::Text("Choose a System!"); - ImGui::PopFont(); - - if (ImGui::BeginTable("sysPicker",2)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); - - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableNextColumn(); - ImGui::Text("Categories"); - ImGui::TableNextColumn(); - ImGui::Text("Systems"); - - ImGui::TableNextRow(); - - // CATEGORIES - ImGui::TableNextColumn(); - int index=0; - for (FurnaceGUISysCategory& i: sysCategories) { - if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ - newSongCategory=index; - } - index++; - } - - // SYSTEMS - ImGui::TableNextColumn(); - if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { - for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { - nextDesc=i.definition.data(); - accepted=true; - } - } - ImGui::EndTable(); - } - - ImGui::EndTable(); - } - - if (ImGui::Button("Cancel")) { - ImGui::CloseCurrentPopup(); - } - - if (accepted) { - e->createNew(nextDesc); - undoHist.clear(); - redoHist.clear(); - curFileName=""; - modified=false; - curNibble=false; - orderNibble=false; - orderCursor=-1; - selStart=SelectionPoint(); - selEnd=SelectionPoint(); - cursor=SelectionPoint(); - updateWindowTitle(); - ImGui::CloseCurrentPopup(); - } -} - -void FurnaceGUI::drawStats() { - if (nextWindow==GUI_WINDOW_STATS) { - statsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!statsOpen) return; - if (ImGui::Begin("Statistics",&statsOpen)) { - String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); - String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); - String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); - String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); - ImGui::Text("ADPCM-A"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); - ImGui::Text("ADPCM-B"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); - ImGui::Text("QSound"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); - ImGui::Text("X1-010"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; - ImGui::End(); -} - -void FurnaceGUI::drawCompatFlags() { - if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { - compatFlagsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!compatFlagsOpen) return; - if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { - ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); - ImGui::Checkbox("Limit slide range",&e->song.limitSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); - } - ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); - } - ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); - } - ImGui::Checkbox("Game Boy instrument duty is wave volume",&e->song.waveDutyIsVol); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("if enabled, an instrument with duty macro in the wave channel will be mapped to wavetable volume."); - } - - ImGui::Checkbox("Restart macro on portamento",&e->song.resetMacroOnPorta); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, a portamento effect will reset the channel's macro if used in combination with a note."); - } - ImGui::Checkbox("Legacy volume slides",&e->song.legacyVolumeSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("simulate glitchy volume slide behavior by silently overflowing the volume when the slide goes below 0."); - } - ImGui::Checkbox("Compatible arpeggio",&e->song.compatibleArpeggio); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("delay arpeggio by one tick on every new note."); - } - ImGui::Checkbox("Reset slides after note off",&e->song.noteOffResetsSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, note off will reset the channel's slide effect."); - } - ImGui::Checkbox("Reset portamento after reaching target",&e->song.targetResetsSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, the slide effect is disabled after it reaches its target."); - } - ImGui::Checkbox("Ignore duplicate slide effects",&e->song.ignoreDuplicateSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("if this is on, only the first slide of a row in a channel will be considered."); - } - ImGui::Checkbox("Continuous vibrato",&e->song.continuousVibrato); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); - } - ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); - } - ImGui::Checkbox("Auto-insert one tick gap between notes",&e->song.oneTickCut); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); - } - - ImGui::Text("Loop modality:"); - if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { - e->song.loopModality=0; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("select to reset channels on loop. may trigger a voltage click on every loop!"); - } - if (ImGui::RadioButton("Soft reset channels",e->song.loopModality==1)) { - e->song.loopModality=1; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("select to turn channels off on loop."); - } - if (ImGui::RadioButton("Do nothing",e->song.loopModality==2)) { - e->song.loopModality=2; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("select to not reset channels on loop."); - } - - ImGui::Separator(); - - ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); - - ImGui::Checkbox("Arpeggio inhibits non-porta slides",&e->song.arpNonPorta); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.5.5"); - } - ImGui::Checkbox("Wack FM algorithm macro",&e->song.algMacroBehavior); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.5.5"); - } - ImGui::Checkbox("Broken shortcut slides (E1xy/E2xy)",&e->song.brokenShortcutSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.5.7"); - } - ImGui::Checkbox("Stop portamento on note off",&e->song.stopPortaOnNoteOff); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); - } - ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); - } - ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; - ImGui::End(); -} - -void FurnaceGUI::drawPiano() { - if (nextWindow==GUI_WINDOW_PIANO) { - pianoOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!pianoOpen) return; - if (ImGui::Begin("Piano",&pianoOpen)) { - for (int i=0; igetTotalChannelCount(); i++) { - DivChannelState* cs=e->getChanState(i); - if (cs->keyOn) { - const char* noteName=NULL; - if (cs->note<-60 || cs->note>120) { - noteName="???"; - } else { - noteName=noteNames[cs->note+60]; - } - ImGui::Text("%d: %s",i,noteName); - } - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PIANO; - ImGui::End(); -} - -// NOTE: please don't ask me to enable text wrap. -// Dear ImGui doesn't have that feature. D: -void FurnaceGUI::drawNotes() { - if (nextWindow==GUI_WINDOW_NOTES) { - notesOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!notesOpen) return; - if (ImGui::Begin("Song Comments",¬esOpen)) { - ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; - ImGui::End(); -} - -void FurnaceGUI::drawChannels() { - if (nextWindow==GUI_WINDOW_CHANNELS) { - channelsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!channelsOpen) return; - if (ImGui::Begin("Channels",&channelsOpen)) { - if (ImGui::BeginTable("ChannelList",3)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); - for (int i=0; igetTotalChannelCount(); i++) { - ImGui::PushID(i); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("##Visible",&e->song.chanShow[i]); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); - ImGui::PopID(); - } - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHANNELS; - ImGui::End(); -} - -void FurnaceGUI::drawRegView() { - if (nextWindow==GUI_WINDOW_REGISTER_VIEW) { - channelsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!regViewOpen) return; - if (ImGui::Begin("Register View",®ViewOpen)) { - for (int i=0; isong.systemLen; i++) { - ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); - int size=0; - int depth=8; - unsigned char* regPool=e->getRegisterPool(i,size,depth); - unsigned short* regPoolW=(unsigned short*)regPool; - if (regPool==NULL) { - ImGui::Text("- no register pool available"); - } else { - ImGui::PushFont(patFont); - if (ImGui::BeginTable("Memory",17)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - for (int i=0; i<16; i++) { - ImGui::TableNextColumn(); - ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX]," %X",i); - } - for (int i=0; i<=((size-1)>>4); i++) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX],"%.2X",i*16); - for (int j=0; j<16; j++) { - ImGui::TableNextColumn(); - if (i*16+j>=size) continue; - if (depth == 8) { - ImGui::Text("%.2x",regPool[i*16+j]); - } else if (depth == 16) { - ImGui::Text("%.4x",regPoolW[i*16+j]); - } else { - ImGui::Text("??"); - } - } - } - ImGui::EndTable(); - } - ImGui::PopFont(); - } - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_REGISTER_VIEW; - ImGui::End(); -} - -void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { - if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) { - curNibble=false; - } - cursor.xCoarse=xCoarse; - cursor.xFine=xFine; - cursor.y=y; - selStart.xCoarse=xCoarse; - selStart.xFine=xFine; - selStart.y=y; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; - selecting=true; -} - -void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { - if (!selecting) return; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; -} - -void FurnaceGUI::finishSelection() { - // swap points if needed - if (selEnd.ygetTotalChannelCount(); - - if (selStart.xCoarse<0) selStart.xCoarse=0; - if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; - if (selStart.y<0) selStart.y=0; - if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; - if (selEnd.xCoarse<0) selEnd.xCoarse=0; - if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; - if (selEnd.y<0) selEnd.y=0; - if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; - if (cursor.xCoarse<0) cursor.xCoarse=0; - if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; - if (cursor.y<0) cursor.y=0; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; - - if (e->song.chanCollapse[selEnd.xCoarse]) { - selStart.xFine=0; - } - if (e->song.chanCollapse[selEnd.xCoarse]) { - selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; - } -} - -#define DETERMINE_FIRST \ - int firstChannel=0; \ - for (int i=0; igetTotalChannelCount(); i++) { \ - if (e->song.chanShow[i]) { \ - firstChannel=i; \ - break; \ - } \ - } \ - -#define DETERMINE_LAST \ - int lastChannel=0; \ - for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ - if (e->song.chanShow[i]) { \ - lastChannel=i+1; \ - break; \ - } \ - } - -#define DETERMINE_FIRST_LAST \ - DETERMINE_FIRST \ - DETERMINE_LAST - -void FurnaceGUI::moveCursor(int x, int y, bool select) { - if (!select) { - finishSelection(); - } - - DETERMINE_FIRST_LAST; - - curNibble=false; - if (x!=0) { - demandScrollX=true; - if (x>0) { - for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?1:(3+e->song.pat[cursor.xCoarse].effectRows*2))) { - cursor.xFine=0; - if (++cursor.xCoarse>=lastChannel) { - if (settings.wrapHorizontal!=0 && !select) { - cursor.xCoarse=firstChannel; - if (settings.wrapHorizontal==2) y++; - } else { - cursor.xCoarse=lastChannel-1; - cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?0:(2+e->song.pat[cursor.xCoarse].effectRows*2); - } - } else { - while (!e->song.chanShow[cursor.xCoarse]) { - cursor.xCoarse++; - if (cursor.xCoarse>=e->getTotalChannelCount()) break; - } - } - } - } - } else { - for (int i=0; i<-x; i++) { - if (--cursor.xFine<0) { - if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectRows*2; - if (settings.wrapHorizontal==2) y--; - } else { - cursor.xCoarse=firstChannel; - cursor.xFine=0; - } - } else { - while (!e->song.chanShow[cursor.xCoarse]) { - cursor.xCoarse--; - if (cursor.xCoarse<0) break; - } - if (e->song.chanCollapse[cursor.xCoarse]) { - cursor.xFine=0; - } else { - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; - } - } - } - } - } - } - if (y!=0) { - if (y>0) { - for (int i=0; i=e->song.patLen) { - if (settings.wrapVertical!=0 && !select) { - cursor.y=0; - if (settings.wrapVertical==2) { - if (!e->isPlaying() && e->getOrder()<(e->song.ordersLen-1)) { - e->setOrder(e->getOrder()+1); - } else { - cursor.y=e->song.patLen-1; - } - } - } else { - cursor.y=e->song.patLen-1; - } - } - } - } else { - for (int i=0; i<-y; i++) { - cursor.y--; - if (cursor.y<0) { - if (settings.wrapVertical!=0 && !select) { - cursor.y=e->song.patLen-1; - if (settings.wrapVertical==2) { - if (!e->isPlaying() && e->getOrder()>0) { - e->setOrder(e->getOrder()-1); - } else { - cursor.y=0; - } - } - } else { - cursor.y=0; - } - } - } - } - } - if (!select) { - selStart=cursor; - } - selEnd=cursor; - updateScroll(cursor.y); -} - -void FurnaceGUI::moveCursorPrevChannel(bool overflow) { - finishSelection(); - curNibble=false; - - DETERMINE_FIRST_LAST; - - do { - cursor.xCoarse--; - if (cursor.xCoarse<0) break; - } while (!e->song.chanShow[cursor.xCoarse]); - if (cursor.xCoarse=e->getTotalChannelCount()) break; - } while (!e->song.chanShow[cursor.xCoarse]); - if (cursor.xCoarse>=lastChannel) { - if (overflow) { - cursor.xCoarse=firstChannel; - } else { - cursor.xCoarse=lastChannel-1; - } - } - - selStart=cursor; - selEnd=cursor; - demandScrollX=true; -} - -void FurnaceGUI::moveCursorTop(bool select) { - finishSelection(); - curNibble=false; - if (cursor.y==0) { - DETERMINE_FIRST; - cursor.xCoarse=firstChannel; - cursor.xFine=0; - demandScrollX=true; - } else { - cursor.y=0; - } - selStart=cursor; - if (!select) { - selEnd=cursor; - } - updateScroll(cursor.y); -} - -void FurnaceGUI::moveCursorBottom(bool select) { - finishSelection(); - curNibble=false; - if (cursor.y==e->song.patLen-1) { - DETERMINE_LAST; - cursor.xCoarse=lastChannel-1; - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; - demandScrollX=true; - } else { - cursor.y=e->song.patLen-1; - } - if (!select) { - selStart=cursor; - } - selEnd=cursor; - updateScroll(cursor.y); -} - -void FurnaceGUI::editAdvance() { - finishSelection(); - cursor.y+=editStep; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; - selStart=cursor; - selEnd=cursor; - updateScroll(cursor.y); -} - -void FurnaceGUI::prepareUndo(ActionType action) { - int order=e->getOrder(); - switch (action) { - case GUI_UNDO_CHANGE_ORDER: - oldOrders=e->song.orders; - oldOrdersLen=e->song.ordersLen; - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (int i=0; igetTotalChannelCount(); i++) { - e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); - } - break; - } -} - -void FurnaceGUI::makeUndo(ActionType action) { - bool doPush=false; - UndoStep s; - s.type=action; - s.cursor=cursor; - s.selStart=selStart; - s.selEnd=selEnd; - int order=e->getOrder(); - s.order=order; - s.nibble=curNibble; - switch (action) { - case GUI_UNDO_CHANGE_ORDER: - for (int i=0; isong.orders.ord[i][j]) { - s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); - } - } - } - s.oldOrdersLen=oldOrdersLen; - s.newOrdersLen=e->song.ordersLen; - if (oldOrdersLen!=e->song.ordersLen) { - doPush=true; - } - if (!s.ord.empty()) { - doPush=true; - } - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); - for (int j=0; jsong.patLen; j++) { - for (int k=0; k<32; k++) { - if (p->data[j][k]!=oldPat[i]->data[j][k]) { - s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][order],j,k,oldPat[i]->data[j][k],p->data[j][k])); - } - } - } - } - if (!s.pat.empty()) { - doPush=true; - } - break; - } - if (doPush) { - modified=true; - undoHist.push_back(s); - redoHist.clear(); - if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); - } -} - -void FurnaceGUI::doSelectAll() { - finishSelection(); - curNibble=false; - if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectRows*2) { - if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern - selStart.xCoarse=0; - selStart.xFine=0; - selEnd.xCoarse=e->getTotalChannelCount()-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; - } else { // select entire column - selStart.y=0; - selEnd.y=e->song.patLen-1; - } - } else { - int selStartX=0; - int selEndX=0; - // find row position - for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { - i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { - i.xFine=0; - i.xCoarse++; - } - } - for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { - i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { - i.xFine=0; - i.xCoarse++; - } - } - - float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); - if (aspect<1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down - selStart.y=0; - selEnd.y=e->song.patLen-1; - } else { // left-right - selStart.xFine=0; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; - } - } -} - -#define maskOut(x) \ - if (x==0) { \ - if (!opMaskNote) continue; \ - } else if (x==1) { \ - if (!opMaskIns) continue; \ - } else if (x==2) { \ - if (!opMaskVol) continue; \ - } else if (((x)&1)==0) { \ - if (!opMaskEffectVal) continue; \ - } else if (((x)&1)==1) { \ - if (!opMaskEffect) continue; \ - } - -void FurnaceGUI::doDelete() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_DELETE); - curNibble=false; - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine]=0; - if (selStart.y==selEnd.y) pat->data[j][2]=-1; - } - pat->data[j][iFine+1]=(iFine<1)?0:-1; - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_DELETE); -} - -void FurnaceGUI::doPullDelete() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_PULL); - curNibble=false; - - if (settings.pullDeleteBehavior) { - if (--selStart.y<0) selStart.y=0; - if (--selEnd.y<0) selEnd.y=0; - if (--cursor.y<0) cursor.y=0; - updateScroll(cursor.y); - } - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen; j++) { - if (jsong.patLen-1) { - if (iFine==0) { - pat->data[j][iFine]=pat->data[j+1][iFine]; - } - pat->data[j][iFine+1]=pat->data[j+1][iFine+1]; - } else { - if (iFine==0) { - pat->data[j][iFine]=0; - } - pat->data[j][iFine+1]=(iFine<1)?0:-1; - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_PULL); -} - -void FurnaceGUI::doInsert() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_PUSH); - curNibble=false; - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { - if (j==selStart.y) { - if (iFine==0) { - pat->data[j][iFine]=0; - } - pat->data[j][iFine+1]=(iFine<1)?0:-1; - } else { - if (iFine==0) { - pat->data[j][iFine]=pat->data[j-1][iFine]; - } - pat->data[j][iFine+1]=pat->data[j-1][iFine+1]; - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_PUSH); -} - -void FurnaceGUI::doTranspose(int amount) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_DELETE); - curNibble=false; - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - int origOctave=(signed char)pat->data[j][1]; - if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) { - origNote+=amount; - while (origNote>12) { - origNote-=12; - origOctave++; - } - while (origNote<1) { - origNote+=12; - origOctave--; - } - if (origOctave==9 && origNote>11) { - origNote=11; - origOctave=9; - } - if (origOctave>9) { - origNote=11; - origOctave=9; - } - if (origOctave<-5) { - origNote=1; - origOctave=-5; - } - pat->data[j][0]=origNote; - pat->data[j][1]=(unsigned char)origOctave; - } - } else { - int top=255; - if (iFine==1) { - if (e->song.ins.empty()) continue; - top=e->song.ins.size()-1; - } else if (iFine==2) { // volume - top=e->getMaxVolumeChan(iCoarse); - } - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_DELETE); -} - -void FurnaceGUI::doCopy(bool cut) { - finishSelection(); - if (cut) { - curNibble=false; - prepareUndo(GUI_UNDO_PATTERN_CUT); - } - clipboard=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,selStart.xFine); - - for (int j=selStart.y; j<=selEnd.y; j++) { - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - if (iFine>3 && !(iFine&1)) { - iFine--; - } - int ord=e->getOrder(); - clipboard+='\n'; - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0],pat->data[j][1]); - if (cut) { - pat->data[j][0]=0; - pat->data[j][1]=0; - } - } else { - if (pat->data[j][iFine+1]==-1) { - clipboard+=".."; - } else { - clipboard+=fmt::sprintf("%.2X",pat->data[j][iFine+1]); - } - if (cut) { - pat->data[j][iFine+1]=-1; - } - } - } - clipboard+='|'; - iFine=0; - } - } - SDL_SetClipboardText(clipboard.c_str()); - - if (cut) { - makeUndo(GUI_UNDO_PATTERN_CUT); - } -} - -void FurnaceGUI::doPaste(PasteMode mode) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_PASTE); - char* clipText=SDL_GetClipboardText(); - if (clipText!=NULL) { - if (clipText[0]) { - clipboard=clipText; - } - SDL_free(clipText); - } - std::vector data; - String tempS; - for (char i: clipboard) { - if (i=='\r') continue; - if (i=='\n') { - data.push_back(tempS); - tempS=""; - continue; - } - tempS+=i; - } - data.push_back(tempS); - - int startOff=-1; - bool invalidData=false; - if (data.size()<2) return; - if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; - if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; - if (startOff<0) return; - - DETERMINE_LAST; - - int j=cursor.y; - char note[4]; - int ord=e->getOrder(); - for (size_t i=2; isong.patLen; i++) { - size_t charPos=0; - int iCoarse=cursor.xCoarse; - int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; - - String& line=data[i]; - - while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - if (line[charPos]=='|') { - iCoarse++; - if (iCoarsesong.chanShow[iCoarse]) { - iCoarse++; - if (iCoarse>=lastChannel) break; - } - iFine=0; - charPos++; - continue; - } - if (iFine==0) { - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[0]=line[charPos++]; - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[1]=line[charPos++]; - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[2]=line[charPos++]; - note[3]=0; - - if (iFine==0 && !opMaskNote) { - iFine++; - continue; - } - - if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { - // do nothing. - } else { - if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { - if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { - invalidData=true; - break; - } - } - } - } else { - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[0]=line[charPos++]; - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[1]=line[charPos++]; - note[2]=0; - - if (iFine==1) { - if (!opMaskIns) { - iFine++; - continue; - } - } else if (iFine==2) { - if (!opMaskVol) { - iFine++; - continue; - } - } else if ((iFine&1)==0) { - if (!opMaskEffectVal) { - iFine++; - continue; - } - } else if ((iFine&1)==1) { - if (!opMaskEffect) { - iFine++; - continue; - } - } - - if (strcmp(note,"..")==0) { - if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { - pat->data[j][iFine+1]=-1; - } - } else { - unsigned int val=0; - if (sscanf(note,"%2X",&val)!=1) { - invalidData=true; - break; - } - if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { - if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; - } - } - } - iFine++; - } - - if (invalidData) { - logW("invalid clipboard data! failed at line %d char %d\n",i,charPos); - logW("%s\n",line.c_str()); - break; - } - j++; - if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { - j=0; - ord++; - } - - if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { - i=1; - } - } - - makeUndo(GUI_UNDO_PATTERN_PASTE); -} - -void FurnaceGUI::doChangeIns(int ins) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); - - int iCoarse=selStart.xCoarse; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { - pat->data[j][2]=ins; - } - } - } - - makeUndo(GUI_UNDO_PATTERN_CHANGE_INS); -} - -void FurnaceGUI::doInterpolate() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); - - std::vector> points; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine+1]!=-1) { - points.emplace(points.end(),j,pat->data[j][iFine+1]); - } - } - - if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; - std::pair& nextPoint=points[j+1]; - double distance=nextPoint.first-curPoint.first; - for (int k=0; k<(nextPoint.first-curPoint.first); k++) { - pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); - } - } - } else { - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { - if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { - points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); - } - } - } - - if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; - std::pair& nextPoint=points[j+1]; - double distance=nextPoint.first-curPoint.first; - for (int k=0; k<(nextPoint.first-curPoint.first); k++) { - int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); - pat->data[k+curPoint.first][0]=val%12; - pat->data[k+curPoint.first][1]=val/12; - if (pat->data[k+curPoint.first][0]==0) { - pat->data[k+curPoint.first][0]=12; - pat->data[k+curPoint.first][1]--; - } - pat->data[k+curPoint.first][1]&=255; - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); -} - -void FurnaceGUI::doFade(int p0, int p1, bool mode) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_FADE); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - absoluteTop=e->song.ins.size()-1; - } else if (iFine==2) { // volume - absoluteTop=e->getMaxVolumeChan(iCoarse); - } - if (selEnd.y-selStart.y<1) continue; - for (int j=selStart.y; j<=selEnd.y; j++) { - double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); - int value=p0+double(p1-p0)*fraction; - if (mode) { // nibble - value&=15; - pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); - } else { // byte - pat->data[j][iFine+1]=MIN(absoluteTop,value); - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_FADE); -} - -void FurnaceGUI::doInvertValues() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - top=e->song.ins.size()-1; - } else if (iFine==2) { // volume - top=e->getMaxVolumeChan(iCoarse); - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); -} - -void FurnaceGUI::doScale(float top) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_SCALE); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - absoluteTop=e->song.ins.size()-1; - } else if (iFine==2) { // volume - absoluteTop=e->getMaxVolumeChan(iCoarse); - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_SCALE); -} - -void FurnaceGUI::doRandomize(int bottom, int top, bool mode) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - absoluteTop=e->song.ins.size()-1; - } else if (iFine==2) { // volume - absoluteTop=e->getMaxVolumeChan(iCoarse); - } - for (int j=selStart.y; j<=selEnd.y; j++) { - int value=0; - int value2=0; - if (top-bottom<=0) { - value=MIN(absoluteTop,bottom); - value2=MIN(absoluteTop,bottom); - } else { - value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); - value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); - } - if (mode) { - value&=15; - value2&=15; - pat->data[j][iFine+1]=value|(value2<<4); - } else { - pat->data[j][iFine+1]=value; - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); -} - -void FurnaceGUI::doFlip() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_FLIP); - - DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (iFine==0) { - pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; - } - pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_FLIP); -} - -void FurnaceGUI::doCollapse(int divider) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); - - DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=0; j<=selEnd.y-selStart.y; j++) { - if (j*divider>=selEnd.y-selStart.y) { - if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; - } else { - pat->data[j+selStart.y][iFine+1]=-1; - } - } else { - if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; - } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; - - if (iFine==0) { - for (int k=1; k=selEnd.y-selStart.y) break; - if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; - pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; - } - } else { - for (int k=1; k=selEnd.y-selStart.y) break; - if (pat->data[j+selStart.y][iFine+1]!=-1) break; - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; - } - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_COLLAPSE); -} - -void FurnaceGUI::doExpand(int multiplier) { - if (multiplier<1) return; - - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_EXPAND); - - DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->song.patLen) break; - if ((j%multiplier)!=0) { - if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; - } else { - pat->data[j+selStart.y][iFine+1]=-1; - } - continue; - } - if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; - } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_EXPAND); -} - -void FurnaceGUI::doUndo() { - if (undoHist.empty()) return; - UndoStep& us=undoHist.back(); - redoHist.push_back(us); - modified=true; - - switch (us.type) { - case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.oldOrdersLen; - for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.oldVal; - } - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); - p->data[i.row][i.col]=i.oldVal; - } - if (!e->isPlaying()) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - e->setOrder(us.order); - } - break; - } - - undoHist.pop_back(); -} - -void FurnaceGUI::doRedo() { - if (redoHist.empty()) return; - UndoStep& us=redoHist.back(); - undoHist.push_back(us); - modified=true; - - switch (us.type) { - case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.newOrdersLen; - for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.newVal; - } - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); - p->data[i.row][i.col]=i.newVal; - } - if (!e->isPlaying()) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - e->setOrder(us.order); - } - - break; - } - - redoHist.pop_back(); -} - void FurnaceGUI::play(int row) { e->walkSong(loopOrder,loopRow,loopEnd); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); @@ -3652,661 +725,6 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode) { } } -void FurnaceGUI::doAction(int what) { - switch (what) { - case GUI_ACTION_OPEN: - if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); - } else { - openFileDialog(GUI_FILE_OPEN); - } - break; - case GUI_ACTION_SAVE: - if (curFileName=="" || e->song.version>=0xff00) { - openFileDialog(GUI_FILE_SAVE); - } else { - if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - } - } - break; - case GUI_ACTION_SAVE_AS: - openFileDialog(GUI_FILE_SAVE); - break; - case GUI_ACTION_UNDO: - doUndo(); - break; - case GUI_ACTION_REDO: - doRedo(); - break; - case GUI_ACTION_PLAY_TOGGLE: - if (e->isPlaying() && !e->isStepping()) { - stop(); - } else { - play(); - } - break; - case GUI_ACTION_PLAY: - play(); - break; - case GUI_ACTION_STOP: - stop(); - break; - case GUI_ACTION_PLAY_REPEAT: - play(); - e->setRepeatPattern(true); - break; - case GUI_ACTION_PLAY_CURSOR: - if (e->isPlaying() && !e->isStepping()) { - stop(); - } else { - play(cursor.y); - } - break; - case GUI_ACTION_STEP_ONE: - e->stepOne(cursor.y); - break; - case GUI_ACTION_OCTAVE_UP: - if (++curOctave>7) { - curOctave=7; - } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - } - break; - case GUI_ACTION_OCTAVE_DOWN: - if (--curOctave<-5) { - curOctave=-5; - } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - } - break; - case GUI_ACTION_INS_UP: - if (--curIns<-1) curIns=-1; - break; - case GUI_ACTION_INS_DOWN: - if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; - break; - case GUI_ACTION_STEP_UP: - if (++editStep>64) editStep=64; - break; - case GUI_ACTION_STEP_DOWN: - if (--editStep<0) editStep=0; - break; - case GUI_ACTION_TOGGLE_EDIT: - edit=!edit; - break; - case GUI_ACTION_METRONOME: - e->setMetronome(!e->getMetronome()); - break; - case GUI_ACTION_REPEAT_PATTERN: - e->setRepeatPattern(!e->getRepeatPattern()); - break; - case GUI_ACTION_FOLLOW_ORDERS: - followOrders=!followOrders; - break; - case GUI_ACTION_FOLLOW_PATTERN: - followPattern=!followPattern; - break; - case GUI_ACTION_PANIC: - e->syncReset(); - break; - - case GUI_ACTION_WINDOW_EDIT_CONTROLS: - nextWindow=GUI_WINDOW_EDIT_CONTROLS; - break; - case GUI_ACTION_WINDOW_ORDERS: - nextWindow=GUI_WINDOW_ORDERS; - break; - case GUI_ACTION_WINDOW_INS_LIST: - nextWindow=GUI_WINDOW_INS_LIST; - break; - case GUI_ACTION_WINDOW_INS_EDIT: - nextWindow=GUI_WINDOW_INS_EDIT; - break; - case GUI_ACTION_WINDOW_SONG_INFO: - nextWindow=GUI_WINDOW_SONG_INFO; - break; - case GUI_ACTION_WINDOW_PATTERN: - nextWindow=GUI_WINDOW_PATTERN; - break; - case GUI_ACTION_WINDOW_WAVE_LIST: - nextWindow=GUI_WINDOW_WAVE_LIST; - break; - case GUI_ACTION_WINDOW_WAVE_EDIT: - nextWindow=GUI_WINDOW_WAVE_EDIT; - break; - case GUI_ACTION_WINDOW_SAMPLE_LIST: - nextWindow=GUI_WINDOW_SAMPLE_LIST; - break; - case GUI_ACTION_WINDOW_SAMPLE_EDIT: - nextWindow=GUI_WINDOW_SAMPLE_EDIT; - break; - case GUI_ACTION_WINDOW_ABOUT: - nextWindow=GUI_WINDOW_ABOUT; - break; - case GUI_ACTION_WINDOW_SETTINGS: - nextWindow=GUI_WINDOW_SETTINGS; - break; - case GUI_ACTION_WINDOW_MIXER: - nextWindow=GUI_WINDOW_MIXER; - break; - case GUI_ACTION_WINDOW_DEBUG: - nextWindow=GUI_WINDOW_DEBUG; - break; - case GUI_ACTION_WINDOW_OSCILLOSCOPE: - nextWindow=GUI_WINDOW_OSCILLOSCOPE; - break; - case GUI_ACTION_WINDOW_VOL_METER: - nextWindow=GUI_WINDOW_VOL_METER; - break; - case GUI_ACTION_WINDOW_STATS: - nextWindow=GUI_WINDOW_STATS; - break; - case GUI_ACTION_WINDOW_COMPAT_FLAGS: - nextWindow=GUI_WINDOW_COMPAT_FLAGS; - break; - case GUI_ACTION_WINDOW_PIANO: - nextWindow=GUI_WINDOW_PIANO; - break; - case GUI_ACTION_WINDOW_NOTES: - nextWindow=GUI_WINDOW_NOTES; - break; - case GUI_ACTION_WINDOW_CHANNELS: - nextWindow=GUI_WINDOW_CHANNELS; - break; - case GUI_ACTION_WINDOW_REGISTER_VIEW: - nextWindow=GUI_WINDOW_REGISTER_VIEW; - break; - - case GUI_ACTION_COLLAPSE_WINDOW: - collapseWindow=true; - break; - case GUI_ACTION_CLOSE_WINDOW: - switch (curWindow) { - case GUI_WINDOW_EDIT_CONTROLS: - editControlsOpen=false; - break; - case GUI_WINDOW_SONG_INFO: - songInfoOpen=false; - break; - case GUI_WINDOW_ORDERS: - ordersOpen=false; - break; - case GUI_WINDOW_INS_LIST: - insListOpen=false; - break; - case GUI_WINDOW_PATTERN: - patternOpen=false; - break; - case GUI_WINDOW_INS_EDIT: - insEditOpen=false; - break; - case GUI_WINDOW_WAVE_LIST: - waveListOpen=false; - break; - case GUI_WINDOW_WAVE_EDIT: - waveEditOpen=false; - break; - case GUI_WINDOW_SAMPLE_LIST: - sampleListOpen=false; - break; - case GUI_WINDOW_SAMPLE_EDIT: - sampleEditOpen=false; - break; - case GUI_WINDOW_MIXER: - mixerOpen=false; - break; - case GUI_WINDOW_ABOUT: - aboutOpen=false; - break; - case GUI_WINDOW_SETTINGS: - settingsOpen=false; - break; - case GUI_WINDOW_DEBUG: - debugOpen=false; - break; - case GUI_WINDOW_OSCILLOSCOPE: - oscOpen=false; - break; - case GUI_WINDOW_VOL_METER: - volMeterOpen=false; - break; - case GUI_WINDOW_STATS: - statsOpen=false; - break; - case GUI_WINDOW_COMPAT_FLAGS: - compatFlagsOpen=false; - break; - case GUI_WINDOW_PIANO: - pianoOpen=false; - break; - case GUI_WINDOW_NOTES: - notesOpen=false; - break; - case GUI_WINDOW_CHANNELS: - channelsOpen=false; - break; - case GUI_WINDOW_REGISTER_VIEW: - regViewOpen=false; - break; - default: - break; - } - curWindow=GUI_WINDOW_NOTHING; - break; - - case GUI_ACTION_PAT_NOTE_UP: - doTranspose(1); - break; - case GUI_ACTION_PAT_NOTE_DOWN: - doTranspose(-1); - break; - case GUI_ACTION_PAT_OCTAVE_UP: - doTranspose(12); - break; - case GUI_ACTION_PAT_OCTAVE_DOWN: - doTranspose(-12); - break; - case GUI_ACTION_PAT_SELECT_ALL: - doSelectAll(); - break; - case GUI_ACTION_PAT_CUT: - doCopy(true); - break; - case GUI_ACTION_PAT_COPY: - doCopy(false); - break; - case GUI_ACTION_PAT_PASTE: - doPaste(); - break; - case GUI_ACTION_PAT_CURSOR_UP: - moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),false); - break; - case GUI_ACTION_PAT_CURSOR_DOWN: - moveCursor(0,MAX(1,settings.scrollStep?editStep:1),false); - break; - case GUI_ACTION_PAT_CURSOR_LEFT: - moveCursor(-1,0,false); - break; - case GUI_ACTION_PAT_CURSOR_RIGHT: - moveCursor(1,0,false); - break; - case GUI_ACTION_PAT_CURSOR_UP_ONE: - moveCursor(0,-1,false); - break; - case GUI_ACTION_PAT_CURSOR_DOWN_ONE: - moveCursor(0,1,false); - break; - case GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL: - moveCursorPrevChannel(false); - break; - case GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL: - moveCursorNextChannel(false); - break; - case GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL: - moveCursorNextChannel(true); - break; - case GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL: - moveCursorPrevChannel(true); - break; - case GUI_ACTION_PAT_CURSOR_BEGIN: - moveCursorTop(false); - break; - case GUI_ACTION_PAT_CURSOR_END: - moveCursorBottom(false); - break; - case GUI_ACTION_PAT_CURSOR_UP_COARSE: - moveCursor(0,-16,false); - break; - case GUI_ACTION_PAT_CURSOR_DOWN_COARSE: - moveCursor(0,16,false); - break; - case GUI_ACTION_PAT_SELECTION_UP: - moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),true); - break; - case GUI_ACTION_PAT_SELECTION_DOWN: - moveCursor(0,MAX(1,settings.scrollStep?editStep:1),true); - break; - case GUI_ACTION_PAT_SELECTION_LEFT: - moveCursor(-1,0,true); - break; - case GUI_ACTION_PAT_SELECTION_RIGHT: - moveCursor(1,0,true); - break; - case GUI_ACTION_PAT_SELECTION_UP_ONE: - moveCursor(0,-1,true); - break; - case GUI_ACTION_PAT_SELECTION_DOWN_ONE: - moveCursor(0,1,true); - break; - case GUI_ACTION_PAT_SELECTION_BEGIN: - moveCursorTop(true); - break; - case GUI_ACTION_PAT_SELECTION_END: - moveCursorBottom(true); - break; - case GUI_ACTION_PAT_SELECTION_UP_COARSE: - moveCursor(0,-16,true); - break; - case GUI_ACTION_PAT_SELECTION_DOWN_COARSE: - moveCursor(0,16,true); - break; - case GUI_ACTION_PAT_DELETE: - doDelete(); - if (settings.stepOnDelete) { - moveCursor(0,editStep,false); - } - break; - case GUI_ACTION_PAT_PULL_DELETE: - doPullDelete(); - break; - case GUI_ACTION_PAT_INSERT: - doInsert(); - if (settings.stepOnInsert) { - moveCursor(0,editStep,false); - } - break; - case GUI_ACTION_PAT_MUTE_CURSOR: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->toggleMute(cursor.xCoarse); - break; - case GUI_ACTION_PAT_SOLO_CURSOR: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->toggleSolo(cursor.xCoarse); - break; - case GUI_ACTION_PAT_UNMUTE_ALL: - e->unmuteAll(); - break; - case GUI_ACTION_PAT_NEXT_ORDER: - if (e->getOrder()song.ordersLen-1) { - e->setOrder(e->getOrder()+1); - } - break; - case GUI_ACTION_PAT_PREV_ORDER: - if (e->getOrder()>0) { - e->setOrder(e->getOrder()-1); - } - break; - case GUI_ACTION_PAT_COLLAPSE: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse]; - break; - case GUI_ACTION_PAT_INCREASE_COLUMNS: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectRows++; - if (e->song.pat[cursor.xCoarse].effectRows>8) e->song.pat[cursor.xCoarse].effectRows=8; - break; - case GUI_ACTION_PAT_DECREASE_COLUMNS: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectRows--; - if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; - break; - case GUI_ACTION_PAT_INTERPOLATE: - doInterpolate(); - break; - case GUI_ACTION_PAT_INVERT_VALUES: - doInvertValues(); - break; - case GUI_ACTION_PAT_FLIP_SELECTION: - doFlip(); - break; - case GUI_ACTION_PAT_COLLAPSE_ROWS: - doCollapse(2); - break; - case GUI_ACTION_PAT_EXPAND_ROWS: - doExpand(2); - break; - case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO - break; - case GUI_ACTION_PAT_EXPAND_PAT: // TODO - break; - case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO - break; - case GUI_ACTION_PAT_EXPAND_SONG: // TODO - break; - case GUI_ACTION_PAT_LATCH: // TODO - break; - - case GUI_ACTION_INS_LIST_ADD: - curIns=e->addInstrument(cursor.xCoarse); - modified=true; - break; - case GUI_ACTION_INS_LIST_DUPLICATE: - if (curIns>=0 && curIns<(int)e->song.ins.size()) { - int prevIns=curIns; - curIns=e->addInstrument(cursor.xCoarse); - (*e->song.ins[curIns])=(*e->song.ins[prevIns]); - modified=true; - } - break; - case GUI_ACTION_INS_LIST_OPEN: - openFileDialog(GUI_FILE_INS_OPEN); - break; - case GUI_ACTION_INS_LIST_SAVE: - if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); - break; - case GUI_ACTION_INS_LIST_MOVE_UP: - if (e->moveInsUp(curIns)) curIns--; - break; - case GUI_ACTION_INS_LIST_MOVE_DOWN: - if (e->moveInsDown(curIns)) curIns++; - break; - case GUI_ACTION_INS_LIST_DELETE: - if (curIns>=0 && curIns<(int)e->song.ins.size()) { - e->delInstrument(curIns); - modified=true; - if (curIns>=(int)e->song.ins.size()) { - curIns--; - } - } - break; - case GUI_ACTION_INS_LIST_EDIT: - insEditOpen=true; - break; - case GUI_ACTION_INS_LIST_UP: - if (--curIns<0) curIns=0; - break; - case GUI_ACTION_INS_LIST_DOWN: - if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; - break; - - case GUI_ACTION_WAVE_LIST_ADD: - curWave=e->addWave(); - modified=true; - break; - case GUI_ACTION_WAVE_LIST_DUPLICATE: - if (curWave>=0 && curWave<(int)e->song.wave.size()) { - int prevWave=curWave; - curWave=e->addWave(); - (*e->song.wave[curWave])=(*e->song.wave[prevWave]); - modified=true; - } - break; - case GUI_ACTION_WAVE_LIST_OPEN: - openFileDialog(GUI_FILE_WAVE_OPEN); - break; - case GUI_ACTION_WAVE_LIST_SAVE: - if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); - break; - case GUI_ACTION_WAVE_LIST_MOVE_UP: - if (e->moveWaveUp(curWave)) curWave--; - break; - case GUI_ACTION_WAVE_LIST_MOVE_DOWN: - if (e->moveWaveDown(curWave)) curWave++; - break; - case GUI_ACTION_WAVE_LIST_DELETE: - if (curWave>=0 && curWave<(int)e->song.wave.size()) { - e->delWave(curWave); - modified=true; - if (curWave>=(int)e->song.wave.size()) { - curWave--; - } - } - break; - case GUI_ACTION_WAVE_LIST_EDIT: - waveEditOpen=true; - break; - case GUI_ACTION_WAVE_LIST_UP: - if (--curWave<0) curWave=0; - break; - case GUI_ACTION_WAVE_LIST_DOWN: - if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1; - break; - - case GUI_ACTION_SAMPLE_LIST_ADD: - curSample=e->addSample(); - modified=true; - break; - case GUI_ACTION_SAMPLE_LIST_OPEN: - openFileDialog(GUI_FILE_SAMPLE_OPEN); - break; - case GUI_ACTION_SAMPLE_LIST_SAVE: - 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--; - break; - case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN: - if (e->moveSampleDown(curSample)) curSample++; - break; - case GUI_ACTION_SAMPLE_LIST_DELETE: - e->delSample(curSample); - modified=true; - if (curSample>=(int)e->song.sample.size()) { - curSample--; - } - break; - case GUI_ACTION_SAMPLE_LIST_EDIT: - sampleEditOpen=true; - break; - case GUI_ACTION_SAMPLE_LIST_UP: - if (--curSample<0) curSample=0; - break; - case GUI_ACTION_SAMPLE_LIST_DOWN: - if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1; - break; - case GUI_ACTION_SAMPLE_LIST_PREVIEW: - e->previewSample(curSample); - break; - case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW: - e->stopSamplePreview(); - break; - - case GUI_ACTION_ORDERS_UP: - if (e->getOrder()>0) { - e->setOrder(e->getOrder()-1); - } - break; - case GUI_ACTION_ORDERS_DOWN: - if (e->getOrder()song.ordersLen-1) { - e->setOrder(e->getOrder()+1); - } - break; - case GUI_ACTION_ORDERS_LEFT: { - DETERMINE_FIRST; - - do { - orderCursor--; - if (orderCursorsong.chanShow[orderCursor]); - break; - } - case GUI_ACTION_ORDERS_RIGHT: { - DETERMINE_LAST; - - do { - orderCursor++; - if (orderCursor>=lastChannel) { - orderCursor=lastChannel-1; - break; - } - } while (!e->song.chanShow[orderCursor]); - break; - } - case GUI_ACTION_ORDERS_INCREASE: { - if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - int curOrder=e->getOrder(); - if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { - e->song.orders.ord[orderCursor][curOrder]++; - } - break; - } - case GUI_ACTION_ORDERS_DECREASE: { - if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - int curOrder=e->getOrder(); - if (e->song.orders.ord[orderCursor][curOrder]>0) { - e->song.orders.ord[orderCursor][curOrder]--; - } - break; - } - case GUI_ACTION_ORDERS_EDIT_MODE: - orderEditMode++; - if (orderEditMode>3) orderEditMode=0; - break; - case GUI_ACTION_ORDERS_LINK: - changeAllOrders=!changeAllOrders; - break; - case GUI_ACTION_ORDERS_ADD: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->addOrder(false,false); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_DUPLICATE: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->addOrder(true,false); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_DEEP_CLONE: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->deepCloneOrder(false); - makeUndo(GUI_UNDO_CHANGE_ORDER); - if (!e->getWarnings().empty()) { - showWarning(e->getWarnings(),GUI_WARN_GENERIC); - } - break; - case GUI_ACTION_ORDERS_DUPLICATE_END: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->addOrder(true,true); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_DEEP_CLONE_END: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->deepCloneOrder(true); - makeUndo(GUI_UNDO_CHANGE_ORDER); - if (!e->getWarnings().empty()) { - showWarning(e->getWarnings(),GUI_WARN_GENERIC); - } - break; - case GUI_ACTION_ORDERS_REMOVE: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->deleteOrder(); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_MOVE_UP: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->moveOrderUp(); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_MOVE_DOWN: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->moveOrderDown(); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_REPLAY: - e->setOrder(e->getOrder()); - break; - } -} - void FurnaceGUI::keyDown(SDL_Event& ev) { if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; @@ -4367,6 +785,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { 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 (edit) { // TODO: separate when adding MIDI input. DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); @@ -4475,7 +896,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=valueKeys.at(ev.key.keysym.sym); if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { int curOrder=e->getOrder(); - e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num)&0x7f; + e->lockSave([this,curOrder,num]() { + e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num)&0x7f; + }); if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; if (!curNibble) { @@ -4495,6 +918,16 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } } break; + case GUI_WINDOW_SAMPLE_EDIT: + try { + int action=actionMapSample.at(mapped); + if (action>0) { + doAction(action); + return; + } + } catch (std::out_of_range& e) { + } + break; case GUI_WINDOW_INS_LIST: try { int action=actionMapInsList.at(mapped); @@ -4943,6 +1376,8 @@ int FurnaceGUI::load(String path) { curNibble=false; orderNibble=false; orderCursor=-1; + samplePos=0; + updateSampleTex=true; selStart=SelectionPoint(); selEnd=SelectionPoint(); cursor=SelectionPoint(); @@ -5035,7 +1470,39 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { if (ynotifyWaveChange(curWave); - modified=true; + MARK_MODIFIED; + } + } + if (sampleDragActive) { + int x=samplePos+round(double(dragX-sampleDragStart.x)*sampleZoom); + int x1=samplePos+round(double(dragX-sampleDragStart.x+1)*sampleZoom); + if (x<0) x=0; + if (sampleDragMode) { + if (x>=(int)sampleDragLen) x=sampleDragLen-1; + } else { + if (x>(int)sampleDragLen) x=sampleDragLen; + } + if (x1<0) x1=0; + if (x1>=(int)sampleDragLen) x1=sampleDragLen-1; + double y=0.5-double(dragY-sampleDragStart.y)/sampleDragAreaSize.y; + if (sampleDragMode) { // draw + if (sampleDrag16) { + int val=y*65536; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + for (int i=x; i<=x1; i++) ((short*)sampleDragTarget)[i]=val; + } else { + int val=y*256; + if (val<-128) val=-128; + if (val>127) val=127; + for (int i=x; i<=x1; i++) ((signed char*)sampleDragTarget)[i]=val; + } + updateSampleTex=true; + } else { // select + if (sampleSelStart<0) { + sampleSelStart=x; + } + sampleSelEnd=x; } } } @@ -5287,8 +1754,8 @@ bool FurnaceGUI::loop() { addScroll(1); } } - if (macroDragActive || macroLoopDragActive || waveDragActive) { - int distance=fabs(motionXrel); + if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { + int distance=fabs((double)motionXrel); if (distance<1) distance=1; float start=motionX-motionXrel; float end=motionX; @@ -5304,7 +1771,9 @@ bool FurnaceGUI::loop() { break; } case SDL_MOUSEBUTTONUP: - if (macroDragActive || macroLoopDragActive || waveDragActive) modified=true; + if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) { + MARK_MODIFIED; + } macroDragActive=false; macroDragBitMode=false; macroDragInitialValue=false; @@ -5313,6 +1782,19 @@ bool FurnaceGUI::loop() { macroDragLastY=-1; macroLoopDragActive=false; waveDragActive=false; + if (sampleDragActive) { + logD("stopping sample drag\n"); + if (sampleDragMode) { + e->renderSamplesP(); + } else { + if (sampleSelStart>sampleSelEnd) { + sampleSelStart^=sampleSelEnd; + sampleSelEnd^=sampleSelStart; + sampleSelStart^=sampleSelEnd; + } + } + } + sampleDragActive=false; if (selecting) { cursor=selEnd; finishSelection(); @@ -5418,7 +1900,7 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { - if (curFileName=="" || e->song.version>=0xff00) { + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { @@ -5478,421 +1960,15 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::BeginMenu("add system...")) { - sysAddOption(DIV_SYSTEM_YM2612); - sysAddOption(DIV_SYSTEM_YM2612_EXT); - sysAddOption(DIV_SYSTEM_SMS); - sysAddOption(DIV_SYSTEM_GB); - sysAddOption(DIV_SYSTEM_PCE); - sysAddOption(DIV_SYSTEM_NES); - sysAddOption(DIV_SYSTEM_C64_8580); - sysAddOption(DIV_SYSTEM_C64_6581); - sysAddOption(DIV_SYSTEM_YM2151); - sysAddOption(DIV_SYSTEM_SEGAPCM); - sysAddOption(DIV_SYSTEM_SEGAPCM_COMPAT); - sysAddOption(DIV_SYSTEM_YM2610); - sysAddOption(DIV_SYSTEM_YM2610_EXT); - sysAddOption(DIV_SYSTEM_YM2610_FULL); - sysAddOption(DIV_SYSTEM_YM2610_FULL_EXT); - sysAddOption(DIV_SYSTEM_YM2610B); - sysAddOption(DIV_SYSTEM_YM2610B_EXT); - sysAddOption(DIV_SYSTEM_AY8910); - sysAddOption(DIV_SYSTEM_AMIGA); - sysAddOption(DIV_SYSTEM_PCSPKR); - sysAddOption(DIV_SYSTEM_OPLL); - sysAddOption(DIV_SYSTEM_OPLL_DRUMS); - sysAddOption(DIV_SYSTEM_VRC7); - sysAddOption(DIV_SYSTEM_OPL); - sysAddOption(DIV_SYSTEM_OPL_DRUMS); - sysAddOption(DIV_SYSTEM_OPL2); - sysAddOption(DIV_SYSTEM_OPL2_DRUMS); - sysAddOption(DIV_SYSTEM_OPL3); - sysAddOption(DIV_SYSTEM_OPL3_DRUMS); - sysAddOption(DIV_SYSTEM_TIA); - sysAddOption(DIV_SYSTEM_SAA1099); - sysAddOption(DIV_SYSTEM_AY8930); - sysAddOption(DIV_SYSTEM_LYNX); - sysAddOption(DIV_SYSTEM_QSOUND); - sysAddOption(DIV_SYSTEM_X1_010); - sysAddOption(DIV_SYSTEM_SWAN); - sysAddOption(DIV_SYSTEM_VERA); - sysAddOption(DIV_SYSTEM_BUBSYS_WSG); - sysAddOption(DIV_SYSTEM_N163); + for (int j=0; availableSystems[j]; j++) { + sysAddOption((DivSystem)availableSystems[j]); + } ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - unsigned int flags=e->song.systemFlags[i]; - bool restart=settings.restartOnFlagChange; - bool sysPal=flags&1; - switch (e->song.system[i]) { - case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_EXT: { - if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&3)==0)) { - e->setSysFlags(i,(flags&0x80000000)|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (7.61MHz)",(flags&3)==1)) { - e->setSysFlags(i,(flags&0x80000000)|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("FM Towns (8MHz)",(flags&3)==2)) { - e->setSysFlags(i,(flags&0x80000000)|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&3)==3)) { - e->setSysFlags(i,(flags&0x80000000)|3,restart); - updateWindowTitle(); - } - bool ladder=flags&0x80000000; - if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { - e->setSysFlags(i,(flags&(~0x80000000))|(ladder?0x80000000:0),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_SMS: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { - e->setSysFlags(i,(flags&(~3))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (3.55MHz)",(flags&3)==1)) { - e->setSysFlags(i,(flags&(~3))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&3)==2)) { - e->setSysFlags(i,(flags&(~3))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { - e->setSysFlags(i,(flags&(~3))|3,restart); - updateWindowTitle(); - } - ImGui::Text("Chip type:"); - if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { - e->setSysFlags(i,(flags&(~12))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { - e->setSysFlags(i,(flags&(~12))|4,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { - e->setSysFlags(i,(flags&(~12))|8,restart); - updateWindowTitle(); - } - /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { - e->setSysFlags(i,(flags&3)|12); - }*/ - - bool noPhaseReset=flags&16; - if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { - e->setSysFlags(i,(flags&(~16))|(noPhaseReset<<4),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_VRC7: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { - e->setSysFlags(i,(flags&(~15))|3,restart); - updateWindowTitle(); - } - if (e->song.system[i]!=DIV_SYSTEM_VRC7) { - ImGui::Text("Patch set:"); - if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { - e->setSysFlags(i,(flags&(~0xf0))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { - e->setSysFlags(i,(flags&(~0xf0))|0x10,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { - e->setSysFlags(i,(flags&(~0xf0))|0x20,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { - e->setSysFlags(i,(flags&(~0xf0))|0x30,restart); - updateWindowTitle(); - } - } - break; - } - case DIV_SYSTEM_YM2151: - if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_NES: - if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_C64_8580: - case DIV_SYSTEM_C64_6581: - if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { - e->setSysFlags(i,(flags&(~15))|3,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { - e->setSysFlags(i,(flags&(~15))|4,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { - e->setSysFlags(i,(flags&(~15))|5,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) { - e->setSysFlags(i,(flags&(~15))|6,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { - e->setSysFlags(i,(flags&(~15))|7,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) { - e->setSysFlags(i,(flags&(~15))|8,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { - e->setSysFlags(i,(flags&(~15))|9,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { - e->setSysFlags(i,(flags&(~15))|10,restart); - updateWindowTitle(); - } - if (e->song.system[i]==DIV_SYSTEM_AY8910) { - ImGui::Text("Chip type:"); - if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { - e->setSysFlags(i,(flags&(~0x30))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { - e->setSysFlags(i,(flags&(~0x30))|16,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { - e->setSysFlags(i,(flags&(~0x30))|32,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { - e->setSysFlags(i,(flags&(~0x30))|48,restart); - updateWindowTitle(); - } - } - bool stereo=flags&0x40; - ImGui::BeginDisabled((flags&0x30)==32); - if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { - e->setSysFlags(i,(flags&(~0x40))|(stereo?0x40:0),restart); - updateWindowTitle(); - } - ImGui::EndDisabled(); - break; - } - case DIV_SYSTEM_SAA1099: - if (ImGui::RadioButton("SAM Coupé (8MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("NTSC (7.15MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (7.09MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_AMIGA: { - ImGui::Text("Stereo separation:"); - int stereoSep=(flags>>8)&127; - if (ImGui::SliderInt("##StereoSep",&stereoSep,0,127)) { - if (stereoSep<0) stereoSep=0; - if (stereoSep>127) stereoSep=127; - e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); - updateWindowTitle(); - } rightClickable - if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { - e->setSysFlags(i,flags&(~2),restart); - } - if (ImGui::RadioButton("Amiga 1200 (AGA)",(flags&2)==2)) { - e->setSysFlags(i,(flags&(~2))|2,restart); - } - sysPal=flags&1; - if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,(flags&(~1))|sysPal,restart); - updateWindowTitle(); - } - bool bypassLimits=flags&4; - if (ImGui::Checkbox("Bypass frequency limits",&bypassLimits)) { - e->setSysFlags(i,(flags&(~4))|(bypassLimits<<2),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_PCSPKR: { - ImGui::Text("Speaker type:"); - if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { - e->setSysFlags(i,(flags&(~3))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Cone",(flags&3)==1)) { - e->setSysFlags(i,(flags&(~3))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Piezo",(flags&3)==2)) { - e->setSysFlags(i,(flags&(~3))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { - e->setSysFlags(i,(flags&(~3))|3,restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_QSOUND: { - ImGui::Text("Echo delay:"); - int echoBufSize=2725 - (flags & 4095); - if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) { - if (echoBufSize<0) echoBufSize=0; - if (echoBufSize>2725) echoBufSize=2725; - e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); - updateWindowTitle(); - } rightClickable - ImGui::Text("Echo feedback:"); - int echoFeedback=(flags>>12)&255; - if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) { - if (echoFeedback<0) echoFeedback=0; - if (echoFeedback>255) echoFeedback=255; - e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); - updateWindowTitle(); - } rightClickable - break; - } - case DIV_SYSTEM_X1_010: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - bool x1_010Stereo=flags&16; - if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { - e->setSysFlags(i,(flags&(~16))|(x1_010Stereo<<4),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_N163: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("NTSC (1.79MHz)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (1.67MHz)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Dendy (1.77MHz)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); - } - ImGui::Text("Initial channel limit:"); - int initialChannelLimit=((flags>>4)&7)+1; - if (ImGui::SliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { - if (initialChannelLimit<1) initialChannelLimit=1; - if (initialChannelLimit>8) initialChannelLimit=8; - e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); - updateWindowTitle(); - } rightClickable - break; - } - case DIV_SYSTEM_GB: - case DIV_SYSTEM_SWAN: - case DIV_SYSTEM_VERA: - case DIV_SYSTEM_BUBSYS_WSG: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL: - case DIV_SYSTEM_YM2610_FULL_EXT: - case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_YMU759: - ImGui::Text("nothing to configure"); - break; - default: - if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,sysPal,restart); - updateWindowTitle(); - } - break; - } + drawSysConf(i); ImGui::TreePop(); } } @@ -5901,45 +1977,9 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("change system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - sysChangeOption(i,DIV_SYSTEM_YM2612); - sysChangeOption(i,DIV_SYSTEM_YM2612_EXT); - sysChangeOption(i,DIV_SYSTEM_SMS); - sysChangeOption(i,DIV_SYSTEM_GB); - sysChangeOption(i,DIV_SYSTEM_PCE); - sysChangeOption(i,DIV_SYSTEM_NES); - sysChangeOption(i,DIV_SYSTEM_C64_8580); - sysChangeOption(i,DIV_SYSTEM_C64_6581); - sysChangeOption(i,DIV_SYSTEM_YM2151); - sysChangeOption(i,DIV_SYSTEM_SEGAPCM); - sysChangeOption(i,DIV_SYSTEM_SEGAPCM_COMPAT); - sysChangeOption(i,DIV_SYSTEM_YM2610); - sysChangeOption(i,DIV_SYSTEM_YM2610_EXT); - sysChangeOption(i,DIV_SYSTEM_YM2610_FULL); - sysChangeOption(i,DIV_SYSTEM_YM2610_FULL_EXT); - sysChangeOption(i,DIV_SYSTEM_YM2610B); - sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); - sysChangeOption(i,DIV_SYSTEM_AY8910); - sysChangeOption(i,DIV_SYSTEM_AMIGA); - sysChangeOption(i,DIV_SYSTEM_PCSPKR); - sysChangeOption(i,DIV_SYSTEM_OPLL); - sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); - sysChangeOption(i,DIV_SYSTEM_VRC7); - sysChangeOption(i,DIV_SYSTEM_OPL); - sysChangeOption(i,DIV_SYSTEM_OPL_DRUMS); - sysChangeOption(i,DIV_SYSTEM_OPL2); - sysChangeOption(i,DIV_SYSTEM_OPL2_DRUMS); - sysChangeOption(i,DIV_SYSTEM_OPL3); - sysChangeOption(i,DIV_SYSTEM_OPL3_DRUMS); - sysChangeOption(i,DIV_SYSTEM_TIA); - sysChangeOption(i,DIV_SYSTEM_SAA1099); - sysChangeOption(i,DIV_SYSTEM_AY8930); - sysChangeOption(i,DIV_SYSTEM_LYNX); - sysChangeOption(i,DIV_SYSTEM_QSOUND); - sysChangeOption(i,DIV_SYSTEM_X1_010); - sysChangeOption(i,DIV_SYSTEM_SWAN); - sysChangeOption(i,DIV_SYSTEM_VERA); - sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); - sysChangeOption(i,DIV_SYSTEM_N163); + for (int j=0; availableSystems[j]; j++) { + sysChangeOption(i,(DivSystem)availableSystems[j]); + } ImGui::EndMenu(); } } @@ -5956,6 +1996,10 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); + if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { + doAction(GUI_ACTION_OPEN_BACKUP); + } + ImGui::Separator(); if (ImGui::MenuItem("exit")) { if (modified) { showWarning("Unsaved changes! Are you sure you want to quit?",GUI_WARN_QUIT); @@ -5977,6 +2021,9 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("settings")) { if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; + e->enableCommandStream(fancyPattern); + e->getCommandStream(cmdStream); + cmdStream.clear(); } if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); @@ -6113,7 +2160,7 @@ bool FurnaceGUI::loop() { drawChannels(); drawRegView(); - //ImGui::ShowMetricsWindow(); + if (inspectorOpen) ImGui::ShowMetricsWindow(&inspectorOpen); if (firstFrame) { firstFrame=false; @@ -6222,7 +2269,7 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_SAMPLE_OPEN: e->addSampleFromFile(copyOfName.c_str()); - modified=true; + MARK_MODIFIED; break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { @@ -6249,7 +2296,7 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_WAVE_OPEN: e->addWaveFromFile(copyOfName.c_str()); - modified=true; + MARK_MODIFIED; break; case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop); @@ -6355,6 +2402,11 @@ bool FurnaceGUI::loop() { case GUI_WARN_OPEN: openFileDialog(GUI_FILE_OPEN); break; + case GUI_WARN_OPEN_BACKUP: + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + break; case GUI_WARN_OPEN_DROP: if (load(nextFile)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); @@ -6378,6 +2430,39 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + // backup trigger + if (modified) { + if (backupTimer>0) { + backupTimer-=ImGui::GetIO().DeltaTime; + if (backupTimer<=0) { + backupTask=std::async(std::launch::async,[this]() -> bool { + if (backupPath==curFileName) { + logD("backup file open. not saving backup.\n"); + return true; + } + logD("saving backup...\n"); + SafeWriter* w=e->saveFur(true); + + if (w!=NULL) { + FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); + if (outFile!=NULL) { + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write backup entirely: %s!\n",strerror(errno)); + w->finish(); + } + fclose(outFile); + } else { + logW("could not save backup: %s!\n",strerror(errno)); + w->finish(); + } + } + backupTimer=30.0; + return true; + }); + } + } + } + SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, uiColors[GUI_COLOR_BACKGROUND].y*255, uiColors[GUI_COLOR_BACKGROUND].z*255, @@ -6401,372 +2486,6 @@ bool FurnaceGUI::loop() { return false; } -void FurnaceGUI::parseKeybinds() { - actionMapGlobal.clear(); - actionMapPat.clear(); - actionMapInsList.clear(); - actionMapWaveList.clear(); - actionMapSampleList.clear(); - actionMapOrders.clear(); - - for (int i=GUI_ACTION_GLOBAL_MIN+1; igetConfInt(#target,ImGui::GetColorU32(def))); - -#ifdef _WIN32 -#define SYSTEM_FONT_PATH_1 "C:\\Windows\\Fonts\\segoeui.ttf" -#define SYSTEM_FONT_PATH_2 "C:\\Windows\\Fonts\\tahoma.ttf" -// TODO! -#define SYSTEM_FONT_PATH_3 "C:\\Windows\\Fonts\\tahoma.ttf" -// TODO! -#define SYSTEM_PAT_FONT_PATH_1 "C:\\Windows\\Fonts\\consola.ttf" -#define SYSTEM_PAT_FONT_PATH_2 "C:\\Windows\\Fonts\\cour.ttf" -// GOOD LUCK WITH THIS ONE - UNTESTED -#define SYSTEM_PAT_FONT_PATH_3 "C:\\Windows\\Fonts\\vgasys.fon" -#elif defined(__APPLE__) -#define SYSTEM_FONT_PATH_1 "/System/Library/Fonts/SFAANS.ttf" -#define SYSTEM_FONT_PATH_2 "/System/Library/Fonts/Helvetica.ttc" -#define SYSTEM_FONT_PATH_3 "/System/Library/Fonts/Helvetica.dfont" -#define SYSTEM_PAT_FONT_PATH_1 "/System/Library/Fonts/SFNSMono.ttf" -#define SYSTEM_PAT_FONT_PATH_2 "/System/Library/Fonts/Courier New.ttf" -#define SYSTEM_PAT_FONT_PATH_3 "/System/Library/Fonts/Courier New.ttf" -#else -#define SYSTEM_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" -#define SYSTEM_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSans.ttf" -#define SYSTEM_FONT_PATH_3 "/usr/share/fonts/ubuntu/Ubuntu-R.ttf" -#define SYSTEM_PAT_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" -#define SYSTEM_PAT_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSansMono.ttf" -#define SYSTEM_PAT_FONT_PATH_3 "/usr/share/fonts/ubuntu/UbuntuMono-R.ttf" -#endif - -void FurnaceGUI::applyUISettings() { - ImGuiStyle sty; - if (settings.guiColorsBase) { - ImGui::StyleColorsLight(&sty); - } else { - ImGui::StyleColorsDark(&sty); - } - - if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; - - GET_UI_COLOR(GUI_COLOR_BACKGROUND,ImVec4(0.1f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND,ImVec4(0.0f,0.0f,0.0f,0.85f)); - GET_UI_COLOR(GUI_COLOR_MODAL_BACKDROP,ImVec4(0.0f,0.0f,0.0f,0.55f)); - GET_UI_COLOR(GUI_COLOR_HEADER,ImVec4(0.2f,0.2f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); - GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_WAVE,ImVec4(1.0f,0.4f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_FM,ImVec4(0.6f,0.9f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_STD,ImVec4(0.6f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_GB,ImVec4(1.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_C64,ImVec4(0.85f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AMIGA,ImVec4(1.0f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_PCE,ImVec4(1.0f,0.8f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AY,ImVec4(1.0f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AY8930,ImVec4(0.7f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_TIA,ImVec4(1.0f,0.6f,0.4f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SAA1099,ImVec4(0.3f,0.3f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VIC,ImVec4(0.2f,1.0f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_PET,ImVec4(1.0f,1.0f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VRC6,ImVec4(1.0f,0.9f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPLL,ImVec4(0.6f,0.7f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPL,ImVec4(0.3f,1.0f,0.9f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_FDS,ImVec4(0.8f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VBOY,ImVec4(1.0f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_N163,ImVec4(1.0f,0.4f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SCC,ImVec4(0.7f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPZ,ImVec4(0.2f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_POKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_NOISE,ImVec4(0.8f,0.8f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_PCM,ImVec4(1.0f,0.9f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_WAVE,ImVec4(1.0f,0.5f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION,ImVec4(0.15f,0.15f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_HOVER,ImVec4(0.2f,0.2f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_ACTIVE,ImVec4(0.4f,0.4f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_HI_1,ImVec4(0.6f,0.6f,0.6f,0.2f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_HI_2,ImVec4(0.5f,0.8f,1.0f,0.2f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE,ImVec4(0.5f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INS,ImVec4(0.4f,0.7f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN,ImVec4(0.0f,0.5f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF,ImVec4(0.0f,0.75f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_INVALID,ImVec4(1.0f,0.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PITCH,ImVec4(1.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_VOLUME,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PANNING,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SONG,ImVec4(1.0f,0.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_TIME,ImVec4(0.5f,0.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SPEED,ImVec4(1.0f,0.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,ImVec4(0.5f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,ImVec4(0.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC,ImVec4(0.3f,0.3f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_EE_VALUE,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PLAYBACK_STAT,ImVec4(0.6f,0.6f,0.6f,1.0f)); - - for (int i=0; i<64; i++) { - ImVec4 col1=uiColors[GUI_COLOR_PATTERN_VOLUME_MIN]; - ImVec4 col2=uiColors[GUI_COLOR_PATTERN_VOLUME_HALF]; - ImVec4 col3=uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]; - volColors[i]=ImVec4(col1.x+((col2.x-col1.x)*float(i)/64.0f), - col1.y+((col2.y-col1.y)*float(i)/64.0f), - col1.z+((col2.z-col1.z)*float(i)/64.0f), - 1.0f); - volColors[i+64]=ImVec4(col2.x+((col3.x-col2.x)*float(i)/64.0f), - col2.y+((col3.y-col2.y)*float(i)/64.0f), - col2.z+((col3.z-col2.z)*float(i)/64.0f), - 1.0f); - } - - float hue, sat, val; - - ImVec4 primaryActive=uiColors[GUI_COLOR_ACCENT_PRIMARY]; - ImVec4 primaryHover, primary; - primaryHover.w=primaryActive.w; - primary.w=primaryActive.w; - ImGui::ColorConvertRGBtoHSV(primaryActive.x,primaryActive.y,primaryActive.z,hue,sat,val); - if (settings.guiColorsBase) { - primary=primaryActive; - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,primaryHover.x,primaryHover.y,primaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,primaryActive.x,primaryActive.y,primaryActive.z); - } else { - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,primaryHover.x,primaryHover.y,primaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.8,val*0.35,primary.x,primary.y,primary.z); - } - - ImVec4 secondaryActive=uiColors[GUI_COLOR_ACCENT_SECONDARY]; - ImVec4 secondaryHover, secondary, secondarySemiActive; - secondarySemiActive.w=secondaryActive.w; - secondaryHover.w=secondaryActive.w; - secondary.w=secondaryActive.w; - ImGui::ColorConvertRGBtoHSV(secondaryActive.x,secondaryActive.y,secondaryActive.z,hue,sat,val); - if (settings.guiColorsBase) { - secondary=secondaryActive; - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.7,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,secondaryHover.x,secondaryHover.y,secondaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,secondaryActive.x,secondaryActive.y,secondaryActive.z); - } else { - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.75,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,secondaryHover.x,secondaryHover.y,secondaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.25,secondary.x,secondary.y,secondary.z); - } - - - sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND]; - sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP]; - sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT]; - - sty.Colors[ImGuiCol_Button]=primary; - sty.Colors[ImGuiCol_ButtonHovered]=primaryHover; - sty.Colors[ImGuiCol_ButtonActive]=primaryActive; - sty.Colors[ImGuiCol_Tab]=primary; - sty.Colors[ImGuiCol_TabHovered]=secondaryHover; - sty.Colors[ImGuiCol_TabActive]=secondarySemiActive; - sty.Colors[ImGuiCol_TabUnfocused]=primary; - sty.Colors[ImGuiCol_TabUnfocusedActive]=primaryHover; - sty.Colors[ImGuiCol_Header]=secondary; - sty.Colors[ImGuiCol_HeaderHovered]=secondaryHover; - sty.Colors[ImGuiCol_HeaderActive]=secondaryActive; - sty.Colors[ImGuiCol_ResizeGrip]=secondary; - sty.Colors[ImGuiCol_ResizeGripHovered]=secondaryHover; - sty.Colors[ImGuiCol_ResizeGripActive]=secondaryActive; - sty.Colors[ImGuiCol_FrameBg]=secondary; - sty.Colors[ImGuiCol_FrameBgHovered]=secondaryHover; - sty.Colors[ImGuiCol_FrameBgActive]=secondaryActive; - sty.Colors[ImGuiCol_SliderGrab]=primaryActive; - sty.Colors[ImGuiCol_SliderGrabActive]=primaryActive; - sty.Colors[ImGuiCol_TitleBgActive]=primary; - sty.Colors[ImGuiCol_CheckMark]=primaryActive; - sty.Colors[ImGuiCol_TextSelectedBg]=secondaryHover; - sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; - sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; - - sty.ScaleAllSizes(dpiScale); - - ImGui::GetStyle()=sty; - - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; - pitchGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_ACTIVE]; - noteGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PANNING]; - panGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_INS]; - insGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=volColors[i/2]; - volGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]; - sysCmd1Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]; - sysCmd2Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - - // set to 800 for now due to problems with unifont - static const ImWchar loadEverything[]={0x20,0x800,0}; - - if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; - if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; - - if (settings.mainFont==6 && settings.mainFontPath.empty()) { - logW("UI font path is empty! reverting to default font\n"); - settings.mainFont=0; - } - if (settings.patFont==6 && settings.patFontPath.empty()) { - logW("pattern font path is empty! reverting to default font\n"); - settings.patFont=0; - } - - if (settings.mainFont==6) { // custom font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load UI font! reverting to default font\n"); - settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } else if (settings.mainFont==5) { // system font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load UI font! reverting to default font\n"); - settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } - } - } else { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - - ImFontConfig fc; - fc.MergeMode=true; - fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; - static const ImWchar fontRange[]={ICON_MIN_FA,ICON_MAX_FA,0}; - if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRange))==NULL) { - logE("could not load icon font!\n"); - } - if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { - logD("using main font for pat font.\n"); - patFont=mainFont; - } else { - if (settings.patFont==6) { // custom font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load pattern font! reverting to default font\n"); - settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean.\n"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } else if (settings.patFont==5) { // system font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load pattern font! reverting to default font\n"); - settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean.\n"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } - } - } else { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load pattern font!\n"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } - if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { - logE("could not load big UI font!\n"); - } - - if (fileDialog!=NULL) delete fileDialog; - fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); -} - bool FurnaceGUI::init() { #ifndef __APPLE__ float dpiScaleF; @@ -6890,34 +2609,13 @@ bool FurnaceGUI::init() { } strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); + backupPath=e->getConfigPath()+String(BACKUP_FUR); prepareLayout(); ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; ImGui::GetIO().IniFilename=finalLayoutPath; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); - // TODO: allow changing these colors. - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - updateWindowTitle(); for (int i=0; i #include #include +#include +#include #include #include "fileDialog.h" @@ -34,6 +39,10 @@ #define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} #define unimportant(x) if (x) {handleUnimportant} +#define MARK_MODIFIED modified=true; + +#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, @@ -42,6 +51,8 @@ enum FurnaceGUIColors { GUI_COLOR_TEXT, GUI_COLOR_ACCENT_PRIMARY, GUI_COLOR_ACCENT_SECONDARY, + GUI_COLOR_TOGGLE_OFF, + GUI_COLOR_TOGGLE_ON, GUI_COLOR_EDITING, GUI_COLOR_SONG_LOOP, @@ -185,6 +196,7 @@ enum FurnaceGUIWarnings { GUI_WARN_QUIT, GUI_WARN_NEW, GUI_WARN_OPEN, + GUI_WARN_OPEN_BACKUP, GUI_WARN_OPEN_DROP, GUI_WARN_RESET_LAYOUT, GUI_WARN_GENERIC @@ -199,6 +211,7 @@ enum FurnaceGUIFMAlgs { enum FurnaceGUIActions { GUI_ACTION_GLOBAL_MIN=0, GUI_ACTION_OPEN, + GUI_ACTION_OPEN_BACKUP, GUI_ACTION_SAVE, GUI_ACTION_SAVE_AS, GUI_ACTION_UNDO, @@ -351,6 +364,35 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW, GUI_ACTION_SAMPLE_LIST_MAX, + GUI_ACTION_SAMPLE_MIN, + GUI_ACTION_SAMPLE_SELECT, + GUI_ACTION_SAMPLE_DRAW, + GUI_ACTION_SAMPLE_CUT, + GUI_ACTION_SAMPLE_COPY, + GUI_ACTION_SAMPLE_PASTE, + GUI_ACTION_SAMPLE_PASTE_REPLACE, + GUI_ACTION_SAMPLE_PASTE_MIX, + GUI_ACTION_SAMPLE_SELECT_ALL, + GUI_ACTION_SAMPLE_RESIZE, + GUI_ACTION_SAMPLE_RESAMPLE, + GUI_ACTION_SAMPLE_AMPLIFY, + GUI_ACTION_SAMPLE_NORMALIZE, + GUI_ACTION_SAMPLE_FADE_IN, + GUI_ACTION_SAMPLE_FADE_OUT, + GUI_ACTION_SAMPLE_SILENCE, + GUI_ACTION_SAMPLE_DELETE, + GUI_ACTION_SAMPLE_TRIM, + GUI_ACTION_SAMPLE_REVERSE, + GUI_ACTION_SAMPLE_INVERT, + GUI_ACTION_SAMPLE_SIGN, + GUI_ACTION_SAMPLE_FILTER, + GUI_ACTION_SAMPLE_PREVIEW, + GUI_ACTION_SAMPLE_STOP_PREVIEW, + GUI_ACTION_SAMPLE_ZOOM_IN, + GUI_ACTION_SAMPLE_ZOOM_OUT, + GUI_ACTION_SAMPLE_ZOOM_AUTO, + GUI_ACTION_SAMPLE_MAX, + GUI_ACTION_ORDERS_MIN, GUI_ACTION_ORDERS_UP, GUI_ACTION_ORDERS_DOWN, @@ -512,10 +554,16 @@ class FurnaceGUI { double aboutScroll, aboutSin; float aboutHue; + double backupTimer; + std::future backupTask; + std::mutex backupLock; + String backupPath; + ImFont* mainFont; ImFont* iconFont; ImFont* patFont; ImFont* bigFont; + ImWchar* fontRange; ImVec4 uiColors[GUI_COLOR_MAX]; ImVec4 volColors[128]; ImU32 pitchGrad[256]; @@ -567,6 +615,10 @@ class FurnaceGUI { int unifiedDataView; int sysFileDialog; // end + int roundedWindows; + int roundedButtons; + int roundedMenus; + int loadJapanese; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -613,6 +665,10 @@ class FurnaceGUI { stepOnInsert(0), unifiedDataView(0), sysFileDialog(1), + roundedWindows(1), + roundedButtons(1), + roundedMenus(0), + loadJapanese(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -625,7 +681,7 @@ class FurnaceGUI { int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; - bool mixerOpen, debugOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; + bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen; SelectionPoint selStart, selEnd, cursor; bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders; @@ -650,6 +706,7 @@ class FurnaceGUI { std::map actionMapGlobal; std::map actionMapPat; std::map actionMapOrders; + std::map actionMapSample; std::map actionMapInsList; std::map actionMapWaveList; std::map actionMapSampleList; @@ -737,16 +794,23 @@ class FurnaceGUI { // sample editor specific double sampleZoom; + double prevSampleZoom; int samplePos; int resizeSize; double resampleTarget; int resampleStrat; float amplifyVol; int sampleSelStart, sampleSelEnd; - bool sampleDragActive, sampleDragMode, sampleDrag16; + bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto; void* sampleDragTarget; ImVec2 sampleDragStart; ImVec2 sampleDragAreaSize; + unsigned int sampleDragLen; + float sampleFilterL, sampleFilterB, sampleFilterH, sampleFilterRes, sampleFilterCutStart, sampleFilterCutEnd; + unsigned char sampleFilterPower; + short* sampleClipboard; + size_t sampleClipboardLen; + bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleFilterOpt; // visualizer float keyHit[DIV_MAX_CHANS]; @@ -754,6 +818,7 @@ class FurnaceGUI { void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size); + void drawSysConf(int i); void updateWindowTitle(); void prepareLayout(); @@ -844,6 +909,7 @@ class FurnaceGUI { void exportAudio(String path, DivAudioExportModes mode); void applyUISettings(); + void initSystemPresets(); void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel); void encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel); @@ -865,8 +931,11 @@ class FurnaceGUI { void updateScroll(int amount); void addScroll(int amount); void setFileName(String name); + void runBackupThread(); bool loop(); bool finish(); bool init(); FurnaceGUI(); }; + +#endif \ No newline at end of file diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 3e12314c..c6744e62 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -19,7 +19,7 @@ // guiConst: constants used in the GUI like arrays, strings and other stuff #include "guiConst.h" -#include "../engine/instrument.h" +#include "../engine/song.h" const int opOrder[4]={ 0, 2, 1, 3 @@ -122,3 +122,48 @@ const char* resampleStrats[]={ "sinc", "best possible" }; + +// define systems. +const int availableSystems[]={ + DIV_SYSTEM_YM2612, + DIV_SYSTEM_YM2612_EXT, + DIV_SYSTEM_SMS, + DIV_SYSTEM_GB, + DIV_SYSTEM_PCE, + DIV_SYSTEM_NES, + DIV_SYSTEM_C64_8580, + DIV_SYSTEM_C64_6581, + DIV_SYSTEM_YM2151, + DIV_SYSTEM_SEGAPCM, + DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_YM2610, + DIV_SYSTEM_YM2610_EXT, + DIV_SYSTEM_YM2610_FULL, + DIV_SYSTEM_YM2610_FULL_EXT, + DIV_SYSTEM_YM2610B, + DIV_SYSTEM_YM2610B_EXT, + DIV_SYSTEM_AY8910, + DIV_SYSTEM_AMIGA, + DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_OPLL, + DIV_SYSTEM_OPLL_DRUMS, + DIV_SYSTEM_VRC7, + DIV_SYSTEM_OPL, + DIV_SYSTEM_OPL_DRUMS, + DIV_SYSTEM_OPL2, + DIV_SYSTEM_OPL2_DRUMS, + DIV_SYSTEM_OPL3, + DIV_SYSTEM_OPL3_DRUMS, + DIV_SYSTEM_TIA, + DIV_SYSTEM_SAA1099, + DIV_SYSTEM_AY8930, + DIV_SYSTEM_LYNX, + DIV_SYSTEM_QSOUND, + DIV_SYSTEM_X1_010, + DIV_SYSTEM_SWAN, + DIV_SYSTEM_VERA, + DIV_SYSTEM_BUBSYS_WSG, + DIV_SYSTEM_N163, + DIV_SYSTEM_PET, + 0 // don't remove this last one! +}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index ee49c1da..25b4bc4c 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -25,4 +25,5 @@ extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; extern const char* sampleDepths[17]; -extern const char* resampleStrats[]; \ No newline at end of file +extern const char* resampleStrats[]; +extern const int availableSystems[]; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0afac9cc..4bd54d21 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -673,11 +673,11 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } #define P(x) if (x) { \ - modified=true; \ + MARK_MODIFIED; \ e->notifyInsChange(curIns); \ } -#define PARAMETER modified=true; e->notifyInsChange(curIns); +#define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); #define NORMAL_MACRO(macro,macroLen,macroLoop,macroRel,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc,blockMode) \ ImGui::TableNextRow(); \ @@ -689,7 +689,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } \ if (displayLoop) { \ ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { \ + if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED \ if (macroLen>127) macroLen=127; \ } \ if (macroMode!=NULL) { \ @@ -778,7 +778,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } \ if (displayLoop) { \ ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IOPMacroLen_" #op macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { \ + if (ImGui::InputScalar("##IOPMacroLen_" #op macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED \ if (macroLen>127) macroLen=127; \ } \ } \ @@ -928,7 +928,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("no instrument selected"); } else { DivInstrument* ins=e->song.ins[curIns]; - ImGui::InputText("Name",&ins->name); + if (ImGui::InputText("Name",&ins->name)) { + MARK_MODIFIED; + } if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { @@ -1372,25 +1374,25 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { ImGui::Text("Waveform"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.triOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn)); if (ImGui::Button("tri")) { PARAMETER ins->c64.triOn=!ins->c64.triOn; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.sawOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn)); if (ImGui::Button("saw")) { PARAMETER ins->c64.sawOn=!ins->c64.sawOn; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.pulseOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn)); if (ImGui::Button("pulse")) { PARAMETER ins->c64.pulseOn=!ins->c64.pulseOn; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.noiseOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn)); if (ImGui::Button("noise")) { PARAMETER ins->c64.noiseOn=!ins->c64.noiseOn; } @@ -1419,25 +1421,25 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Filter Mode"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.lp)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp)); if (ImGui::Button("low")) { PARAMETER ins->c64.lp=!ins->c64.lp; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.bp)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp)); if (ImGui::Button("band")) { PARAMETER ins->c64.bp=!ins->c64.bp; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.hp)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp)); if (ImGui::Button("high")) { PARAMETER ins->c64.hp=!ins->c64.hp; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.ch3off)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off)); if (ImGui::Button("ch3off")) { PARAMETER ins->c64.ch3off=!ins->c64.ch3off; } @@ -1554,6 +1556,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GB) { volMax=0; } + if (ins->type==DIV_INS_PET) { + volMax=1; + } bool arpMode=ins->std.arpMacroMode; @@ -1583,7 +1588,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY8930) { dutyMax=255; } - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC) { + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET) { dutyMax=0; } if (ins->type==DIV_INS_PCE) { @@ -1617,8 +1622,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SAA1099) waveMax=2; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; + if (ins->type==DIV_INS_PET) { + waveMax=8; + bitMode=true; + } - const char** waveNames=ayShapeBits; + const char** waveNames=NULL; + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) waveNames=ayShapeBits; if (ins->type==DIV_INS_C64) waveNames=c64ShapeBits; int ex1Max=(ins->type==DIV_INS_AY8930)?8:0; @@ -1959,131 +1969,3 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT; ImGui::End(); } - -#undef P -#undef PARAMETER - -void FurnaceGUI::drawWaveList() { - if (nextWindow==GUI_WINDOW_WAVE_LIST) { - waveListOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!waveListOpen) return; - if (ImGui::Begin("Wavetables",&waveListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { - doAction(GUI_ACTION_WAVE_LIST_ADD); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FILES_O "##WaveClone")) { - doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { - doAction(GUI_ACTION_WAVE_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { - doAction(GUI_ACTION_WAVE_LIST_SAVE); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_WAVE_LIST_UP); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_WAVE_LIST_DOWN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##WaveDelete")) { - doAction(GUI_ACTION_WAVE_LIST_DELETE); - } - ImGui::Separator(); - if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { - actualWaveList(); - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; - ImGui::End(); -} - -void FurnaceGUI::drawWaveEdit() { - if (nextWindow==GUI_WINDOW_WAVE_EDIT) { - waveEditOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!waveEditOpen) return; - float wavePreview[256]; - ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - if (curWave<0 || curWave>=(int)e->song.wave.size()) { - ImGui::Text("no wavetable selected"); - } else { - DivWavetable* wave=e->song.wave[curWave]; - ImGui::Text("Width"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- 32 on Game Boy, PC Engine, WonderSwan and Bubble System WSG\n- 128 on X1-010\nany other widths will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); - if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { - if (wave->len>256) wave->len=256; - if (wave->len<1) wave->len=1; - e->notifyWaveChange(curWave); - if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); - modified=true; - } - ImGui::SameLine(); - ImGui::Text("Height"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 envelope shape, Bubble System WSG and N163\n- 31 for PC Engine\n- 255 for X1-010 waveform\nany other heights will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); - if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { - if (wave->max>255) wave->max=255; - if (wave->max<1) wave->max=1; - e->notifyWaveChange(curWave); - modified=true; - } - for (int i=0; ilen; i++) { - if (wave->data[i]>wave->max) wave->data[i]=wave->max; - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); //wavetable text input size found here - if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max); - } - if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1); - } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - - ImVec2 contentRegion=ImGui::GetContentRegionAvail(); //wavetable graph size determined here - if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { - contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); - } - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - waveDragStart=ImGui::GetItemRectMin(); - waveDragAreaSize=contentRegion; - waveDragMin=0; - waveDragMax=wave->max; - waveDragLen=wave->len; - waveDragActive=true; - waveDragTarget=wave->data; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - e->notifyWaveChange(curWave); - modified=true; - } - ImGui::PopStyleVar(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_EDIT; - ImGui::End(); -} - diff --git a/src/gui/mixer.cpp b/src/gui/mixer.cpp new file mode 100644 index 00000000..04710176 --- /dev/null +++ b/src/gui/mixer.cpp @@ -0,0 +1,59 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "intConst.h" + +void FurnaceGUI::drawMixer() { + if (nextWindow==GUI_WINDOW_MIXER) { + mixerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!mixerOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + char id[32]; + if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { + if (e->song.masterVol<0) e->song.masterVol=0; + if (e->song.masterVol>3) e->song.masterVol=3; + } rightClickable + for (int i=0; isong.systemLen; i++) { + snprintf(id,31,"MixS%d",i); + bool doInvert=e->song.systemVol[i]&128; + signed char vol=e->song.systemVol[i]&127; + ImGui::PushID(id); + ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); + ImGui::SameLine(ImGui::GetWindowWidth()-(82.0f*dpiScale)); + if (ImGui::Checkbox("Invert",&doInvert)) { + e->song.systemVol[i]^=128; + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); + if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { + e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol; + } rightClickable + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); + ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable + + ImGui::PopID(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MIXER; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp new file mode 100644 index 00000000..d6188b3e --- /dev/null +++ b/src/gui/newSong.cpp @@ -0,0 +1,90 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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" + +void FurnaceGUI::drawNewSong() { + bool accepted=false; + + ImGui::PushFont(bigFont); + ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Choose a System!").x)*0.5); + ImGui::Text("Choose a System!"); + ImGui::PopFont(); + + if (ImGui::BeginTable("sysPicker",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Categories"); + ImGui::TableNextColumn(); + ImGui::Text("Systems"); + + ImGui::TableNextRow(); + + // CATEGORIES + ImGui::TableNextColumn(); + int index=0; + for (FurnaceGUISysCategory& i: sysCategories) { + if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ + newSongCategory=index; + } + index++; + } + + // SYSTEMS + ImGui::TableNextColumn(); + if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { + for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { + nextDesc=i.definition.data(); + accepted=true; + } + } + ImGui::EndTable(); + } + + ImGui::EndTable(); + } + + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + + if (accepted) { + e->createNew(nextDesc); + undoHist.clear(); + redoHist.clear(); + curFileName=""; + modified=false; + curNibble=false; + orderNibble=false; + orderCursor=-1; + samplePos=0; + updateSampleTex=true; + selStart=SelectionPoint(); + selEnd=SelectionPoint(); + cursor=SelectionPoint(); + updateWindowTitle(); + ImGui::CloseCurrentPopup(); + } +} \ No newline at end of file diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 43976fdc..37faba8d 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -97,13 +97,15 @@ void FurnaceGUI::drawOrders() { if (curOrder==i) { if (orderEditMode==0) { prepareUndo(GUI_UNDO_CHANGE_ORDER); - if (changeAllOrders) { - for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++; + e->lockSave([this,i,j]() { + if (changeAllOrders) { + for (int k=0; kgetTotalChannelCount(); k++) { + if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++; + } + } else { + if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++; } - } else { - if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++; - } + }); e->walkSong(loopOrder,loopRow,loopEnd); makeUndo(GUI_UNDO_CHANGE_ORDER); } else { @@ -130,13 +132,15 @@ void FurnaceGUI::drawOrders() { if (curOrder==i) { if (orderEditMode==0) { prepareUndo(GUI_UNDO_CHANGE_ORDER); - if (changeAllOrders) { - for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + e->lockSave([this,i,j]() { + if (changeAllOrders) { + for (int k=0; kgetTotalChannelCount(); k++) { + if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + } + } else { + if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; } - } else { - if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; - } + }); e->walkSong(loopOrder,loopRow,loopEnd); makeUndo(GUI_UNDO_CHANGE_ORDER); } else { diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp new file mode 100644 index 00000000..0bdcdf8a --- /dev/null +++ b/src/gui/osc.cpp @@ -0,0 +1,47 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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" + +void FurnaceGUI::drawOsc() { + if (nextWindow==GUI_WINDOW_OSCILLOSCOPE) { + oscOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!oscOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); + if (ImGui::Begin("Oscilloscope",&oscOpen)) { + float values[512]; + for (int i=0; i<512; i++) { + int pos=i*e->oscSize/512; + values[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; + } + //ImGui::SetCursorPos(ImVec2(0,0)); + ImGui::BeginDisabled(); + ImGui::PlotLines("##SingleOsc",values,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); + ImGui::EndDisabled(); + } + ImGui::PopStyleVar(3); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp new file mode 100644 index 00000000..d3a3d35e --- /dev/null +++ b/src/gui/piano.cpp @@ -0,0 +1,46 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "guiConst.h" + +void FurnaceGUI::drawPiano() { + if (nextWindow==GUI_WINDOW_PIANO) { + pianoOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!pianoOpen) return; + if (ImGui::Begin("Piano",&pianoOpen)) { + for (int i=0; igetTotalChannelCount(); i++) { + DivChannelState* cs=e->getChanState(i); + if (cs->keyOn) { + const char* noteName=NULL; + if (cs->note<-60 || cs->note>120) { + noteName="???"; + } else { + noteName=noteNames[cs->note+60]; + } + ImGui::Text("%d: %s",i,noteName); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PIANO; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp new file mode 100644 index 00000000..ba747855 --- /dev/null +++ b/src/gui/presets.cpp @@ -0,0 +1,713 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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" + +// add system configurations here. +// every entry is written in the following format: +// cat.systems.push_back(FurnaceGUISysDef( +// "System Name", { +// DIV_SYSTEM_???, Volume, Panning, Flags, +// DIV_SYSTEM_???, Volume, Panning, Flags, +// ... +// 0 +// } +// )); + +void FurnaceGUI::initSystemPresets() { + FurnaceGUISysCategory cat; + + cat=FurnaceGUISysCategory("FM"); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2612", { + DIV_SYSTEM_YM2612, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2612 (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2151", { + DIV_SYSTEM_YM2151, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610 (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610B", { + DIV_SYSTEM_YM2610B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610B (extended channel 3)", { + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413", { + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413 (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526", { + DIV_SYSTEM_OPL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526 (drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812", { + DIV_SYSTEM_OPL2, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812 (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262", { + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Square"); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76489", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "AY-3-8910", { + DIV_SYSTEM_AY8910, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Philips SAA1099", { + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Sample"); + cat.systems.push_back(FurnaceGUISysDef( + "Amiga", { + DIV_SYSTEM_AMIGA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SegaPCM", { + DIV_SYSTEM_SEGAPCM, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom QSound", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta/Allumer X1-010", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Game consoles"); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis", { + DIV_SYSTEM_YM2612, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion in drums mode)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Game Boy", { + DIV_SYSTEM_GB, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC Engine/TurboGrafx-16", { + DIV_SYSTEM_PCE, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES", { + DIV_SYSTEM_NES, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC7", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC7, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Sunsoft 5B", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 38, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Namco 163", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_N163, 64, 0, 112, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Mattel Intellivision", { + DIV_SYSTEM_AY8910, 64, 0, 48, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Vectrex", { + DIV_SYSTEM_AY8910, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo AES", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo AES (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari 2600/7800", { + DIV_SYSTEM_TIA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Lynx", { + DIV_SYSTEM_LYNX, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "WonderSwan", { + DIV_SYSTEM_SWAN, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Gamate", { + DIV_SYSTEM_AY8910, 64, 0, 73, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Computers"); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore PET", { + DIV_SYSTEM_PET, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore VIC-20", { + DIV_SYSTEM_VIC20, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID)", { + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID)", { + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Amiga", { + DIV_SYSTEM_AMIGA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX", { + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SFG-01", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC", { + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (48K)", { + DIV_SYSTEM_AY8910, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Amstrad CPC", { + DIV_SYSTEM_AY8910, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SAM Coupé", { + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "BBC Micro", { + DIV_SYSTEM_SMS, 64, 0, 6, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC (barebones)", { + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Covox Sound Master", { + DIV_SYSTEM_AY8930, 64, 0, 3, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + SSI 2001", { + DIV_SYSTEM_C64_6581, 64, 0, 2, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Game Blaster", { + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib/Sound Blaster", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib/Sound Blaster (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro", { + DIV_SYSTEM_OPL2, 64, -127, 0, + DIV_SYSTEM_OPL2, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, + DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro 2", { + DIV_SYSTEM_OPL3, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro 2 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + SAAYM", { + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz or 4MHz selectable via jumper + DIV_SYSTEM_SAA1099, 64, -127, 1, // 7.16MHz or 8MHz selectable via jumper + DIV_SYSTEM_SAA1099, 64, 127, 1, // "" + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1", { + DIV_SYSTEM_AY8910, 64, 0, 3, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1 + FM Addon", { + DIV_SYSTEM_AY8910, 64, 0, 3, + DIV_SYSTEM_YM2151, 64, 0, 2, + 0 + } + )); + /* + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X68000", { + DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_MSM6258, 64, 0, 0, + 0 + } + ));*/ + cat.systems.push_back(FurnaceGUISysDef( + "Commander X16", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_VERA, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Arcade systems"); + cat.systems.push_back(FurnaceGUISysDef( + "Bally Midway MCR", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Kyugo", { + DIV_SYSTEM_AY8910, 64, 0, 4, + DIV_SYSTEM_AY8910, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega OutRun/X Board", { + DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_SEGAPCM, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo MVS", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo MVS (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Taito Arcade", { + DIV_SYSTEM_YM2610B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Taito Arcade (extended channel 3)", { + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-2 (QSound)", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1 + FM addon", { + DIV_SYSTEM_YM2612, 64, 0, 2, // Discrete YM3438 + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 2", { + DIV_SYSTEM_X1_010, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Bubble System", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, + // VLM5030 exists but not used for music at all + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("DefleMask-compatible"); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis", { + DIV_SYSTEM_YM2612, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Game Boy", { + DIV_SYSTEM_GB, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC Engine/TurboGrafx-16", { + DIV_SYSTEM_PCE, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES", { + DIV_SYSTEM_NES, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC7", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC7, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID)", { + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID)", { + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Arcade (YM2151 and SegaPCM)", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo CD", { + DIV_SYSTEM_YM2610, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo CD (extended channel 2)", { + DIV_SYSTEM_YM2610_EXT, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); +} diff --git a/src/gui/regView.cpp b/src/gui/regView.cpp new file mode 100644 index 00000000..9755e4a8 --- /dev/null +++ b/src/gui/regView.cpp @@ -0,0 +1,71 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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" + +void FurnaceGUI::drawRegView() { + if (nextWindow==GUI_WINDOW_REGISTER_VIEW) { + channelsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!regViewOpen) return; + if (ImGui::Begin("Register View",®ViewOpen)) { + for (int i=0; isong.systemLen; i++) { + ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); + int size=0; + int depth=8; + unsigned char* regPool=e->getRegisterPool(i,size,depth); + unsigned short* regPoolW=(unsigned short*)regPool; + if (regPool==NULL) { + ImGui::Text("- no register pool available"); + } else { + ImGui::PushFont(patFont); + if (ImGui::BeginTable("Memory",17)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + for (int i=0; i<16; i++) { + ImGui::TableNextColumn(); + ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX]," %X",i); + } + for (int i=0; i<=((size-1)>>4); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX],"%.2X",i*16); + for (int j=0; j<16; j++) { + ImGui::TableNextColumn(); + if (i*16+j>=size) continue; + if (depth == 8) { + ImGui::Text("%.2x",regPool[i*16+j]); + } else if (depth == 16) { + ImGui::Text("%.4x",regPoolW[i*16+j]); + } else { + ImGui::Text("??"); + } + } + } + ImGui::EndTable(); + } + ImGui::PopFont(); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_REGISTER_VIEW; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index f63080cf..28e0f3d5 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -18,12 +18,14 @@ */ #include "gui.h" #include +#include #include #include "../ta-log.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include #include "guiConst.h" +#include "sampleUtil.h" void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { @@ -46,7 +48,9 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Name"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##SampleName",&sample->name); + if (ImGui::InputText("##SampleName",&sample->name)) { + MARK_MODIFIED; + } if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); @@ -61,6 +65,7 @@ void FurnaceGUI::drawSampleEdit() { sample->depth=i; e->renderSamplesP(); updateSampleTex=true; + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("no undo for sample type change operations!"); @@ -73,7 +78,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Rate (Hz)"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { + if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { MARK_MODIFIED if (sample->rate<100) sample->rate=100; if (sample->rate>96000) sample->rate=96000; } @@ -82,14 +87,14 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("C-4 (Hz)"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { + if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { MARK_MODIFIED if (sample->centerRate<100) sample->centerRate=100; if (sample->centerRate>65535) sample->centerRate=65535; } ImGui::TableNextColumn(); bool doLoop=(sample->loopStart>=0); - if (ImGui::Checkbox("Loop",&doLoop)) { + if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loopStart=0; } else { @@ -100,7 +105,7 @@ void FurnaceGUI::drawSampleEdit() { if (doLoop) { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { + if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { sample->loopStart=0; } @@ -110,40 +115,30 @@ void FurnaceGUI::drawSampleEdit() { ImGui::EndTable(); } - if (ImGui::InputDouble("Zoom",&sampleZoom,0.1,2.0)) { - if (sampleZoom<0.01) sampleZoom=0.01; - updateSampleTex=true; - } - if (ImGui::InputInt("Pos",&samplePos,1,10)) { - if (samplePos>=(int)sample->samples) samplePos=sample->samples-1; - if (samplePos<0) samplePos=0; - updateSampleTex=true; - } - /* if (ImGui::Button("Apply")) { e->renderSamplesP(); } ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { - e->previewSample(curSample); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { - e->stopSamplePreview(); - }*/ + */ ImGui::Separator(); + ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } + ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } + ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -157,19 +152,26 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resize"); } + if (openSampleResizeOpt) { + openSampleResizeOpt=false; + ImGui::OpenPopup("SResizeOpt"); + } if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { if (ImGui::InputInt("Samples",&resizeSize,1,64)) { if (resizeSize<0) resizeSize=0; if (resizeSize>16777215) resizeSize=16777215; } if (ImGui::Button("Resize")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { if (!sample->resize(resizeSize)) { showError("couldn't resize! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -184,6 +186,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resample"); } + if (openSampleResampleOpt) { + openSampleResampleOpt=false; + ImGui::OpenPopup("SResampleOpt"); + } if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { ImGui::Text("Rate"); if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { @@ -210,13 +216,16 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); if (ImGui::Button("Resample")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { if (!sample->resample(resampleTarget,resampleStrat)) { showError("couldn't resample! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -230,6 +239,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Amplify"); } + if (openSampleAmplifyOpt) { + openSampleAmplifyOpt=false; + ImGui::OpenPopup("SAmplifyOpt"); + } if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { ImGui::Text("Volume"); if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { @@ -238,33 +251,98 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::SameLine(); ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); - ImGui::Button("Apply"); + if (ImGui::Button("Apply")) { + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float vol=amplifyVol/100.0f; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } ImGui::EndPopup(); } ImGui::SameLine(); - ImGui::Button(ICON_FA_ARROWS_V "##SNormalize"); + if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { + doAction(GUI_ACTION_SAMPLE_NORMALIZE); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Normalize"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_ERASER "##SSilence"); + if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { + doAction(GUI_ACTION_SAMPLE_FADE_IN); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade in"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { + doAction(GUI_ACTION_SAMPLE_FADE_OUT); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade out"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { + doAction(GUI_ACTION_SAMPLE_SILENCE); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply silence"); } ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { + doAction(GUI_ACTION_SAMPLE_DELETE); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CROP "##STrim")) { + doAction(GUI_ACTION_SAMPLE_TRIM); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Trim"); + } + ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); - ImGui::Button(ICON_FA_BACKWARD "##SReverse"); + if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { + doAction(GUI_ACTION_SAMPLE_REVERSE); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Reverse"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert"); + if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { + doAction(GUI_ACTION_SAMPLE_INVERT); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Invert"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign"); + if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { + doAction(GUI_ACTION_SAMPLE_SIGN); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Signed/unsigned exchange"); } @@ -273,14 +351,178 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply filter"); } + if (openSampleFilterOpt) { + openSampleFilterOpt=false; + ImGui::OpenPopup("SFilterOpt"); + } + if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { + float lowP=sampleFilterL*100.0f; + float bandP=sampleFilterB*100.0f; + float highP=sampleFilterH*100.0f; + float resP=sampleFilterRes*100.0f; + ImGui::Text("Cutoff:"); + if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; + if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; + } + if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; + if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { + sampleFilterRes=resP/100.0f; + if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; + if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; + } + ImGui::Text("Power"); + ImGui::SameLine(); + if (ImGui::RadioButton("1x",sampleFilterPower==1)) { + sampleFilterPower=1; + } + ImGui::SameLine(); + if (ImGui::RadioButton("2x",sampleFilterPower==2)) { + sampleFilterPower=2; + } + ImGui::SameLine(); + if (ImGui::RadioButton("3x",sampleFilterPower==3)) { + sampleFilterPower=3; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { + sampleFilterL=lowP/100.0f; + if (sampleFilterL<0.0f) sampleFilterL=0.0f; + if (sampleFilterL>1.0f) sampleFilterL=1.0f; + } + if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { + sampleFilterB=bandP/100.0f; + if (sampleFilterB<0.0f) sampleFilterB=0.0f; + if (sampleFilterB>1.0f) sampleFilterB=1.0f; + } + if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { + sampleFilterH=highP/100.0f; + if (sampleFilterH<0.0f) sampleFilterH=0.0f; + if (sampleFilterH>1.0f) sampleFilterH=1.0f; + } + + if (ImGui::Button("Apply")) { + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float res=1.0-pow(sampleFilterRes,0.5f); + float low=0; + float band=0; + float high=0; + + double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; + + if (sample->depth==16) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata16[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata8[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { + e->previewSample(curSample); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Preview sample"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { + e->stopSamplePreview(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop sample preview"); + } + + ImGui::SameLine(); + double zoomPercent=100.0/sampleZoom; + ImGui::Text("Zoom"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(150.0f*dpiScale); + if (ImGui::InputDouble("##SZoom",&zoomPercent,5.0,20.0,"%g%%")) { + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + updateSampleTex=true; + } + ImGui::SameLine(); + if (sampleZoomAuto) { + if (ImGui::Button("100%")) { + sampleZoom=1.0; + sampleZoomAuto=false; + updateSampleTex=true; + } + } else { + if (ImGui::Button("Auto")) { + sampleZoomAuto=true; + updateSampleTex=true; + } + } ImGui::Separator(); ImVec2 avail=ImGui::GetContentRegionAvail(); - avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y; + avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y+ImGui::GetStyle().ScrollbarSize; int availX=avail.x; int availY=avail.y; + + if (sampleZoomAuto) { + samplePos=0; + if (sample->samples<1 || avail.x<=0) { + sampleZoom=1.0; + } else { + sampleZoom=(double)sample->samples/avail.x; + } + if (sampleZoom!=prevSampleZoom) { + prevSampleZoom=sampleZoom; + updateSampleTex=true; + } + } + if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) { if (sampleTex!=NULL) { SDL_DestroyTexture(sampleTex); @@ -320,7 +562,11 @@ void FurnaceGUI::drawSampleEdit() { if (xCoarse>=sample->samples) break; int y1, y2; int totalAdvance=0; - y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (sample->depth==8) { + y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + } else { + y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + } xFine+=xAdvanceFine; if (xFine>=16777216) { xFine-=16777216; @@ -329,14 +575,22 @@ void FurnaceGUI::drawSampleEdit() { totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { - y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (sample->depth==8) { + y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + } else { + y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + } if (y1>y2) { y2^=y1; y1^=y2; y2^=y1; } + if (y1<0) y1=0; + if (y1>=availY) y1=availY-1; + if (y2<0) y2=0; + if (y2>=availY) y2=availY-1; for (int j=y1; j<=y2; j++) { - data[i+availX*j]=lineColor; + data[i+availX*(availY-j-1)]=lineColor; } if (totalAdvance>0) xCoarse++; } while ((totalAdvance--)>0); @@ -347,13 +601,35 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); + + ImVec2 rectMin=ImGui::GetItemRectMin(); + ImVec2 rectMax=ImGui::GetItemRectMax(); + ImVec2 rectSize=ImGui::GetItemRectSize(); + if (ImGui::IsItemClicked()) { - logD("drawing\n"); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleDragActive=false; + sampleSelStart=0; + sampleSelEnd=sample->samples; + } else { + if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { + sampleDragStart=rectMin; + sampleDragAreaSize=rectSize; + sampleDrag16=(sample->depth==16); + sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); + sampleDragLen=sample->samples; + sampleDragActive=true; + sampleSelStart=-1; + sampleSelEnd=-1; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + } } String statusBar=sampleDragMode?"Draw":"Select"; + bool drawSelection=false; if (!sampleDragMode) { - if (sampleSelStart>=0 && sampleSelEnd>=0) { + if (sampleSelStart>=0 && sampleSelEnd>=0 && sampleSelStart!=sampleSelEnd) { int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { @@ -362,6 +638,7 @@ void FurnaceGUI::drawSampleEdit() { start^=end; } statusBar+=fmt::sprintf(" (%d-%d)",start,end); + drawSelection=true; } } @@ -369,19 +646,56 @@ void FurnaceGUI::drawSampleEdit() { int posX=-1; int posY=0; ImVec2 pos=ImGui::GetMousePos(); - pos.x-=ImGui::GetItemRectMin().x; - pos.y-=ImGui::GetItemRectMin().y; + pos.x-=rectMin.x; + pos.y-=rectMin.y; if (sampleZoom>0) { posX=samplePos+pos.x*sampleZoom; if (posX>(int)sample->samples) posX=-1; } - posY=(0.5-pos.y/ImGui::GetItemRectSize().y)*((sample->depth==8)?255:32767); + posY=(0.5-pos.y/rectSize.y)*((sample->depth==8)?255:32767); if (posX>=0) { statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); } } + + if (drawSelection) { + int start=sampleSelStart; + int end=sampleSelEnd; + if (start>end) { + start^=end; + end^=start; + start^=end; + } + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 p1=rectMin; + p1.x+=start/sampleZoom-samplePos; + + ImVec2 p2=ImVec2(rectMin.x+end/sampleZoom-samplePos,rectMax.y); + ImVec4 selColor=uiColors[GUI_COLOR_ACCENT_SECONDARY]; + selColor.w*=0.25; + + dl->AddRectFilled(p1,p2,ImGui::GetColorU32(selColor)); + } + + ImS64 scrollV=samplePos; + ImS64 availV=round(rectSize.x*sampleZoom); + ImS64 contentsV=MAX(sample->samples,MAX(availV,1)); + + if (ImGui::ScrollbarEx(ImRect(ImVec2(rectMin.x,rectMax.y),ImVec2(rectMax.x,rectMax.y+ImGui::GetStyle().ScrollbarSize)),ImGui::GetID("sampleScroll"),ImGuiAxis_X,&scrollV,availV,contentsV,0)) { + if (!sampleZoomAuto && samplePos!=scrollV) { + samplePos=scrollV; + updateSampleTex=true; + } + } + + if (sample->depth!=8 && sample->depth!=16) { + statusBar="Non-8/16-bit samples cannot be edited without prior conversion."; + } + + ImGui::EndDisabled(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); ImGui::Text("%s",statusBar.c_str()); } diff --git a/src/gui/sampleUtil.h b/src/gui/sampleUtil.h new file mode 100644 index 00000000..981a5604 --- /dev/null +++ b/src/gui/sampleUtil.h @@ -0,0 +1,31 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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. + */ + +#define SAMPLE_OP_BEGIN \ + unsigned int start=0; \ + unsigned int end=sample->samples; \ + if (sampleSelStart!=-1 && sampleSelEnd!=-1 && sampleSelStart!=sampleSelEnd) { \ + start=sampleSelStart; \ + end=sampleSelEnd; \ + if (start>end) { \ + start^=end; \ + end^=start; \ + start^=end; \ + } \ + } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index aa342ae9..9a1a9eee 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -18,10 +18,13 @@ */ #include "gui.h" +#include "fonts.h" #include "../ta-log.h" #include "util.h" +#include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include #include #include #include @@ -344,6 +347,19 @@ void FurnaceGUI::drawSettings() { if (settings.patFontSize>96) settings.patFontSize=96; } + bool loadJapaneseB=settings.loadJapanese; + if (ImGui::Checkbox("Display Japanese characters",&loadJapaneseB)) { + settings.loadJapanese=loadJapaneseB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "このオプションは、十分なグラフィックメモリがある場合にのみ切り替えてください。\n" + "これは、Dear ImGuiにダイナミックフォントアトラスが実装されるまでの一時的な解決策です。" + ); + } + ImGui::Separator(); ImGui::Text("Orders row number format:"); @@ -449,6 +465,23 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + bool roundedWindowsB=settings.roundedWindows; + if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) { + settings.roundedWindows=roundedWindowsB; + } + + bool roundedButtonsB=settings.roundedButtons; + if (ImGui::Checkbox("Rounded buttons",&roundedButtonsB)) { + settings.roundedButtons=roundedButtonsB; + } + + bool roundedMenusB=settings.roundedMenus; + if (ImGui::Checkbox("Rounded menu corners",&roundedMenusB)) { + settings.roundedMenus=roundedMenusB; + } + + ImGui::Separator(); + if (ImGui::TreeNode("Color scheme")) { if (ImGui::TreeNode("General")) { ImGui::Text("Color scheme type:"); @@ -465,6 +498,8 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); + UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); UI_COLOR_CONFIG(GUI_COLOR_SONG_LOOP,"Song loop"); UI_COLOR_CONFIG(GUI_COLOR_PLAYBACK_STAT,"Playback status"); @@ -562,6 +597,7 @@ void FurnaceGUI::drawSettings() { KEYBIND_CONFIG_BEGIN("keysGlobal"); UI_KEYBIND_CONFIG(GUI_ACTION_OPEN,"Open file"); + UI_KEYBIND_CONFIG(GUI_ACTION_OPEN_BACKUP,"Restore backup"); UI_KEYBIND_CONFIG(GUI_ACTION_SAVE,"Save file"); UI_KEYBIND_CONFIG(GUI_ACTION_SAVE_AS,"Save as"); UI_KEYBIND_CONFIG(GUI_ACTION_UNDO,"Undo"); @@ -830,6 +866,39 @@ void FurnaceGUI::drawSettings() { KEYBIND_CONFIG_END; ImGui::TreePop(); } + if (ImGui::TreeNode("Sample editor")) { + KEYBIND_CONFIG_BEGIN("keysSampleEdit"); + + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT,"Edit mode: Select"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DRAW,"Edit mode: Draw"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_CUT,"Cut"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_COPY,"Copy"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE,"Paste"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_REPLACE,"Paste replace"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_MIX,"Paste mix"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT_ALL,"Select all"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESIZE,"Resize"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESAMPLE,"Resample"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_AMPLIFY,"Amplify"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE,"Normalize"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN,"Fade in"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT,"Fade out"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE,"Apply silence"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE,"Delete"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM,"Trim"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_REVERSE,"Reverse"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INVERT,"Invert"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SIGN,"Signed/unsigned exchange"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FILTER,"Apply filter"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PREVIEW,"Preview sample"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_STOP_PREVIEW,"Stop sample preview"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_IN,"Zoom in"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_OUT,"Zoom out"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_AUTO,"Toggle auto-zoom"); + + KEYBIND_CONFIG_END; + ImGui::TreePop(); + } ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -904,6 +973,10 @@ void FurnaceGUI::syncSettings() { settings.stepOnInsert=e->getConfInt("stepOnInsert",0); settings.unifiedDataView=e->getConfInt("unifiedDataView",0); settings.sysFileDialog=e->getConfInt("sysFileDialog",1); + settings.roundedWindows=e->getConfInt("roundedWindows",1); + settings.roundedButtons=e->getConfInt("roundedButtons",1); + settings.roundedMenus=e->getConfInt("roundedMenus",0); + settings.loadJapanese=e->getConfInt("loadJapanese",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -944,9 +1017,14 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.stepOnInsert,0,1); clampSetting(settings.unifiedDataView,0,1); clampSetting(settings.sysFileDialog,0,1); + clampSetting(settings.roundedWindows,0,1); + clampSetting(settings.roundedButtons,0,1); + clampSetting(settings.roundedMenus,0,1); + clampSetting(settings.loadJapanese,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); + LOAD_KEYBIND(GUI_ACTION_OPEN_BACKUP,0); LOAD_KEYBIND(GUI_ACTION_SAVE,FURKMOD_CMD|SDLK_s); LOAD_KEYBIND(GUI_ACTION_SAVE_AS,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_s); LOAD_KEYBIND(GUI_ACTION_UNDO,FURKMOD_CMD|SDLK_z); @@ -1075,6 +1153,33 @@ void FurnaceGUI::syncSettings() { LOAD_KEYBIND(GUI_ACTION_SAMPLE_LIST_PREVIEW,0); LOAD_KEYBIND(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW,0); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT,FURKMOD_SHIFT|SDLK_i); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_DRAW,FURKMOD_SHIFT|SDLK_d); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_CUT,FURKMOD_CMD|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_COPY,FURKMOD_CMD|SDLK_c); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE,FURKMOD_CMD|SDLK_v); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX,FURKMOD_CMD|FURKMOD_ALT|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL,FURKMOD_CMD|SDLK_a); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESIZE,FURKMOD_CMD|SDLK_r); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE,FURKMOD_CMD|SDLK_e); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_AMPLIFY,FURKMOD_CMD|SDLK_b); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE,FURKMOD_CMD|SDLK_n); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN,FURKMOD_CMD|SDLK_i); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT,FURKMOD_CMD|SDLK_o); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SILENCE,FURKMOD_SHIFT|SDLK_DELETE); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_DELETE,SDLK_DELETE); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_TRIM,FURKMOD_CMD|SDLK_DELETE); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_REVERSE,FURKMOD_CMD|SDLK_t); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_INVERT,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_t); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SIGN,FURKMOD_CMD|SDLK_u); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_FILTER,FURKMOD_CMD|SDLK_f); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PREVIEW,0); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_STOP_PREVIEW,0); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_IN,FURKMOD_CMD|SDLK_EQUALS); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_OUT,FURKMOD_CMD|SDLK_MINUS); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_AUTO,FURKMOD_CMD|SDLK_0); + LOAD_KEYBIND(GUI_ACTION_ORDERS_UP,SDLK_UP); LOAD_KEYBIND(GUI_ACTION_ORDERS_DOWN,SDLK_DOWN); LOAD_KEYBIND(GUI_ACTION_ORDERS_LEFT,SDLK_LEFT); @@ -1145,6 +1250,10 @@ void FurnaceGUI::commitSettings() { e->setConf("stepOnInsert",settings.stepOnInsert); e->setConf("unifiedDataView",settings.unifiedDataView); e->setConf("sysFileDialog",settings.sysFileDialog); + e->setConf("roundedWindows",settings.roundedWindows); + e->setConf("roundedButtons",settings.roundedButtons); + e->setConf("roundedMenus",settings.roundedMenus); + e->setConf("loadJapanese",settings.loadJapanese); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); @@ -1153,6 +1262,8 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_TEXT); PUT_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY); PUT_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY); + PUT_UI_COLOR(GUI_COLOR_TOGGLE_ON); + PUT_UI_COLOR(GUI_COLOR_TOGGLE_OFF); PUT_UI_COLOR(GUI_COLOR_EDITING); PUT_UI_COLOR(GUI_COLOR_SONG_LOOP); PUT_UI_COLOR(GUI_COLOR_VOLMETER_LOW); @@ -1225,6 +1336,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_PLAYBACK_STAT); SAVE_KEYBIND(GUI_ACTION_OPEN); + SAVE_KEYBIND(GUI_ACTION_OPEN_BACKUP); SAVE_KEYBIND(GUI_ACTION_SAVE); SAVE_KEYBIND(GUI_ACTION_SAVE_AS); SAVE_KEYBIND(GUI_ACTION_UNDO); @@ -1353,6 +1465,33 @@ void FurnaceGUI::commitSettings() { SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_PREVIEW); SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SELECT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_DRAW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_CUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_COPY); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_RESIZE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_AMPLIFY); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SILENCE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_DELETE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_TRIM); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_REVERSE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_INVERT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SIGN); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_FILTER); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PREVIEW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_STOP_PREVIEW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_IN); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_OUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_AUTO); + SAVE_KEYBIND(GUI_ACTION_ORDERS_UP); SAVE_KEYBIND(GUI_ACTION_ORDERS_DOWN); SAVE_KEYBIND(GUI_ACTION_ORDERS_LEFT); @@ -1396,3 +1535,434 @@ void FurnaceGUI::commitSettings() { } } } + +void FurnaceGUI::parseKeybinds() { + actionMapGlobal.clear(); + actionMapPat.clear(); + actionMapInsList.clear(); + actionMapWaveList.clear(); + actionMapSampleList.clear(); + actionMapSample.clear(); + actionMapOrders.clear(); + + for (int i=GUI_ACTION_GLOBAL_MIN+1; igetConfInt(#target,ImGui::GetColorU32(def))); + +#ifdef _WIN32 +#define SYSTEM_FONT_PATH_1 "C:\\Windows\\Fonts\\segoeui.ttf" +#define SYSTEM_FONT_PATH_2 "C:\\Windows\\Fonts\\tahoma.ttf" +// TODO! +#define SYSTEM_FONT_PATH_3 "C:\\Windows\\Fonts\\tahoma.ttf" +// TODO! +#define SYSTEM_PAT_FONT_PATH_1 "C:\\Windows\\Fonts\\consola.ttf" +#define SYSTEM_PAT_FONT_PATH_2 "C:\\Windows\\Fonts\\cour.ttf" +// GOOD LUCK WITH THIS ONE - UNTESTED +#define SYSTEM_PAT_FONT_PATH_3 "C:\\Windows\\Fonts\\vgasys.fon" +#elif defined(__APPLE__) +#define SYSTEM_FONT_PATH_1 "/System/Library/Fonts/SFAANS.ttf" +#define SYSTEM_FONT_PATH_2 "/System/Library/Fonts/Helvetica.ttc" +#define SYSTEM_FONT_PATH_3 "/System/Library/Fonts/Helvetica.dfont" +#define SYSTEM_PAT_FONT_PATH_1 "/System/Library/Fonts/SFNSMono.ttf" +#define SYSTEM_PAT_FONT_PATH_2 "/System/Library/Fonts/Courier New.ttf" +#define SYSTEM_PAT_FONT_PATH_3 "/System/Library/Fonts/Courier New.ttf" +#else +#define SYSTEM_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" +#define SYSTEM_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSans.ttf" +#define SYSTEM_FONT_PATH_3 "/usr/share/fonts/ubuntu/Ubuntu-R.ttf" +#define SYSTEM_PAT_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" +#define SYSTEM_PAT_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSansMono.ttf" +#define SYSTEM_PAT_FONT_PATH_3 "/usr/share/fonts/ubuntu/UbuntuMono-R.ttf" +#endif + +void FurnaceGUI::applyUISettings() { + ImGuiStyle sty; + if (settings.guiColorsBase) { + ImGui::StyleColorsLight(&sty); + } else { + ImGui::StyleColorsDark(&sty); + } + + if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; + + GET_UI_COLOR(GUI_COLOR_BACKGROUND,ImVec4(0.1f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND,ImVec4(0.0f,0.0f,0.0f,0.85f)); + GET_UI_COLOR(GUI_COLOR_MODAL_BACKDROP,ImVec4(0.0f,0.0f,0.0f,0.55f)); + GET_UI_COLOR(GUI_COLOR_HEADER,ImVec4(0.2f,0.2f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); + GET_UI_COLOR(GUI_COLOR_TOGGLE_ON,ImVec4(0.2f,0.6f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_TOGGLE_OFF,ImVec4(0.2f,0.2f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_WAVE,ImVec4(1.0f,0.4f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_FM,ImVec4(0.6f,0.9f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_STD,ImVec4(0.6f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_GB,ImVec4(1.0f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_C64,ImVec4(0.85f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_AMIGA,ImVec4(1.0f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_PCE,ImVec4(1.0f,0.8f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_AY,ImVec4(1.0f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_AY8930,ImVec4(0.7f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_TIA,ImVec4(1.0f,0.6f,0.4f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_SAA1099,ImVec4(0.3f,0.3f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VIC,ImVec4(0.2f,1.0f,0.6f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_PET,ImVec4(1.0f,1.0f,0.8f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VRC6,ImVec4(1.0f,0.9f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_OPLL,ImVec4(0.6f,0.7f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_OPL,ImVec4(0.3f,1.0f,0.9f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_FDS,ImVec4(0.8f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VBOY,ImVec4(1.0f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_N163,ImVec4(1.0f,0.4f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_SCC,ImVec4(0.7f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_OPZ,ImVec4(0.2f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_POKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_NOISE,ImVec4(0.8f,0.8f,0.8f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_PCM,ImVec4(1.0f,0.9f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_WAVE,ImVec4(1.0f,0.5f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION,ImVec4(0.15f,0.15f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_HOVER,ImVec4(0.2f,0.2f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_ACTIVE,ImVec4(0.4f,0.4f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_HI_1,ImVec4(0.6f,0.6f,0.6f,0.2f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_HI_2,ImVec4(0.5f,0.8f,1.0f,0.2f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE,ImVec4(0.5f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_INS,ImVec4(0.4f,0.7f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN,ImVec4(0.0f,0.5f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF,ImVec4(0.0f,0.75f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX,ImVec4(0.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_INVALID,ImVec4(1.0f,0.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PITCH,ImVec4(1.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_VOLUME,ImVec4(0.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PANNING,ImVec4(0.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SONG,ImVec4(1.0f,0.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_TIME,ImVec4(0.5f,0.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SPEED,ImVec4(1.0f,0.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,ImVec4(0.5f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,ImVec4(0.0f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC,ImVec4(0.3f,0.3f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_EE_VALUE,ImVec4(0.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PLAYBACK_STAT,ImVec4(0.6f,0.6f,0.6f,1.0f)); + + for (int i=0; i<64; i++) { + ImVec4 col1=uiColors[GUI_COLOR_PATTERN_VOLUME_MIN]; + ImVec4 col2=uiColors[GUI_COLOR_PATTERN_VOLUME_HALF]; + ImVec4 col3=uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]; + volColors[i]=ImVec4(col1.x+((col2.x-col1.x)*float(i)/64.0f), + col1.y+((col2.y-col1.y)*float(i)/64.0f), + col1.z+((col2.z-col1.z)*float(i)/64.0f), + 1.0f); + volColors[i+64]=ImVec4(col2.x+((col3.x-col2.x)*float(i)/64.0f), + col2.y+((col3.y-col2.y)*float(i)/64.0f), + col2.z+((col3.z-col2.z)*float(i)/64.0f), + 1.0f); + } + + float hue, sat, val; + + ImVec4 primaryActive=uiColors[GUI_COLOR_ACCENT_PRIMARY]; + ImVec4 primaryHover, primary; + primaryHover.w=primaryActive.w; + primary.w=primaryActive.w; + ImGui::ColorConvertRGBtoHSV(primaryActive.x,primaryActive.y,primaryActive.z,hue,sat,val); + if (settings.guiColorsBase) { + primary=primaryActive; + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,primaryHover.x,primaryHover.y,primaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,primaryActive.x,primaryActive.y,primaryActive.z); + } else { + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,primaryHover.x,primaryHover.y,primaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.8,val*0.35,primary.x,primary.y,primary.z); + } + + ImVec4 secondaryActive=uiColors[GUI_COLOR_ACCENT_SECONDARY]; + ImVec4 secondaryHover, secondary, secondarySemiActive; + secondarySemiActive.w=secondaryActive.w; + secondaryHover.w=secondaryActive.w; + secondary.w=secondaryActive.w; + ImGui::ColorConvertRGBtoHSV(secondaryActive.x,secondaryActive.y,secondaryActive.z,hue,sat,val); + if (settings.guiColorsBase) { + secondary=secondaryActive; + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.7,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,secondaryHover.x,secondaryHover.y,secondaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,secondaryActive.x,secondaryActive.y,secondaryActive.z); + } else { + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.75,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,secondaryHover.x,secondaryHover.y,secondaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.25,secondary.x,secondary.y,secondary.z); + } + + + sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND]; + sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP]; + sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT]; + + sty.Colors[ImGuiCol_Button]=primary; + sty.Colors[ImGuiCol_ButtonHovered]=primaryHover; + sty.Colors[ImGuiCol_ButtonActive]=primaryActive; + sty.Colors[ImGuiCol_Tab]=primary; + sty.Colors[ImGuiCol_TabHovered]=secondaryHover; + sty.Colors[ImGuiCol_TabActive]=secondarySemiActive; + sty.Colors[ImGuiCol_TabUnfocused]=primary; + sty.Colors[ImGuiCol_TabUnfocusedActive]=primaryHover; + sty.Colors[ImGuiCol_Header]=secondary; + sty.Colors[ImGuiCol_HeaderHovered]=secondaryHover; + sty.Colors[ImGuiCol_HeaderActive]=secondaryActive; + sty.Colors[ImGuiCol_ResizeGrip]=secondary; + sty.Colors[ImGuiCol_ResizeGripHovered]=secondaryHover; + sty.Colors[ImGuiCol_ResizeGripActive]=secondaryActive; + sty.Colors[ImGuiCol_FrameBg]=secondary; + sty.Colors[ImGuiCol_FrameBgHovered]=secondaryHover; + sty.Colors[ImGuiCol_FrameBgActive]=secondaryActive; + sty.Colors[ImGuiCol_SliderGrab]=primaryActive; + sty.Colors[ImGuiCol_SliderGrabActive]=primaryActive; + sty.Colors[ImGuiCol_TitleBgActive]=primary; + sty.Colors[ImGuiCol_CheckMark]=primaryActive; + sty.Colors[ImGuiCol_TextSelectedBg]=secondaryHover; + sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; + sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; + + if (settings.roundedWindows) sty.WindowRounding=8.0f; + if (settings.roundedButtons) { + sty.FrameRounding=6.0f; + sty.GrabRounding=6.0f; + } + if (settings.roundedMenus) sty.PopupRounding=8.0f; + + sty.ScaleAllSizes(dpiScale); + + ImGui::GetStyle()=sty; + + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; + pitchGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_ACTIVE]; + noteGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PANNING]; + panGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_INS]; + insGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=volColors[i/2]; + volGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]; + sysCmd1Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]; + sysCmd2Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + + // set to 800 for now due to problems with unifont + static const ImWchar upTo800[]={0x20,0x7e,0xa0,0x800,0}; + ImFontGlyphRangesBuilder range; + ImVector outRange; + + range.AddRanges(upTo800); + if (settings.loadJapanese) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); + } + // I'm terribly sorry + range.UsedChars[0x80>>5]=0; + + range.BuildRanges(&outRange); + if (fontRange!=NULL) delete[] fontRange; + fontRange=new ImWchar[outRange.size()]; + int index=0; + for (ImWchar& i: outRange) { + fontRange[index++]=i; + } + + if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; + if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; + + if (settings.mainFont==6 && settings.mainFontPath.empty()) { + logW("UI font path is empty! reverting to default font\n"); + settings.mainFont=0; + } + if (settings.patFont==6 && settings.patFontPath.empty()) { + logW("pattern font path is empty! reverting to default font\n"); + settings.patFont=0; + } + + ImFontConfig fc1; + fc1.MergeMode=true; + + if (settings.mainFont==6) { // custom font + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logW("could not load UI font! reverting to default font\n"); + settings.mainFont=0; + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logE("could not load UI font! falling back to Proggy Clean.\n"); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } else if (settings.mainFont==5) { // system font + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logW("could not load UI font! reverting to default font\n"); + settings.mainFont=0; + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logE("could not load UI font! falling back to Proggy Clean.\n"); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + } + } else { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logE("could not load UI font! falling back to Proggy Clean.\n"); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + + // two fallback fonts + mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_liberationSans_compressed_data,font_liberationSans_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); + mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_unifont_compressed_data,font_unifont_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); + + ImFontConfig fc; + fc.MergeMode=true; + fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; + static const ImWchar fontRangeIcon[]={ICON_MIN_FA,ICON_MAX_FA,0}; + if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { + logE("could not load icon font!\n"); + } + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { + logD("using main font for pat font.\n"); + patFont=mainFont; + } else { + if (settings.patFont==6) { // custom font + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logW("could not load pattern font! reverting to default font\n"); + settings.patFont=0; + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logE("could not load pattern font! falling back to Proggy Clean.\n"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } else if (settings.patFont==5) { // system font + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logW("could not load pattern font! reverting to default font\n"); + settings.patFont=0; + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logE("could not load pattern font! falling back to Proggy Clean.\n"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + } + } else { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logE("could not load pattern font!\n"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { + logE("could not load big UI font!\n"); + } + + mainFont->FallbackChar='?'; + mainFont->DotChar='.'; + + // TODO: allow changing these colors. + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); +} \ No newline at end of file diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp new file mode 100644 index 00000000..e9caf5af --- /dev/null +++ b/src/gui/songInfo.cpp @@ -0,0 +1,173 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "misc/cpp/imgui_stdlib.h" +#include "intConst.h" + +void FurnaceGUI::drawSongInfo() { + if (nextWindow==GUI_WINDOW_SONG_INFO) { + songInfoOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!songInfoOpen) return; + if (ImGui::Begin("Song Information",&songInfoOpen)) { + if (ImGui::BeginTable("NameAuthor",2,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Name",&e->song.name)) { MARK_MODIFIED + updateWindowTitle(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Author"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Author",&e->song.author)) { + MARK_MODIFIED; + } + ImGui::EndTable(); + } + + if (ImGui::BeginTable("OtherProps",3,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("TimeBase"); + ImGui::TableNextColumn(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + unsigned char realTB=e->song.timeBase+1; + if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED + if (realTB<1) realTB=1; + if (realTB>16) realTB=16; + e->song.timeBase=realTB-1; + } + ImGui::TableNextColumn(); + ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Speed"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->song.speed1<1) e->song.speed1=1; + if (e->isPlaying()) play(); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->song.speed2<1) e->song.speed2=1; + if (e->isPlaying()) play(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Highlight"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Pattern Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + int patLen=e->song.patLen; + if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED + if (patLen<1) patLen=1; + if (patLen>256) patLen=256; + e->song.patLen=patLen; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Song Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + int ordLen=e->song.ordersLen; + if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED + if (ordLen<1) ordLen=1; + if (ordLen>127) ordLen=127; + e->song.ordersLen=ordLen; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { + tempoView=!tempoView; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + float setHz=tempoView?e->song.hz*2.5:e->song.hz; + if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED + if (tempoView) setHz/=2.5; + if (setHz<10) setHz=10; + if (setHz>999) setHz=999; + e->setSongRate(setHz,setHz<52); + } + if (tempoView) { + ImGui::TableNextColumn(); + ImGui::Text("= %gHz",e->song.hz); + } else { + if (e->song.hz>=49.98 && e->song.hz<=50.02) { + ImGui::TableNextColumn(); + ImGui::Text("PAL"); + } + if (e->song.hz>=59.9 && e->song.hz<=60.11) { + ImGui::TableNextColumn(); + ImGui::Text("NTSC"); + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Tuning (A-4)"); + ImGui::TableNextColumn(); + float tune=e->song.tuning; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED + if (tune<220.0f) tune=220.0f; + if (tune>880.0f) tune=880.0f; + e->song.tuning=tune; + } + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/songNotes.cpp b/src/gui/songNotes.cpp new file mode 100644 index 00000000..7c7cbdaa --- /dev/null +++ b/src/gui/songNotes.cpp @@ -0,0 +1,37 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "misc/cpp/imgui_stdlib.h" + +// NOTE: please don't ask me to enable text wrap. +// Dear ImGui doesn't have that feature. D: +void FurnaceGUI::drawNotes() { + if (nextWindow==GUI_WINDOW_NOTES) { + notesOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!notesOpen) return; + if (ImGui::Begin("Song Comments",¬esOpen)) { + ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp new file mode 100644 index 00000000..274be2ed --- /dev/null +++ b/src/gui/stats.cpp @@ -0,0 +1,50 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 + +void FurnaceGUI::drawStats() { + if (nextWindow==GUI_WINDOW_STATS) { + statsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!statsOpen) return; + if (ImGui::Begin("Statistics",&statsOpen)) { + String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); + String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); + String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); + String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); + ImGui::Text("ADPCM-A"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); + ImGui::Text("ADPCM-B"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); + ImGui::Text("QSound"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); + ImGui::Text("X1-010"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp new file mode 100644 index 00000000..419decc4 --- /dev/null +++ b/src/gui/sysConf.cpp @@ -0,0 +1,395 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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" + +void FurnaceGUI::drawSysConf(int i) { + unsigned int flags=e->song.systemFlags[i]; + bool restart=settings.restartOnFlagChange; + bool sysPal=flags&1; + switch (e->song.system[i]) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: { + if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&3)==0)) { + e->setSysFlags(i,(flags&0x80000000)|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (7.61MHz)",(flags&3)==1)) { + e->setSysFlags(i,(flags&0x80000000)|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("FM Towns (8MHz)",(flags&3)==2)) { + e->setSysFlags(i,(flags&0x80000000)|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&3)==3)) { + e->setSysFlags(i,(flags&0x80000000)|3,restart); + updateWindowTitle(); + } + bool ladder=flags&0x80000000; + if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { + e->setSysFlags(i,(flags&(~0x80000000))|(ladder?0x80000000:0),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_SMS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + ImGui::Text("Chip type:"); + if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { + e->setSysFlags(i,(flags&(~12))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { + e->setSysFlags(i,(flags&(~12))|4,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { + e->setSysFlags(i,(flags&(~12))|8,restart); + updateWindowTitle(); + } + /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { + e->setSysFlags(i,(flags&3)|12); + }*/ + + bool noPhaseReset=flags&16; + if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { + e->setSysFlags(i,(flags&(~16))|(noPhaseReset<<4),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { + e->setSysFlags(i,(flags&(~15))|3,restart); + updateWindowTitle(); + } + if (e->song.system[i]!=DIV_SYSTEM_VRC7) { + ImGui::Text("Patch set:"); + if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { + e->setSysFlags(i,(flags&(~0xf0))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { + e->setSysFlags(i,(flags&(~0xf0))|0x10,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { + e->setSysFlags(i,(flags&(~0xf0))|0x20,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { + e->setSysFlags(i,(flags&(~0xf0))|0x30,restart); + updateWindowTitle(); + } + } + break; + } + case DIV_SYSTEM_YM2151: + if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_NES: + if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_C64_8580: + case DIV_SYSTEM_C64_6581: + if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { + e->setSysFlags(i,(flags&(~15))|3,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { + e->setSysFlags(i,(flags&(~15))|4,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { + e->setSysFlags(i,(flags&(~15))|5,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) { + e->setSysFlags(i,(flags&(~15))|6,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { + e->setSysFlags(i,(flags&(~15))|7,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) { + e->setSysFlags(i,(flags&(~15))|8,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { + e->setSysFlags(i,(flags&(~15))|9,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { + e->setSysFlags(i,(flags&(~15))|10,restart); + updateWindowTitle(); + } + if (e->song.system[i]==DIV_SYSTEM_AY8910) { + ImGui::Text("Chip type:"); + if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { + e->setSysFlags(i,(flags&(~0x30))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { + e->setSysFlags(i,(flags&(~0x30))|16,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { + e->setSysFlags(i,(flags&(~0x30))|32,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { + e->setSysFlags(i,(flags&(~0x30))|48,restart); + updateWindowTitle(); + } + } + bool stereo=flags&0x40; + ImGui::BeginDisabled((flags&0x30)==32); + if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { + e->setSysFlags(i,(flags&(~0x40))|(stereo?0x40:0),restart); + updateWindowTitle(); + } + ImGui::EndDisabled(); + break; + } + case DIV_SYSTEM_SAA1099: + if (ImGui::RadioButton("SAM Coupé (8MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("NTSC (7.15MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (7.09MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_AMIGA: { + ImGui::Text("Stereo separation:"); + int stereoSep=(flags>>8)&127; + if (ImGui::SliderInt("##StereoSep",&stereoSep,0,127)) { + if (stereoSep<0) stereoSep=0; + if (stereoSep>127) stereoSep=127; + e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); + updateWindowTitle(); + } rightClickable + if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { + e->setSysFlags(i,flags&(~2),restart); + } + if (ImGui::RadioButton("Amiga 1200 (AGA)",(flags&2)==2)) { + e->setSysFlags(i,(flags&(~2))|2,restart); + } + sysPal=flags&1; + if (ImGui::Checkbox("PAL",&sysPal)) { + e->setSysFlags(i,(flags&(~1))|sysPal,restart); + updateWindowTitle(); + } + bool bypassLimits=flags&4; + if (ImGui::Checkbox("Bypass frequency limits",&bypassLimits)) { + e->setSysFlags(i,(flags&(~4))|(bypassLimits<<2),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_PCSPKR: { + ImGui::Text("Speaker type:"); + if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Cone",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Piezo",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_QSOUND: { + ImGui::Text("Echo delay:"); + int echoBufSize=2725 - (flags & 4095); + if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) { + if (echoBufSize<0) echoBufSize=0; + if (echoBufSize>2725) echoBufSize=2725; + e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); + updateWindowTitle(); + } rightClickable + ImGui::Text("Echo feedback:"); + int echoFeedback=(flags>>12)&255; + if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) { + if (echoFeedback<0) echoFeedback=0; + if (echoFeedback>255) echoFeedback=255; + e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); + updateWindowTitle(); + } rightClickable + break; + } + case DIV_SYSTEM_X1_010: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + bool x1_010Stereo=flags&16; + if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { + e->setSysFlags(i,(flags&(~16))|(x1_010Stereo<<4),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_N163: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (1.79MHz)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (1.67MHz)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Dendy (1.77MHz)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + ImGui::Text("Initial channel limit:"); + int initialChannelLimit=((flags>>4)&7)+1; + if (ImGui::SliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { + if (initialChannelLimit<1) initialChannelLimit=1; + if (initialChannelLimit>8) initialChannelLimit=8; + e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); + updateWindowTitle(); + } rightClickable + break; + } + case DIV_SYSTEM_GB: + case DIV_SYSTEM_SWAN: + case DIV_SYSTEM_VERA: + case DIV_SYSTEM_BUBSYS_WSG: + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: + case DIV_SYSTEM_YMU759: + case DIV_SYSTEM_PET: + ImGui::Text("nothing to configure"); + break; + default: + if (ImGui::Checkbox("PAL",&sysPal)) { + e->setSysFlags(i,sysPal,restart); + updateWindowTitle(); + } + break; + } +} \ No newline at end of file diff --git a/src/gui/volMeter.cpp b/src/gui/volMeter.cpp new file mode 100644 index 00000000..29f62c96 --- /dev/null +++ b/src/gui/volMeter.cpp @@ -0,0 +1,111 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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_internal.h" + +void FurnaceGUI::drawVolMeter() { + if (nextWindow==GUI_WINDOW_VOL_METER) { + volMeterOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!volMeterOpen) return; + if (--isClipping<0) isClipping=0; + ImGui::SetNextWindowSizeConstraints(ImVec2(6.0f*dpiScale,6.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); + if (ImGui::Begin("Volume Meter",&volMeterOpen)) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + bool aspectRatio=(ImGui::GetWindowSize().x/ImGui::GetWindowSize().y)>1.0; + + ImVec2 minArea=ImVec2( + ImGui::GetWindowPos().x+ImGui::GetCursorPos().x, + ImGui::GetWindowPos().y+ImGui::GetCursorPos().y + ); + ImVec2 maxArea=ImVec2( + ImGui::GetWindowPos().x+ImGui::GetCursorPos().x+ImGui::GetContentRegionAvail().x, + ImGui::GetWindowPos().y+ImGui::GetCursorPos().y+ImGui::GetContentRegionAvail().y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImGui::ItemSize(ImVec2(4.0f,4.0f),style.FramePadding.y); + ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_LOW]); + float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; + if (ImGui::ItemAdd(rect,ImGui::GetID("volMeter"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + for (int i=0; i<2; i++) { + peak[i]*=1.0-peakDecay; + if (peak[i]<0.0001) peak[i]=0.0; + for (int j=0; joscSize; j++) { + if (fabs(e->oscBuf[i][j])>peak[i]) { + peak[i]=fabs(e->oscBuf[i][j]); + } + } + float logPeak=(20*log10(peak[i])/36.0); + if (logPeak==NAN) logPeak=0.0; + if (logPeak<-1.0) logPeak=-1.0; + if (logPeak>0.0) { + isClipping=8; + logPeak=0.0; + } + logPeak+=1.0; + ImU32 highColor=ImGui::GetColorU32( + ImLerp(uiColors[GUI_COLOR_VOLMETER_LOW],uiColors[GUI_COLOR_VOLMETER_HIGH],logPeak) + ); + ImRect s; + if (aspectRatio) { + s=ImRect( + ImLerp(rect.Min,rect.Max,ImVec2(0,float(i)*0.5)), + ImLerp(rect.Min,rect.Max,ImVec2(logPeak,float(i+1)*0.5)) + ); + if (i==0) s.Max.y-=dpiScale; + if (isClipping) { + dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); + } else { + dl->AddRectFilledMultiColor(s.Min,s.Max,lowColor,highColor,highColor,lowColor); + } + } else { + s=ImRect( + ImLerp(rect.Min,rect.Max,ImVec2(float(i)*0.5,1.0-logPeak)), + ImLerp(rect.Min,rect.Max,ImVec2(float(i+1)*0.5,1.0)) + ); + if (i==0) s.Max.x-=dpiScale; + if (isClipping) { + dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); + } else { + dl->AddRectFilledMultiColor(s.Min,s.Max,highColor,highColor,lowColor,lowColor); + } + } + } + if (ImGui::IsItemHovered()) { + if (aspectRatio) { + ImGui::SetTooltip("%.1fdB",36*((ImGui::GetMousePos().x-ImGui::GetItemRectMin().x)/(rect.Max.x-rect.Min.x)-1.0)); + } else { + ImGui::SetTooltip("%.1fdB",-(36+36*((ImGui::GetMousePos().y-ImGui::GetItemRectMin().y)/(rect.Max.y-rect.Min.y)-1.0))); + } + } + } + } + ImGui::PopStyleVar(4); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_VOL_METER; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp new file mode 100644 index 00000000..60938d0a --- /dev/null +++ b/src/gui/waveEdit.cpp @@ -0,0 +1,101 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 "plot_nolerp.h" +#include "misc/cpp/imgui_stdlib.h" + +void FurnaceGUI::drawWaveEdit() { + if (nextWindow==GUI_WINDOW_WAVE_EDIT) { + waveEditOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!waveEditOpen) return; + float wavePreview[256]; + ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + if (curWave<0 || curWave>=(int)e->song.wave.size()) { + ImGui::Text("no wavetable selected"); + } else { + DivWavetable* wave=e->song.wave[curWave]; + ImGui::Text("Width"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 128 on X1-010\nany other widths will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { + if (wave->len>256) wave->len=256; + if (wave->len<1) wave->len=1; + e->notifyWaveChange(curWave); + if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); + MARK_MODIFIED; + } + ImGui::SameLine(); + ImGui::Text("Height"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 255 for X1-010\nany other heights will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { + if (wave->max>255) wave->max=255; + if (wave->max<1) wave->max=1; + e->notifyWaveChange(curWave); + MARK_MODIFIED; + } + for (int i=0; ilen; i++) { + if (wave->data[i]>wave->max) wave->data[i]=wave->max; + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here + if (ImGui::InputText("##MMLWave",&mmlStringW)) { + decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max); + } + if (!ImGui::IsItemActive()) { + encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1); + } + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + + ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here + if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { + contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); + } + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + waveDragStart=ImGui::GetItemRectMin(); + waveDragAreaSize=contentRegion; + waveDragMin=0; + waveDragMax=wave->max; + waveDragLen=wave->len; + waveDragActive=true; + waveDragTarget=wave->data; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + e->notifyWaveChange(curWave); + modified=true; + } + ImGui::PopStyleVar(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_EDIT; + ImGui::End(); +} \ No newline at end of file