diff --git a/.gitignore b/.gitignore index 55120b422..1c7ed1ead 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ release/ t/ winbuild/ win32build/ +xpbuild/ macbuild/ linuxbuild/ *.swp diff --git a/CMakeLists.txt b/CMakeLists.txt index 89f1cebc8..a78b0253e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,6 +518,7 @@ src/engine/brrUtils.c src/engine/safeReader.cpp src/engine/safeWriter.cpp src/engine/cmdStream.cpp +src/engine/cmdStreamOps.cpp src/engine/config.cpp src/engine/configEngine.cpp src/engine/dispatchContainer.cpp @@ -525,6 +526,7 @@ src/engine/engine.cpp src/engine/export.cpp src/engine/fileOps.cpp src/engine/fileOpsIns.cpp +src/engine/fileOpsSample.cpp src/engine/filter.cpp src/engine/instrument.cpp src/engine/macroInt.cpp @@ -535,6 +537,7 @@ src/engine/song.cpp src/engine/sysDef.cpp src/engine/wavetable.cpp src/engine/waveSynth.cpp +src/engine/wavOps.cpp src/engine/vgmOps.cpp src/engine/zsmOps.cpp src/engine/zsm.cpp diff --git a/README.md b/README.md index f4d4af3c3..08ccfc85c 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,10 @@ for other operating systems, you may [build the source](#developer-info). - Ricoh RF5C68 used in Sega CD and FM Towns - OKI MSM6258 and MSM6295 - Konami K007232 + - Konami K053260 - Irem GA20 - Ensoniq ES5506 + - Namco C140 - wavetable chips: - HuC6280 used in PC Engine - Konami Bubble System WSG @@ -73,6 +75,7 @@ for other operating systems, you may [build the source](#developer-info). - QuadTone engine - Pokémon Mini - Commodore PET + - TED used in Commodore Plus/4 - Casio PV-1000 - TIA used in Atari 2600 - POKEY used in Atari 8-bit computers @@ -124,7 +127,7 @@ for other operating systems, you may [build the source](#developer-info). # quick references - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, the [official Revolt](https://rvlt.gg/GRPS6tmc) or the [official Discord server](https://discord.gg/EfrwT2wq7z). -- **help**: check out the [documentation](doc/README.md). it's about 80% complete. +- **help**: check out the [documentation](doc/README.md). it's about 90% complete. ## packages diff --git a/android/app/build.gradle b/android/app/build.gradle index 5384d6d9e..dcf7fe76e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,8 @@ android { } minSdkVersion 21 targetSdkVersion 26 - versionCode 166 - versionName "0.6pre8" + versionCode 169 + versionName "0.6pre9" externalNativeBuild { cmake { arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a72f1b837..dd3c7a0bd 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/demos/pc98/atomic_failure.fur b/demos/pc98/atomic_failure.fur new file mode 100644 index 000000000..61b430537 Binary files /dev/null and b/demos/pc98/atomic_failure.fur differ diff --git a/doc/2-interface/asset-add.png b/doc/2-interface/asset-add.png deleted file mode 100644 index f6ef5b73b..000000000 Binary files a/doc/2-interface/asset-add.png and /dev/null differ diff --git a/doc/2-interface/asset-delete.png b/doc/2-interface/asset-delete.png deleted file mode 100644 index d0eb8dd97..000000000 Binary files a/doc/2-interface/asset-delete.png and /dev/null differ diff --git a/doc/2-interface/asset-duplicate.png b/doc/2-interface/asset-duplicate.png deleted file mode 100644 index caa740fb1..000000000 Binary files a/doc/2-interface/asset-duplicate.png and /dev/null differ diff --git a/doc/2-interface/asset-folderview.png b/doc/2-interface/asset-folderview.png deleted file mode 100644 index 8d693e0fe..000000000 Binary files a/doc/2-interface/asset-folderview.png and /dev/null differ diff --git a/doc/2-interface/asset-move.png b/doc/2-interface/asset-move.png deleted file mode 100644 index fdcbca6a9..000000000 Binary files a/doc/2-interface/asset-move.png and /dev/null differ diff --git a/doc/2-interface/asset-newfolder.png b/doc/2-interface/asset-newfolder.png deleted file mode 100644 index 01540abd9..000000000 Binary files a/doc/2-interface/asset-newfolder.png and /dev/null differ diff --git a/doc/2-interface/asset-open.png b/doc/2-interface/asset-open.png deleted file mode 100644 index eea985143..000000000 Binary files a/doc/2-interface/asset-open.png and /dev/null differ diff --git a/doc/2-interface/asset-preview.png b/doc/2-interface/asset-preview.png deleted file mode 100644 index 8636285c4..000000000 Binary files a/doc/2-interface/asset-preview.png and /dev/null differ diff --git a/doc/2-interface/asset-previewstop.png b/doc/2-interface/asset-previewstop.png deleted file mode 100644 index 02fe6498e..000000000 Binary files a/doc/2-interface/asset-previewstop.png and /dev/null differ diff --git a/doc/2-interface/asset-save.png b/doc/2-interface/asset-save.png deleted file mode 100644 index 19dd03e9a..000000000 Binary files a/doc/2-interface/asset-save.png and /dev/null differ diff --git a/doc/2-interface/docking.png b/doc/2-interface/docking.png index d39b60ffe..ba4d6973f 100644 Binary files a/doc/2-interface/docking.png and b/doc/2-interface/docking.png differ diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md index 9a5c2d17b..fee8f7c57 100644 --- a/doc/2-interface/menu-bar.md +++ b/doc/2-interface/menu-bar.md @@ -122,6 +122,7 @@ the following settings are available: - I suggest you use the same rate as the song's. - apparently ZSM doesn't support changing the rate mid-song. - **loop**: enables loop. if disabled, the song won't loop. +- **optimize size**: removes unnecessary commands to reduce size. click on **Begin Export** to... you know. diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md index c7f01e8ff..7a5546642 100644 --- a/doc/2-interface/settings.md +++ b/doc/2-interface/settings.md @@ -1,32 +1,27 @@ # settings -settings are saved when clicking the **OK** button at the bottom of the dialog. - - +settings are saved when clicking the **OK** or **Apply** buttons at the bottom of the dialog. ## General ### Program -- **Render backend** - - changing this may help with performace issues. -- **Late render clear** +- **Render backend**: changing this may help with performace issues. +- **Late render clear**: this option is only useful when using old versions of Mesa drivers. it force-waits for VBlank by clearing after present, reducing latency. - **Power-saving mode**: saves power by lowering the frame rate to 2fps when idle. - may cause issues under Mesa drivers! - **Disable threaded input (restart after changing!)**: processes key presses for note preview on a separate thread (on supported platforms), which reduces latency. - however, crashes have been reported when threaded input is on. enable this option if that is the case. -- **Enable event delay** - - may cause issues with high-polling-rate mice when previewing notes. +- **Enable event delay**: may cause issues with high-polling-rate mice when previewing notes. ### File - **Use system file picker**: uses native OS file dialog instead of Furnace's. -- **Number of recent files**: number of files to show in the _open recent..._ menu. -- **Compress when saving** - - uses zlib to compress saved songs. -- **Save unused patterns** -- **Use new pattern format when saving** -- **Don't apply compatibility flags when loading .dmf** +- **Number of recent files**: number of files that will be remembered in the _open recent..._ menu. +- **Compress when saving**: uses zlib to compress saved songs. +- **Save unused patterns**: stores unused patterns in a saved song. +- **Use new pattern format when saving**: stores patterns in the new, optimized and smaller format. only disable if you need to work with older versions of Furnace. +- **Don't apply compatibility flags when loading .dmf**: does exactly what the option says. your .dmf songs may not play correctly after enabled. - **Play after opening song:** - No - Only if already playing @@ -49,7 +44,6 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **When creating new song**: - **Display system preset selector** - **Start with initial system** -- **Restart song when changing chip properties** ### Start-up @@ -74,22 +68,24 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **Backend**: selects SDL or JACK for audio output. - only appears on Linux, or MacOS compiled with JACK support -- **Driver** +- **Driver**: select a different SDL audio driver if you're having problems with the default one. - **Device**: audio device for playback. - **Sample rate** - **Outputs**: number of audio outputs created, up to 16. - only appears when Backend is JACK. - **Channels**: number of output channels to use. - **Buffer size**: size of buffer in both samples and milliseconds. + - setting this to a low value may cause stuttering/glitches in playback (known as "underruns" or "xruns"). + - setting this to a high value increases latency. - **Low-latency mode (experimental!)**: reduces latency by running the engine faster than the tick rate. useful for live playback/jam mode. - - _warning:_ experimental! may produce glitches. only enable if your buffer size is small (10ms or less). -- **Force mono audio** + - only enable if your buffer size is small (10ms or less). +- **Force mono audio**: use if you're unable to hear stereo audio (e.g. single speaker or hearing loss in one ear). - **want:** displays requested audio configuration. - **got:** displays actual audio configuration returned by audio backend. ### Mixing -- **Quality**: selects quality of resampling. low quality reduces CPU load. +- **Quality**: selects quality of resampling. low quality reduces CPU load by a small amount. - **Software clipping**: clips output to nominal range (-1.0 to 1.0) before passing it to the audio device. - this avoids activating Windows' built-in limiter. @@ -97,32 +93,39 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **Metronome volume** - - ## MIDI ### MIDI input - **MIDI input**: input device. -- **Note input** -- **Velocity input** -- **Map MIDI channels to direct channels** -- **Map Yamaha FM voice data to instruments** -- **Program change is instrument selection** -- **Value input style**: - - **Disabled/custom** - - **Two octaves (0 is C-4, F is D#5)** - - **Raw (note number is value)** - - **Two octaves alternate (lower keys are 0-9, upper keys are A-F)** - - **Use dual control change (one for each nibble)** - - **CC of upper nibble** - - **CC of lower nibble** - - **Use 14-bit control change** - - **MSB CC** - - **LSB CC** - - **Use single control change** - - **Control** -- **Per-column control change** +- **Note input**: enables note input. disable if you intend to use this device only for binding actions. +- **Velocity input**: enables velocity input when entering notes in the pattern. +- **Map MIDI channels to direct channels**: when enabled, notes from MIDI channels will be mapped to channels rather than the cursor position. +- **Map Yamaha FM voice data to instruments**: when enabled, Furnace will listen for any transmitted Yamaha SysEx patches. + - this option is only useful if you have a Yamaha FM synthesizer (e.g. TX81Z). + - selecting a voice or using the "Voice Transmit?" option will send a patch, and Furnace will create a new instrument with its data. + - this may also be triggered by clicking on "Receive from TX81Z" in the instrument editor (OPZ only). +- **Program change is instrument selection**: changes the current instrument when a program change event is received. +- **Value input style**: changes the way values are entered when the pattern cursor is not in the Note column. the following styles are available: + - **Disabled/custom**: no value input through MIDI. + - **Two octaves (0 is C-4, F is D#5)**: maps keys in two octaves to single nibble input. the layout is: + - ` - octave n -- octave n+1 -` + - ` 1 3 6 8 A D F # # # ` + - `0 2 4 5 7 9 B C E # # # # #` + - **Raw (note number is value)**: the note number becomes the input value. not useful if you want to input anything above 7F. + - **Two octaves alternate (lower keys are 0-9, upper keys are A-F)**: maps keys in two octaves, but with a different layout: + - ` - octave n -- octave n+1 -` + - ` A B C D E F # # # # ` + - `0 1 2 3 4 5 6 7 8 9 # # # #` + - **Use dual control change (one for each nibble)**: maps two control change events to the nibbles of a value. + - **CC of upper nibble**: select the CC number that will change the upper nibble. + - **CC of lower nibble**: select the CC number that will change the lower nibble. + - **Use 14-bit control change**: maps two control change events that together form a single 14-bit CC. some MIDI controllers do these. + - **MSB CC**: select the CC containing the upper portion of the control. + - **LSB CC**: select the CC containing the lower portion of the control. + - **Use single control change**: maps one control change event. not useful if you want to input odd numbers. + - **Control**: select the CC number that will change the value. +- **Per-column control change**: when enabled, you can map several control change events to a channel's columns. - **Instrument**\ **Volume**\ **Effect `x` type**\ @@ -136,36 +139,34 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **LSB CC** - **Use single control change (imprecise)** - **Control** -- **Volume curve** -- **Actions:** +- **Volume curve**: adjust the velocity to volume curve. +- **Actions**: this allows you to bind note input and control change events to actions. - **`+`** button: adds a new action. - window-with-arrow button: new action with learning! press a button or move a slider/knob/something on your device. - each action has the following: - - **Type** - - **Channel** - - **Note/Control** - - **Velocity/Value** - - **Action** - - **Learn** - - **Remove** + - **Type**: type of event. + - **Channel**: channel of event. + - **Note/Control**: the note/control change number. + - **Velocity/Value**: the velocity or control value + - **Action**: the GUI action to perform. + - **Learn**: after clicking on this button, do something in your MIDI device and Furnace will map that to this action. + - **Remove**: remove this action. ### MIDI output - **MIDI output**: output device. - **Output mode:** - - **Off (use for TX81Z)** - - **Melodic** -- **Send Program Change** -- **Send MIDI clock** -- **Send MIDI timecode** - - **Timecode frame rate:** - - **Closest to Tick Rate** - - **Film (24fps)** - - **PAL (25fps)** - - **NTSC drop (29.97fps)** - - **NTSC non-drop (30fps)** - - + - **Off (use for TX81Z)**: don't output anything. use if you plan to use Furnace as sync master, or the "Receive from TX81Z" button in the OPZ instrument editor. + - **Melodic**: output MIDI events. +- **Send Program Change**: output program change events when instrument change commands occur. +- **Send MIDI clock**: output MIDI beat clock. +- **Send MIDI timecode**: output MIDI timecode. + - **Timecode frame rate**: sets the timing standard used for MIDI timecode. + - **Closest to Tick Rate**: automatically sets the rate based on the song's Tick Rate. + - **Film (24fps)**: output at 24 codes per second. + - **PAL (25fps)**: output at 25 codes per second. + - **NTSC drop (29.97fps)**: output at ~29.97 codes per second, skipping frames 0 and 1 of each minute that doesn't divide by 10. + - **NTSC non-drop (30fps)**: output at 30 codes per second. ## Emulation @@ -185,13 +186,6 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **PC Speaker strategy**: this is covered in the [PC speaker system doc](../7-systems/pcspkr.md). -- **Sample ROMs:** - - **OPL4 YRW801 path** - - **MultiPCM TG100 path** - - **MultiPCM MU5 path** - - - ## Keyboard ### Keyboard @@ -418,10 +412,8 @@ settings are saved when clicking the **OK** button at the bottom of the dialog. - **Macro editor layout:** - **Unified** - - **Mobile** - **Grid** - **Single (with list)** - - **Single (combo box)** - **Use classic macro editor vertical slider** ### Wave Editor diff --git a/doc/4-instrument/README.md b/doc/4-instrument/README.md index 497204646..c7fbb67d2 100644 --- a/doc/4-instrument/README.md +++ b/doc/4-instrument/README.md @@ -1,6 +1,16 @@ # instrument editor -every instrument can be renamed and have its type changed. +the instrument editor always starts with this section: + +![top of instrument editor](instrument-editor-top.png) + +- top-left numeric dropdown: instrument selector. +- folder icon: open an instrument file. +- save icon: save current instrument as a file. + - right-clicking gives the option to save a .dmp format DefleMask preset. +- **Name**: instrument name. +- **Type**: the system for which the instrument is intended. + - if changed, all applicable settings and macros will remain as they are. numbers will not be adjusted. depending on the instrument type, there are many different types of instrument editor: diff --git a/doc/4-instrument/instrument-editor-top.png b/doc/4-instrument/instrument-editor-top.png new file mode 100644 index 000000000..138a117fe Binary files /dev/null and b/doc/4-instrument/instrument-editor-top.png differ diff --git a/doc/4-instrument/macro-ADSR.png b/doc/4-instrument/macro-ADSR.png index 31dce6de3..d01e177ef 100644 Binary files a/doc/4-instrument/macro-ADSR.png and b/doc/4-instrument/macro-ADSR.png differ diff --git a/doc/4-instrument/macro-LFO.png b/doc/4-instrument/macro-LFO.png index bba5bf207..06904e8b7 100644 Binary files a/doc/4-instrument/macro-LFO.png and b/doc/4-instrument/macro-LFO.png differ diff --git a/doc/7-systems/opll.md b/doc/7-systems/opll.md index 92a81490b..f32f1eefc 100644 --- a/doc/7-systems/opll.md +++ b/doc/7-systems/opll.md @@ -13,10 +13,8 @@ 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 5 rhythm channels, with drum mode tones hard-defined in the chip itself, like FM instruments. only pitch might be altered. - - drum mode works like following: FM channel 7 is for Kick Drum, which is a normal FM channel but routed through mixer twice for 2× volume, like all drum sounds. FM channel 8 splits to Snare, Drum, and Hi-Hat. Snare Drum is the carrier and it works with a special 1 bit noise generator combined with a square wave, all possible by overriding phase-generator with some different synthesis method. Hi-Hat is the modulator and it works with the noise generator and also the special synthesis. CH9 splits to Top-Cymbal and Tom-Tom, Top-Cymbal is the carrier and only has the special synthesis, while Tom-Tom is basically a 1op wave. - special synthesis mentioned already is: 5 square waves are gathered from 4×, 64× and 128× the pitch of channel 8 and 16× and 64× the pitch of channel 9 and they go through a process where 2 HH bits OR'd together, then 1 HH and 1 TC bit OR'd, then the two TC bits OR'd together, and those 3 results get XOR'd. - - 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 @@ -37,7 +35,7 @@ the YM2413 is equipped with the following features: - `y` is the multiplier. - `18xx`: **toggle drums mode.** - `0` disables it and `1` enables it. - - only in drums chip. + - only in drums mode. - `19xx`: **set attack of all operators.** - `1Axx`: **set attack of operator 1.** - `1Bxx`: **set attack of operator 2.** @@ -69,3 +67,8 @@ the YM2413 is equipped with the following features: # info this chip uses the [FM (OPLL)](../4-instrument/fm-opll.md) instrument editor. + +## chip options + +- **Ignore top/hi-hat frequency changes**: in drums mode, makes the top/hi-hat channels not write frequency since they share it with snare and tom +- **Apply fixed frequency to all drums at once**: sets the frequency of all drums to that of a fixed frequency OPLL drums instrument when one note with it is reached diff --git a/doc/7-systems/sms.md b/doc/7-systems/sms.md index 36d13976d..e4fe22317 100644 --- a/doc/7-systems/sms.md +++ b/doc/7-systems/sms.md @@ -1,6 +1,8 @@ # TI SN76489 (e.g. Sega Master System) -a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis. +a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis. It has three square wave channels and one noise channel... not really. + +Nominal mode of SN76489 has 3 quare wave channels, with noise channel having only 3 preset frequencies to use (absurdly low, very low, low). To use more pitches, one can enable mode, which "steals" pitch data from square wave channel 3. By doing that, SN76489 becomes effectively a 3 channel sound chip. In addition, periodic noise mode can be enabled, with same caveats. the original iteration of the SN76489 used in the TI-99/4A computer, the SN94624, could only produce tones as low as 100Hz, and was clocked at 447 KHz. all later versions (such as the one in the Master System and Genesis) had a clock divider but ran on a faster clock... except for the SN76494, which can play notes as low as 13.670 Hz (A -1). consequently, its pitch accuracy for higher notes is compromised. diff --git a/doc/9-guides/README.md b/doc/9-guides/README.md index aa6d5cbf6..4efaf6634 100644 --- a/doc/9-guides/README.md +++ b/doc/9-guides/README.md @@ -4,4 +4,9 @@ here is a small collection of useful tricks and techniques to really make Furnac - [using samples with limited playback rates](limited-samples.md) - [choosing emulation cores](emulation-cores.md) -- [guide on using OPLL patch macro](opllswitching.md) \ No newline at end of file +- [using OPLL patch macro](opllswitching.md) +- [using AY/SAA hardware envelope](envelope.md) + +# links + +- [FM Synthesis of Real Instruments](http://www.javelinart.com/FM_Synthesis_of_Real_Instruments.pdf): an in-depth tutorial on creating FM patches from scratch. \ No newline at end of file diff --git a/doc/9-guides/envelope.md b/doc/9-guides/envelope.md new file mode 100644 index 000000000..42aa3dfa0 --- /dev/null +++ b/doc/9-guides/envelope.md @@ -0,0 +1,34 @@ +# AY-3-8910 / AY8930 / SAA1099 envelope guide + +The AY-3-8910 programmable sound generator, aside from normal 4-bit volume control, has an hardware volume envelope. This feature that allows for defining the shape of the volume envelope at arbitrary speed according to 8 preset envelope shapes. One may think, what is any upside of hardware envelope? Well, it's somewhat independent of tone/noise generators, and since it goes so high in frequency, it can be used melodically! This guide explains how to make best use of the AY/SAA envelope. + +## AY-3-8910 / AY8930 + +In the instrument editor: +- Add a single tick to the "Waveform" macro with only `envelope` turned on. This will disable any output, but don't worry. +- Add a single tick to the "Envelope" macro and select `enable`. + +If you play a note now, you will hear a very high-pitched squeak. This is because you must set envelope period, which is the frequency at which the hardware envelope runs. You can do it in two ways: +- `23xx` and `24xx` effects (envelope coarse and fine period); +- `29xx` auto-envelope period effect and macros. + +Auto-envelope works via numerator and denominator. In general, the higher the numerator, the higher the envelope pitch. The higher the denominator, the lower the envelope pitch. Why are there both of these? Because the envelope generator might be used to mask the tone output (i.e. affect the square wave as well). To do it, set the "Waveform" macro values to both `tone` and `envelope`. The higher the denominator value, then the lower the envelope pitch relative to the square wave output, and similarly with the numerator. With the square-and-envelope setting, a lot of wild, detuned synth instruments can be made. + +Back to the hardware envelope itself. Depending on the "Envelope" macro value, different envelope shapes can be obtained. The most basic one, 8, is a sawtooth wave. The `direction` value will invert the envelope, producing the reverse sawtooth. The `alternate` value produces an interesting pseudo-triangular wave, similiar to halved sine. That one can also be reversed. `Hold` option disables the envelope. + +_Warning:_ The envelope pitch resolution is fairly low; at high pitches it will be detuned. Because of this, it's used mostly for bass. + +_Warning_: There is only one hardware envelope generator. You can't use two pitches or two waveforms at once. + +## SAA1099 + +SAA envelope works a bit differently. It doesn't have its own pitch; instead, it relies on the channel 2/5 pitch. It also has many more parameters than the AY envelope. To use it: +- Go to waveform macro and add a single tick set to 0 (unless you want to have a square wave mask). +- Set up an envelope macro. Turn on `enabled`, `loop`, and depending on the desired shape, `cut` and `direction`. `Resolution` will give you higher pitch range than on the AY. +- Place two notes in the pattern editor. One in channel 2 will control the envelope pitch. The other in channel 3 can be any note you wish; it's just to enable the envelope output. + +## examples + +- [Demoscene-type Beat by Duccinator](https://www.youtube.com/watch?v=qcBgmpPrlUA) +- [Philips SAA1099 Test by Duccinator](https://www.youtube.com/watch?v=IBh2gr09zjs) +- [Touhou Kaikidan: Mystic Square title theme by ZUN](https://www.youtube.com/watch?v=tUKei7Pz0Fw): Rare instance of AY envelope used for drums, it can be used to mask the noise generator output too \ No newline at end of file diff --git a/extern/nfd-modified/src/nfd_win.cpp b/extern/nfd-modified/src/nfd_win.cpp index c60f7a753..90e28ec3c 100644 --- a/extern/nfd-modified/src/nfd_win.cpp +++ b/extern/nfd-modified/src/nfd_win.cpp @@ -61,7 +61,7 @@ class NFDWinEvents: public IFileDialogEvents { return ret; } - IFACEMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; } + IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; } IFACEMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; } IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; } IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return E_NOTIMPL; } @@ -677,6 +677,21 @@ nfdresult_t NFD_SaveDialog( const std::vector& filterList, goto end; } + // Set a flag for no history + DWORD dwFlags; + result = fileSaveDialog->GetOptions(&dwFlags); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not get options."); + goto end; + } + result = fileSaveDialog->SetOptions(dwFlags | FOS_DONTADDTORECENT); + if ( !SUCCEEDED(result) ) + { + NFDi_SetError("Could not set options."); + goto end; + } + // Show the dialog. result = fileSaveDialog->Show(NULL); if ( SUCCEEDED(result) ) diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp index 02aa34601..6f9e9b811 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp @@ -67,7 +67,7 @@ void k007232_core::voice_t::tick(u8 ne) } } - m_out = s8(m_data) - 0x40; // send to output (ASD/BSD) pin + m_out = s8(m_data&0x7f) - 0x40; // send to output (ASD/BSD) pin } else { diff --git a/papers/clipboard-format.md b/papers/clipboard-format.md index 1b274543b..cb353546c 100644 --- a/papers/clipboard-format.md +++ b/papers/clipboard-format.md @@ -6,7 +6,7 @@ when copying pattern data from Furnace, it's stored in the clipboard as plain te org.tildearrow.furnace - Pattern Data (144) ``` -this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6pre8 is `166`. +this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6pre9 is `169`. the second line is a number between 0 and 18 (decimal) which indicates which column the clip starts from. - `0`: note. diff --git a/papers/format.md b/papers/format.md index 38696622a..a09579bd2 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 169: Furnace 0.6pre9 - 166: Furnace 0.6pre8 - 162: Furnace 0.6pre7 - 161: Furnace 0.6pre6 @@ -348,7 +349,8 @@ size | description --- | **a couple more compat flags** (>=138) 1 | broken portamento during legato 1 | broken macro during note off in some FM chips (>=155) - 6 | reserved + 1 | pre note (C64) does not compensate for portamento or legato (>=168) + 5 | reserved --- | **speed pattern of first song** (>=139) 1 | length of speed pattern (fail if this is lower than 0 or higher than 16) 16 | speed pattern (this overrides speed 1 and speed 2 settings) diff --git a/res/Info.plist b/res/Info.plist index bb3bab0e8..a7e16ca73 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre8 + 0.6pre9 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre8 + 0.6pre9 CFBundleSignature ???? CFBundleVersion - 0.6pre8 + 0.6pre9 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/res/releaseReadme/stable-linux.txt b/res/releaseReadme/stable-linux.txt new file mode 100644 index 000000000..3eb6e77d9 --- /dev/null +++ b/res/releaseReadme/stable-linux.txt @@ -0,0 +1,5 @@ +# Furnace (chiptune tracker) + +thank you for downloading Furnace! I hope you enjoy using it. + +extract this archive, and run `furnace` to get started. diff --git a/res/releaseReadme/stable-mac.txt b/res/releaseReadme/stable-mac.txt new file mode 100644 index 000000000..fb84fb870 --- /dev/null +++ b/res/releaseReadme/stable-mac.txt @@ -0,0 +1,15 @@ +# Furnace (chiptune tracker) + +thank you for downloading Furnace! I hope you enjoy using it. + +move Furnace to Applications (or some other place). +if you are using a recent version of macOS, you may get an error saying that Furnace is damaged. +in that case, open Terminal, and type this: + +``` +xattr -d com.apple.quarantine /path/to/Furnace.app +``` + +(replace `/path/to/Furnace.app` with the path where Furnace.app is located, e.g. `/Applications/Furnace.app`) + +you may need to reboot after doing this before launching Furnace. diff --git a/res/releaseReadme/stable-win.txt b/res/releaseReadme/stable-win.txt new file mode 100644 index 000000000..22eed14ca --- /dev/null +++ b/res/releaseReadme/stable-win.txt @@ -0,0 +1,5 @@ +# Furnace (chiptune tracker) + +thank you for downloading Furnace! I hope you enjoy using it. + +extract this archive, and run furnace.exe to get started. diff --git a/res/releaseReadme/unstable-other.txt b/res/releaseReadme/unstable-other.txt new file mode 100644 index 000000000..2b042a2bc --- /dev/null +++ b/res/releaseReadme/unstable-other.txt @@ -0,0 +1,6 @@ +# unstable build notice + +this is a dev build (also known as "unstable", "artifact" or "nightly"). +it may contain work-in-progress features and/or bug fixes. + +no compatibility is guaranteed between unstable and stable - you have been warned! diff --git a/res/releaseReadme/unstable-win.txt b/res/releaseReadme/unstable-win.txt new file mode 100644 index 000000000..2ca438fbb --- /dev/null +++ b/res/releaseReadme/unstable-win.txt @@ -0,0 +1,11 @@ +# unstable build notice + +this is a dev build (also known as "unstable", "artifact" or "nightly"). +it may contain work-in-progress features and/or bug fixes. + +no compatibility is guaranteed between unstable and stable - you have been warned! + +# the .pdb file + +YOU SHALL COPY THIS FILE ALONGSIDE furnace.exe. it contains debug information which is vital in the moment of a crash. +failure to copy this file will result in useless backtraces (furnace_crash.txt) and therefore hinder me from fixing any crashes. diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index fc056f5ec..b0c1c2ff6 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -15,7 +15,7 @@ fi cd win32build # TODO: potential Arch-ism? -i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF .. || exit 1 +i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2 -march=i686" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -march=i686" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=OFF -DWITH_RENDER_DX11=ON .. || exit 1 make -j8 || exit 1 cd .. @@ -34,11 +34,6 @@ cp -r ../../wavetables wavetables || exit 1 i686-w64-mingw32-strip -s furnace.exe || exit 1 -# patch to remove GetTickCount64 -xxd -c 256 -ps furnace.exe | sed "s/4765745469636b436f756e743634/4765745469636b436f756e740000/g" | xxd -ps -r > furnace-patched.exe -rm furnace.exe -mv furnace-patched.exe furnace.exe - zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers doc demos instruments wavetables furName=$(git describe --tags | sed "s/v0/0/") diff --git a/scripts/release-winxp.sh b/scripts/release-winxp.sh new file mode 100755 index 000000000..59604ea36 --- /dev/null +++ b/scripts/release-winxp.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# make Windows release +# this script shall be run from Arch Linux with MinGW installed! + +if [ ! -e /tmp/furnace ]; then + ln -s "$PWD" /tmp/furnace || exit 1 +fi + +cd /tmp/furnace + +if [ ! -e xpbuild ]; then + mkdir xpbuild || exit 1 +fi + +cd xpbuild + +# TODO: potential Arch-ism? +i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF .. || exit 1 +make -j8 || exit 1 + +cd .. + +mkdir -p release/winxp || exit 1 +cd release/winxp + +cp ../../LICENSE LICENSE.txt || exit 1 +cp ../../xpbuild/furnace.exe . || exit 1 +cp ../../README.md README.txt || exit 1 +cp -r ../../papers papers || exit 1 +cp -r ../../doc doc || exit 1 +cp -r ../../demos demos || exit 1 +cp -r ../../instruments instruments || exit 1 +cp -r ../../wavetables wavetables || exit 1 + +i686-w64-mingw32-strip -s furnace.exe || exit 1 + +# patch to remove GetTickCount64 +xxd -c 256 -ps furnace.exe | sed "s/4765745469636b436f756e743634/4765745469636b436f756e740000/g" | xxd -ps -r > furnace-patched.exe +rm furnace.exe +mv furnace-patched.exe furnace.exe + +zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers doc demos instruments wavetables + +furName=$(git describe --tags | sed "s/v0/0/") + +mv furnace.zip furnace-"$furName"-win32-XP-ONLY.zip diff --git a/src/audio/sdlAudio.cpp b/src/audio/sdlAudio.cpp index 5d07921e9..ddbe85947 100644 --- a/src/audio/sdlAudio.cpp +++ b/src/audio/sdlAudio.cpp @@ -127,6 +127,7 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { ac.callback=taSDLProcess; ac.userdata=this; + logV("opening audio device..."); ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,0); if (ai==0) { logE("could not open audio device: %s",SDL_GetError()); @@ -147,6 +148,8 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { desc.bufsize=ar.samples; desc.fragments=1; + logV("got info: %d channels, %d bufsize",desc.outChans,desc.bufsize); + if (desc.outChans>0) { outBufs=new float*[desc.outChans]; for (int i=0; i255) { \ + chanStream[x]->writeC(0xfc); \ + chanStream[x]->writeS(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>1) { \ + delayPopularity[tick-lastTick[x]]++; \ + chanStream[x]->writeC(0xfd); \ + chanStream[x]->writeC(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>0) { \ + chanStream[x]->writeC(0xfe); \ + } \ + lastTick[x]=tick; \ + } \ + } else { \ + if (!wroteTickGlobal) { \ + wroteTickGlobal=true; \ + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ + } \ + } + +void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xb4); + } else { + w->writeC(CLAMP(c.value+60,0,0xb3)); + } + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + case DIV_CMD_INSTRUMENT: + case DIV_CMD_PANNING: + case DIV_CMD_PRE_PORTA: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_VOLUME: + case DIV_CMD_HINT_PORTA: + case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_HINT_LEGATO: + w->writeC((unsigned char)c.cmd+0xb4); + break; + default: + w->writeC(0xf0); // unoptimized extended command + w->writeC(c.cmd); + break; + } + switch (c.cmd) { + case DIV_CMD_HINT_LEGATO: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xff); + } else { + w->writeC(c.value+60); + } + break; + case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + break; + case DIV_CMD_INSTRUMENT: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_VOLUME: + w->writeC(c.value); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_PRE_PORTA: + w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); + break; + case DIV_CMD_HINT_VOL_SLIDE: + w->writeS(c.value); + break; + case DIV_CMD_SAMPLE_MODE: + case DIV_CMD_SAMPLE_FREQ: + case DIV_CMD_SAMPLE_BANK: + case DIV_CMD_SAMPLE_POS: + case DIV_CMD_SAMPLE_DIR: + case DIV_CMD_FM_HARD_RESET: + case DIV_CMD_FM_LFO: + case DIV_CMD_FM_LFO_WAVE: + case DIV_CMD_FM_FB: + case DIV_CMD_FM_EXTCH: + case DIV_CMD_FM_AM_DEPTH: + case DIV_CMD_FM_PM_DEPTH: + case DIV_CMD_STD_NOISE_FREQ: + case DIV_CMD_STD_NOISE_MODE: + case DIV_CMD_WAVE: + case DIV_CMD_GB_SWEEP_TIME: + case DIV_CMD_GB_SWEEP_DIR: + case DIV_CMD_PCE_LFO_MODE: + case DIV_CMD_PCE_LFO_SPEED: + case DIV_CMD_NES_DMC: + case DIV_CMD_C64_CUTOFF: + case DIV_CMD_C64_RESONANCE: + case DIV_CMD_C64_FILTER_MODE: + case DIV_CMD_C64_RESET_TIME: + case DIV_CMD_C64_RESET_MASK: + case DIV_CMD_C64_FILTER_RESET: + case DIV_CMD_C64_DUTY_RESET: + case DIV_CMD_C64_EXTENDED: + case DIV_CMD_AY_ENVELOPE_SET: + case DIV_CMD_AY_ENVELOPE_LOW: + case DIV_CMD_AY_ENVELOPE_HIGH: + case DIV_CMD_AY_ENVELOPE_SLIDE: + case DIV_CMD_AY_NOISE_MASK_AND: + case DIV_CMD_AY_NOISE_MASK_OR: + case DIV_CMD_AY_AUTO_ENVELOPE: + case DIV_CMD_FDS_MOD_DEPTH: + case DIV_CMD_FDS_MOD_HIGH: + case DIV_CMD_FDS_MOD_LOW: + case DIV_CMD_FDS_MOD_POS: + case DIV_CMD_FDS_MOD_WAVE: + case DIV_CMD_SAA_ENVELOPE: + case DIV_CMD_AMIGA_FILTER: + case DIV_CMD_AMIGA_AM: + case DIV_CMD_AMIGA_PM: + case DIV_CMD_MACRO_OFF: + case DIV_CMD_MACRO_ON: + case DIV_CMD_HINT_ARP_TIME: + w->writeC(1); // length + w->writeC(c.value); + break; + case DIV_CMD_FM_TL: + case DIV_CMD_FM_AM: + case DIV_CMD_FM_AR: + case DIV_CMD_FM_DR: + case DIV_CMD_FM_SL: + case DIV_CMD_FM_D2R: + case DIV_CMD_FM_RR: + case DIV_CMD_FM_DT: + case DIV_CMD_FM_DT2: + case DIV_CMD_FM_RS: + case DIV_CMD_FM_KSR: + case DIV_CMD_FM_VIB: + case DIV_CMD_FM_SUS: + case DIV_CMD_FM_WS: + case DIV_CMD_FM_SSG: + case DIV_CMD_FM_REV: + case DIV_CMD_FM_EG_SHIFT: + case DIV_CMD_FM_MULT: + case DIV_CMD_FM_FINE: + case DIV_CMD_AY_IO_WRITE: + case DIV_CMD_AY_AUTO_PWM: + case DIV_CMD_SURROUND_PANNING: + w->writeC(2); // length + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_C64_FINE_DUTY: + case DIV_CMD_C64_FINE_CUTOFF: + case DIV_CMD_LYNX_LFSR_LOAD: + w->writeC(2); // length + w->writeS(c.value); + break; + case DIV_CMD_FM_FIXFREQ: + w->writeC(2); // length + w->writeS((c.value<<12)|(c.value2&0x7ff)); + break; + case DIV_CMD_NES_SWEEP: + w->writeC(1); // length + w->writeC((c.value?8:0)|(c.value2&0x77)); + break; + default: + logW("unimplemented command %s!",cmdName[c.cmd]); + w->writeC(0); // length + break; + } +} + +SafeWriter* DivEngine::saveCommand(bool binary) { + stop(); + repeatPattern=false; + shallStop=false; + setOrder(0); + BUSY_BEGIN_SOFT; + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + walkSong(loopOrder,loopRow,loopEnd); + logI("loop point: %d %d",loopOrder,loopRow); + + int cmdPopularity[256]; + int delayPopularity[256]; + + int sortedCmdPopularity[16]; + int sortedDelayPopularity[16]; + unsigned char sortedCmd[16]; + unsigned char sortedDelay[16]; + + SafeWriter* chanStream[DIV_MAX_CHANS]; + unsigned int chanStreamOff[DIV_MAX_CHANS]; + bool wroteTick[DIV_MAX_CHANS]; + + memset(cmdPopularity,0,256*sizeof(int)); + memset(delayPopularity,0,256*sizeof(int)); + memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*)); + memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int)); + memset(sortedCmdPopularity,0,16*sizeof(int)); + memset(sortedDelayPopularity,0,16*sizeof(int)); + memset(sortedCmd,0,16); + memset(sortedDelay,0,16); + + SafeWriter* w=new SafeWriter; + w->init(); + + // write header + if (binary) { + w->write("FCS",4); + w->writeI(chans); + // offsets + for (int i=0; iinit(); + w->writeI(0); + } + // preset delays and speed dial + for (int i=0; i<32; i++) { + w->writeC(0); + } + } else { + w->writeText("# Furnace Command Stream\n\n"); + + w->writeText("[Information]\n"); + w->writeText(fmt::sprintf("name: %s\n",song.name)); + w->writeText(fmt::sprintf("author: %s\n",song.author)); + w->writeText(fmt::sprintf("category: %s\n",song.category)); + w->writeText(fmt::sprintf("system: %s\n",song.systemName)); + + w->writeText("\n"); + + w->writeText("[SubSongInformation]\n"); + w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); + w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); + + w->writeText("\n"); + + w->writeText("[SysDefinition]\n"); + // TODO + + w->writeText("\n"); + } + + // play the song ourselves + bool done=false; + playSub(false); + + if (!binary) { + w->writeText("[Stream]\n"); + } + int tick=0; + bool oldCmdStreamEnabled=cmdStreamEnabled; + cmdStreamEnabled=true; + double curDivider=divider; + int lastTick[DIV_MAX_CHANS]; + + memset(lastTick,0,DIV_MAX_CHANS*sizeof(int)); + while (!done) { + if (nextTick(false,true) || !playing) { + done=true; + } + // get command stream + bool wroteTickGlobal=false; + memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); + if (curDivider!=divider) { + curDivider=divider; + WRITE_TICK(0); + if (binary) { + chanStream[0]->writeC(0xfb); + chanStream[0]->writeI((int)(curDivider*65536)); + } else { + w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); + } + } + for (DivCommand& i: cmdStream) { + switch (i.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + case DIV_CMD_PRE_NOTE: + break; + default: + WRITE_TICK(i.chan); + if (binary) { + cmdPopularity[i.cmd]++; + writePackedCommandValues(chanStream[i.chan],i); + } else { + w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); + } + break; + } + } + cmdStream.clear(); + tick++; + } + cmdStreamEnabled=oldCmdStreamEnabled; + + if (binary) { + int sortCand=-1; + int sortPos=0; + while (sortPos<16) { + sortCand=-1; + for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) { + if (cmdPopularity[i]) { + if (sortCand==-1) { + sortCand=i; + } else if (cmdPopularity[sortCand]writeC(0xff); + // optimize stream + SafeWriter* oldStream=chanStream[i]; + SafeReader* reader=oldStream->toReader(); + chanStream[i]=new SafeWriter; + chanStream[i]->init(); + + while (1) { + try { + unsigned char next=reader->readC(); + switch (next) { + case 0xb8: // instrument + case 0xc0: // pre porta + case 0xc3: // vibrato range + case 0xc4: // vibrato shape + case 0xc5: // pitch + case 0xc7: // volume + case 0xca: // legato + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xbe: // panning + case 0xc2: // vibrato + case 0xc6: // arpeggio + case 0xc8: // vol slide + case 0xc9: // porta + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xf0: { // full command (pre) + unsigned char cmd=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedCmd[j]==cmd) { + chanStream[i]->writeC(0xd0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(0xf7); // full command + chanStream[i]->writeC(cmd); + } + + unsigned char cmdLen=reader->readC(); + logD("cmdLen: %d",cmdLen); + for (unsigned char j=0; jreadC(); + chanStream[i]->writeC(next); + } + break; + } + case 0xfb: // tick rate + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xfc: { // 16-bit wait + unsigned short delay=reader->readS(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeS(delay); + } + break; + } + case 0xfd: { // 8-bit wait + unsigned char delay=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeC(delay); + } + break; + } + default: + chanStream[i]->writeC(next); + break; + } + } catch (EndOfFileException& e) { + break; + } + } + + oldStream->finish(); + delete oldStream; + } + + for (int i=0; itell(); + logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); + w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); + chanStream[i]->finish(); + delete chanStream[i]; + } + + w->seek(8,SEEK_SET); + for (int i=0; iwriteI(chanStreamOff[i]); + } + + logD("delay popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedDelay[i]); + if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); + } + + logD("command popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedCmd[i]); + if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); + } + } else { + if (!playing) { + w->writeText(">> END\n"); + } else { + w->writeText(">> LOOP 0\n"); + } + } + + remainingLoops=-1; + playing=false; + freelance=false; + extValuePresent=false; + BUSY_END; + + return w; +} diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 875ff487d..0ad264e53 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -411,6 +411,13 @@ class DivDispatch { */ virtual DivMacroInt* getChanMacroInt(int chan); + /** + * get the stereo panning of a channel. + * @param chan the channel. + * @return a 16-bit number. left in top 8 bits and right in bottom 8 bits. + */ + virtual unsigned short getPan(int chan); + /** * get currently playing sample (and its position). * @param chan the channel. diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4c1df9f7a..7428de45d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "dispatch.h" #include "song.h" -#define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" #include "safeReader.h" @@ -34,9 +34,6 @@ #endif #include #include -#ifdef HAVE_SNDFILE -#include "sfWrapper.h" -#endif #include void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) { @@ -289,927 +286,6 @@ double DivEngine::benchmarkSeek() { return tAvg; } -#define WRITE_TICK(x) \ - if (binary) { \ - if (!wroteTick[x]) { \ - wroteTick[x]=true; \ - if (tick-lastTick[x]>255) { \ - chanStream[x]->writeC(0xfc); \ - chanStream[x]->writeS(tick-lastTick[x]); \ - } else if (tick-lastTick[x]>1) { \ - delayPopularity[tick-lastTick[x]]++; \ - chanStream[x]->writeC(0xfd); \ - chanStream[x]->writeC(tick-lastTick[x]); \ - } else if (tick-lastTick[x]>0) { \ - chanStream[x]->writeC(0xfe); \ - } \ - lastTick[x]=tick; \ - } \ - } else { \ - if (!wroteTickGlobal) { \ - wroteTickGlobal=true; \ - w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ - } \ - } - -void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { - switch (c.cmd) { - case DIV_CMD_NOTE_ON: - if (c.value==DIV_NOTE_NULL) { - w->writeC(0xb4); - } else { - w->writeC(CLAMP(c.value+60,0,0xb3)); - } - break; - case DIV_CMD_NOTE_OFF: - case DIV_CMD_NOTE_OFF_ENV: - case DIV_CMD_ENV_RELEASE: - case DIV_CMD_INSTRUMENT: - case DIV_CMD_PANNING: - case DIV_CMD_PRE_PORTA: - case DIV_CMD_HINT_VIBRATO: - case DIV_CMD_HINT_VIBRATO_RANGE: - case DIV_CMD_HINT_VIBRATO_SHAPE: - case DIV_CMD_HINT_PITCH: - case DIV_CMD_HINT_ARPEGGIO: - case DIV_CMD_HINT_VOLUME: - case DIV_CMD_HINT_PORTA: - case DIV_CMD_HINT_VOL_SLIDE: - case DIV_CMD_HINT_LEGATO: - w->writeC((unsigned char)c.cmd+0xb4); - break; - default: - w->writeC(0xf0); // unoptimized extended command - w->writeC(c.cmd); - break; - } - switch (c.cmd) { - case DIV_CMD_HINT_LEGATO: - if (c.value==DIV_NOTE_NULL) { - w->writeC(0xff); - } else { - w->writeC(c.value+60); - } - break; - case DIV_CMD_NOTE_ON: - case DIV_CMD_NOTE_OFF: - case DIV_CMD_NOTE_OFF_ENV: - case DIV_CMD_ENV_RELEASE: - break; - case DIV_CMD_INSTRUMENT: - case DIV_CMD_HINT_VIBRATO_RANGE: - case DIV_CMD_HINT_VIBRATO_SHAPE: - case DIV_CMD_HINT_PITCH: - case DIV_CMD_HINT_VOLUME: - w->writeC(c.value); - break; - case DIV_CMD_PANNING: - case DIV_CMD_HINT_VIBRATO: - case DIV_CMD_HINT_ARPEGGIO: - case DIV_CMD_HINT_PORTA: - w->writeC(c.value); - w->writeC(c.value2); - break; - case DIV_CMD_PRE_PORTA: - w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); - break; - case DIV_CMD_HINT_VOL_SLIDE: - w->writeS(c.value); - break; - case DIV_CMD_SAMPLE_MODE: - case DIV_CMD_SAMPLE_FREQ: - case DIV_CMD_SAMPLE_BANK: - case DIV_CMD_SAMPLE_POS: - case DIV_CMD_SAMPLE_DIR: - case DIV_CMD_FM_HARD_RESET: - case DIV_CMD_FM_LFO: - case DIV_CMD_FM_LFO_WAVE: - case DIV_CMD_FM_FB: - case DIV_CMD_FM_EXTCH: - case DIV_CMD_FM_AM_DEPTH: - case DIV_CMD_FM_PM_DEPTH: - case DIV_CMD_STD_NOISE_FREQ: - case DIV_CMD_STD_NOISE_MODE: - case DIV_CMD_WAVE: - case DIV_CMD_GB_SWEEP_TIME: - case DIV_CMD_GB_SWEEP_DIR: - case DIV_CMD_PCE_LFO_MODE: - case DIV_CMD_PCE_LFO_SPEED: - case DIV_CMD_NES_DMC: - case DIV_CMD_C64_CUTOFF: - case DIV_CMD_C64_RESONANCE: - case DIV_CMD_C64_FILTER_MODE: - case DIV_CMD_C64_RESET_TIME: - case DIV_CMD_C64_RESET_MASK: - case DIV_CMD_C64_FILTER_RESET: - case DIV_CMD_C64_DUTY_RESET: - case DIV_CMD_C64_EXTENDED: - case DIV_CMD_AY_ENVELOPE_SET: - case DIV_CMD_AY_ENVELOPE_LOW: - case DIV_CMD_AY_ENVELOPE_HIGH: - case DIV_CMD_AY_ENVELOPE_SLIDE: - case DIV_CMD_AY_NOISE_MASK_AND: - case DIV_CMD_AY_NOISE_MASK_OR: - case DIV_CMD_AY_AUTO_ENVELOPE: - case DIV_CMD_FDS_MOD_DEPTH: - case DIV_CMD_FDS_MOD_HIGH: - case DIV_CMD_FDS_MOD_LOW: - case DIV_CMD_FDS_MOD_POS: - case DIV_CMD_FDS_MOD_WAVE: - case DIV_CMD_SAA_ENVELOPE: - case DIV_CMD_AMIGA_FILTER: - case DIV_CMD_AMIGA_AM: - case DIV_CMD_AMIGA_PM: - case DIV_CMD_MACRO_OFF: - case DIV_CMD_MACRO_ON: - case DIV_CMD_HINT_ARP_TIME: - w->writeC(1); // length - w->writeC(c.value); - break; - case DIV_CMD_FM_TL: - case DIV_CMD_FM_AM: - case DIV_CMD_FM_AR: - case DIV_CMD_FM_DR: - case DIV_CMD_FM_SL: - case DIV_CMD_FM_D2R: - case DIV_CMD_FM_RR: - case DIV_CMD_FM_DT: - case DIV_CMD_FM_DT2: - case DIV_CMD_FM_RS: - case DIV_CMD_FM_KSR: - case DIV_CMD_FM_VIB: - case DIV_CMD_FM_SUS: - case DIV_CMD_FM_WS: - case DIV_CMD_FM_SSG: - case DIV_CMD_FM_REV: - case DIV_CMD_FM_EG_SHIFT: - case DIV_CMD_FM_MULT: - case DIV_CMD_FM_FINE: - case DIV_CMD_AY_IO_WRITE: - case DIV_CMD_AY_AUTO_PWM: - case DIV_CMD_SURROUND_PANNING: - w->writeC(2); // length - w->writeC(c.value); - w->writeC(c.value2); - break; - case DIV_CMD_C64_FINE_DUTY: - case DIV_CMD_C64_FINE_CUTOFF: - case DIV_CMD_LYNX_LFSR_LOAD: - w->writeC(2); // length - w->writeS(c.value); - break; - case DIV_CMD_FM_FIXFREQ: - w->writeC(2); // length - w->writeS((c.value<<12)|(c.value2&0x7ff)); - break; - case DIV_CMD_NES_SWEEP: - w->writeC(1); // length - w->writeC((c.value?8:0)|(c.value2&0x77)); - break; - default: - logW("unimplemented command %s!",cmdName[c.cmd]); - w->writeC(0); // length - break; - } -} - -SafeWriter* DivEngine::saveCommand(bool binary) { - stop(); - repeatPattern=false; - shallStop=false; - setOrder(0); - BUSY_BEGIN_SOFT; - // determine loop point - int loopOrder=0; - int loopRow=0; - int loopEnd=0; - walkSong(loopOrder,loopRow,loopEnd); - logI("loop point: %d %d",loopOrder,loopRow); - - int cmdPopularity[256]; - int delayPopularity[256]; - - int sortedCmdPopularity[16]; - int sortedDelayPopularity[16]; - unsigned char sortedCmd[16]; - unsigned char sortedDelay[16]; - - SafeWriter* chanStream[DIV_MAX_CHANS]; - unsigned int chanStreamOff[DIV_MAX_CHANS]; - bool wroteTick[DIV_MAX_CHANS]; - - memset(cmdPopularity,0,256*sizeof(int)); - memset(delayPopularity,0,256*sizeof(int)); - memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*)); - memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int)); - memset(sortedCmdPopularity,0,16*sizeof(int)); - memset(sortedDelayPopularity,0,16*sizeof(int)); - memset(sortedCmd,0,16); - memset(sortedDelay,0,16); - - SafeWriter* w=new SafeWriter; - w->init(); - - // write header - if (binary) { - w->write("FCS",4); - w->writeI(chans); - // offsets - for (int i=0; iinit(); - w->writeI(0); - } - // preset delays and speed dial - for (int i=0; i<32; i++) { - w->writeC(0); - } - } else { - w->writeText("# Furnace Command Stream\n\n"); - - w->writeText("[Information]\n"); - w->writeText(fmt::sprintf("name: %s\n",song.name)); - w->writeText(fmt::sprintf("author: %s\n",song.author)); - w->writeText(fmt::sprintf("category: %s\n",song.category)); - w->writeText(fmt::sprintf("system: %s\n",song.systemName)); - - w->writeText("\n"); - - w->writeText("[SubSongInformation]\n"); - w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); - w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); - - w->writeText("\n"); - - w->writeText("[SysDefinition]\n"); - // TODO - - w->writeText("\n"); - } - - // play the song ourselves - bool done=false; - playSub(false); - - if (!binary) { - w->writeText("[Stream]\n"); - } - int tick=0; - bool oldCmdStreamEnabled=cmdStreamEnabled; - cmdStreamEnabled=true; - double curDivider=divider; - int lastTick[DIV_MAX_CHANS]; - - memset(lastTick,0,DIV_MAX_CHANS*sizeof(int)); - while (!done) { - if (nextTick(false,true) || !playing) { - done=true; - } - // get command stream - bool wroteTickGlobal=false; - memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); - if (curDivider!=divider) { - curDivider=divider; - WRITE_TICK(0); - if (binary) { - chanStream[0]->writeC(0xfb); - chanStream[0]->writeI((int)(curDivider*65536)); - } else { - w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); - } - } - for (DivCommand& i: cmdStream) { - switch (i.cmd) { - // strip away hinted/useless commands - case DIV_ALWAYS_SET_VOLUME: - break; - case DIV_CMD_GET_VOLUME: - break; - case DIV_CMD_VOLUME: - break; - case DIV_CMD_NOTE_PORTA: - break; - case DIV_CMD_LEGATO: - break; - case DIV_CMD_PITCH: - break; - case DIV_CMD_PRE_NOTE: - break; - default: - WRITE_TICK(i.chan); - if (binary) { - cmdPopularity[i.cmd]++; - writePackedCommandValues(chanStream[i.chan],i); - } else { - w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); - } - break; - } - } - cmdStream.clear(); - tick++; - } - cmdStreamEnabled=oldCmdStreamEnabled; - - if (binary) { - int sortCand=-1; - int sortPos=0; - while (sortPos<16) { - sortCand=-1; - for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) { - if (cmdPopularity[i]) { - if (sortCand==-1) { - sortCand=i; - } else if (cmdPopularity[sortCand]writeC(0xff); - // optimize stream - SafeWriter* oldStream=chanStream[i]; - SafeReader* reader=oldStream->toReader(); - chanStream[i]=new SafeWriter; - chanStream[i]->init(); - - while (1) { - try { - unsigned char next=reader->readC(); - switch (next) { - case 0xb8: // instrument - case 0xc0: // pre porta - case 0xc3: // vibrato range - case 0xc4: // vibrato shape - case 0xc5: // pitch - case 0xc7: // volume - case 0xca: // legato - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xbe: // panning - case 0xc2: // vibrato - case 0xc6: // arpeggio - case 0xc8: // vol slide - case 0xc9: // porta - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xf0: { // full command (pre) - unsigned char cmd=reader->readC(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedCmd[j]==cmd) { - chanStream[i]->writeC(0xd0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(0xf7); // full command - chanStream[i]->writeC(cmd); - } - - unsigned char cmdLen=reader->readC(); - logD("cmdLen: %d",cmdLen); - for (unsigned char j=0; jreadC(); - chanStream[i]->writeC(next); - } - break; - } - case 0xfb: // tick rate - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - next=reader->readC(); - chanStream[i]->writeC(next); - break; - case 0xfc: { // 16-bit wait - unsigned short delay=reader->readS(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedDelay[j]==delay) { - chanStream[i]->writeC(0xe0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(next); - chanStream[i]->writeS(delay); - } - break; - } - case 0xfd: { // 8-bit wait - unsigned char delay=reader->readC(); - bool foundShort=false; - for (int j=0; j<16; j++) { - if (sortedDelay[j]==delay) { - chanStream[i]->writeC(0xe0+j); - foundShort=true; - break; - } - } - if (!foundShort) { - chanStream[i]->writeC(next); - chanStream[i]->writeC(delay); - } - break; - } - default: - chanStream[i]->writeC(next); - break; - } - } catch (EndOfFileException& e) { - break; - } - } - - oldStream->finish(); - delete oldStream; - } - - for (int i=0; itell(); - logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); - w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); - chanStream[i]->finish(); - delete chanStream[i]; - } - - w->seek(8,SEEK_SET); - for (int i=0; iwriteI(chanStreamOff[i]); - } - - logD("delay popularity:"); - for (int i=0; i<16; i++) { - w->writeC(sortedDelay[i]); - if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); - } - - logD("command popularity:"); - for (int i=0; i<16; i++) { - w->writeC(sortedCmd[i]); - if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); - } - } else { - if (!playing) { - w->writeText(">> END\n"); - } else { - w->writeText(">> LOOP 0\n"); - } - } - - remainingLoops=-1; - playing=false; - freelance=false; - extValuePresent=false; - BUSY_END; - - return w; -} - -void _runExportThread(DivEngine* caller) { - caller->runExportThread(); -} - -bool DivEngine::isExporting() { - return exporting; -} - -#ifdef HAVE_SNDFILE -void DivEngine::runExportThread() { - size_t fadeOutSamples=got.rate*exportFadeOut; - size_t curFadeOutSample=0; - bool isFadingOut=false; - - quitDispatch(); - initDispatch(true); - renderSamples(); - - switch (exportMode) { - case DIV_EXPORT_MODE_ONE: { - SNDFILE* sf; - SF_INFO si; - SFWrapper sfWrap; - si.samplerate=got.rate; - si.channels=2; - si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - - sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si); - if (sf==NULL) { - logE("could not open file for writing! (%s)",sf_strerror(NULL)); - quitDispatch(); - initDispatch(false); - renderSamples(); - exporting=false; - return; - } - - float* outBuf[3]; - outBuf[0]=new float[EXPORT_BUFSIZE]; - outBuf[1]=new float[EXPORT_BUFSIZE]; - outBuf[2]=new float[EXPORT_BUFSIZE*2]; - - // take control of audio output - deinitAudioBackend(); - playSub(false); - - logI("rendering to file..."); - - while (playing) { - size_t total=0; - nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); - totalProcessed=EXPORT_BUFSIZE; - } - for (int i=0; i<(int)totalProcessed; i++) { - total++; - if (isFadingOut) { - double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul; - outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul; - if (++curFadeOutSample>=fadeOutSamples) { - playing=false; - break; - } - } else { - outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i])); - outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i])); - if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { - logD("start fading out..."); - isFadingOut=true; - } - } - } - - if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { - logE("error: failed to write entire buffer!"); - break; - } - } - - delete[] outBuf[0]; - delete[] outBuf[1]; - delete[] outBuf[2]; - - if (sfWrap.doClose()!=0) { - logE("could not close audio file!"); - } - exporting=false; - - if (initAudioBackend()) { - for (int i=0; isetRun(true)) { - logE("error while activating audio!"); - } - } - logI("done!"); - break; - } - case DIV_EXPORT_MODE_MANY_SYS: { - SNDFILE* sf[DIV_MAX_CHIPS]; - SF_INFO si[DIV_MAX_CHIPS]; - String fname[DIV_MAX_CHIPS]; - SFWrapper sfWrap[DIV_MAX_CHIPS]; - for (int i=0; igetOutputCount(); - si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - } - - for (int i=0; igetOutputCount()]; - } - - // take control of audio output - deinitAudioBackend(); - playSub(false); - - logI("rendering to files..."); - - while (playing) { - size_t total=0; - nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); - totalProcessed=EXPORT_BUFSIZE; - } - for (int j=0; j<(int)totalProcessed; j++) { - total++; - if (isFadingOut) { - double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - for (int i=0; i=fadeOutSamples) { - playing=false; - break; - } - } else { - for (int i=0; i-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { - logD("start fading out..."); - isFadingOut=true; - } - } - } - for (int i=0; isetRun(true)) { - logE("error while activating audio!"); - } - } - logI("done!"); - break; - } - case DIV_EXPORT_MODE_MANY_CHAN: { - // take control of audio output - deinitAudioBackend(); - - float* outBuf[3]; - outBuf[0]=new float[EXPORT_BUFSIZE]; - outBuf[1]=new float[EXPORT_BUFSIZE]; - outBuf[2]=new float[EXPORT_BUFSIZE*2]; - int loopCount=remainingLoops; - - logI("rendering to files..."); - - for (int i=0; imuteChannel(dispatchChanOfChan[j],isMuted[j]); - } - } - - curOrder=0; - prevOrder=0; - curFadeOutSample=0; - lastLoopPos=-1; - totalLoops=0; - isFadingOut=false; - if (exportFadeOut<=0.01) { - remainingLoops=loopCount; - } else { - remainingLoops=-1; - } - playSub(false); - - while (playing) { - size_t total=0; - nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); - totalProcessed=EXPORT_BUFSIZE; - } - for (int j=0; j<(int)totalProcessed; j++) { - total++; - if (isFadingOut) { - double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]))*mul; - outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]))*mul; - if (++curFadeOutSample>=fadeOutSamples) { - playing=false; - break; - } - } else { - outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j])); - outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j])); - if (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { - logD("start fading out..."); - isFadingOut=true; - } - } - } - if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { - logE("error: failed to write entire buffer!"); - break; - } - } - - if (sfWrap.doClose()!=0) { - logE("could not close audio file!"); - } - - if (getChannelType(i)==5) { - i++; - while (true) { - if (i>=chans) break; - if (getChannelType(i)!=5) break; - i++; - } - i--; - } - - if (stopExport) break; - } - exporting=false; - - delete[] outBuf[0]; - delete[] outBuf[1]; - delete[] outBuf[2]; - - for (int i=0; imuteChannel(dispatchChanOfChan[i],false); - } - } - - if (initAudioBackend()) { - for (int i=0; isetRun(true)) { - logE("error while activating audio!"); - } - } - logI("done!"); - break; - } - } - - quitDispatch(); - initDispatch(false); - renderSamples(); - stopExport=false; -} -#else -void DivEngine::runExportThread() { -} -#endif - -bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) { -#ifndef HAVE_SNDFILE - logE("Furnace was not compiled with libsndfile. cannot export!"); - return false; -#else - exportPath=path; - exportMode=mode; - exportFadeOut=fadeOutTime; - if (exportMode!=DIV_EXPORT_MODE_ONE) { - // remove extension - String lowerCase=exportPath; - for (char& i: lowerCase) { - if (i>='A' && i<='Z') i+='a'-'A'; - } - size_t extPos=lowerCase.rfind(".wav"); - if (extPos!=String::npos) { - exportPath=exportPath.substr(0,extPos); - } - } - exporting=true; - stopExport=false; - stop(); - repeatPattern=false; - setOrder(0); - if (exportFadeOut<=0.01) { - remainingLoops=loops; - } else { - remainingLoops=-1; - } - exportLoopCount=loops; - exportThread=new std::thread(_runExportThread,this); - return true; -#endif -} - -void DivEngine::waitAudioFile() { - if (exportThread!=NULL) { - exportThread->join(); - } -} - -bool DivEngine::haltAudioFile() { - stopExport=true; - stop(); - return true; -} - void DivEngine::notifyInsChange(int ins) { BUSY_BEGIN; for (int i=0; igetOutputCount(); if (outs>16) outs=16; if (outs<2) { + song.patchbay.reserve(DIV_MAX_OUTPUTS); for (unsigned int j=0; j=chans) return 0; + return disCont[dispatchOfChan[ch]].dispatch->getPan(dispatchChanOfChan[ch]); +} + void* DivEngine::getDispatchChanState(int ch) { if (ch<0 || ch>=chans) return NULL; return disCont[dispatchOfChan[ch]].dispatch->getChanState(dispatchChanOfChan[ch]); @@ -2238,6 +1323,7 @@ void DivEngine::enableCommandStream(bool enable) { void DivEngine::getCommandStream(std::vector& where) { BUSY_BEGIN; where.clear(); + where.reserve(cmdStream.size()); for (DivCommand& i: cmdStream) { where.push_back(i); } @@ -3400,492 +2486,6 @@ int DivEngine::addSamplePtr(DivSample* which) { return sampleCount; } -DivSample* DivEngine::sampleFromFile(const char* path) { - if (song.sample.size()>=256) { - lastError="too many samples!"; - return NULL; - } - BUSY_BEGIN; - warnings=""; - - const char* pathRedux=strrchr(path,DIR_SEPARATOR); - if (pathRedux==NULL) { - pathRedux=path; - } else { - pathRedux++; - } - String stripPath; - const char* pathReduxEnd=strrchr(pathRedux,'.'); - if (pathReduxEnd==NULL) { - stripPath=pathRedux; - } else { - for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { - stripPath+=*i; - } - } - - const char* ext=strrchr(path,'.'); - if (ext!=NULL) { - String extS; - for (; *ext; ext++) { - char i=*ext; - if (i>='A' && i<='Z') { - i+='a'-'A'; - } - extS+=i; - } - if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr - size_t len=0; - DivSample* sample=new DivSample; - sample->name=stripPath; - - FILE* f=ps_fopen(path,"rb"); - if (f==NULL) { - BUSY_END; - lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); - delete sample; - return NULL; - } - - if (fseek(f,0,SEEK_END)<0) { - fclose(f); - BUSY_END; - lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); - delete sample; - return NULL; - } - - len=ftell(f); - - if (len==0) { - fclose(f); - BUSY_END; - lastError="file is empty!"; - delete sample; - return NULL; - } - - if (len==(SIZE_MAX>>1)) { - fclose(f); - BUSY_END; - lastError="file is invalid!"; - delete sample; - return NULL; - } - - if (fseek(f,0,SEEK_SET)<0) { - fclose(f); - BUSY_END; - lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); - delete sample; - return NULL; - } - - if (extS==".dmc") { - sample->rate=33144; - sample->centerRate=33144; - sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM; - sample->init(len*8); - } else if (extS==".brr") { - sample->rate=32000; - sample->centerRate=32000; - sample->depth=DIV_SAMPLE_DEPTH_BRR; - sample->init(16*(len/9)); - } else { - fclose(f); - BUSY_END; - lastError="wait... is that right? no I don't think so..."; - delete sample; - return NULL; - } - - unsigned char* dataBuf=sample->dataDPCM; - if (extS==".brr") { - dataBuf=sample->dataBRR; - if ((len%9)==2) { - // read loop position - unsigned short loopPos=0; - logD("BRR file has loop position"); - if (fread(&loopPos,1,2,f)!=2) { - logW("could not read loop position! %s",strerror(errno)); - } else { -#ifdef TA_BIG_ENDIAN - loopPos=(loopPos>>8)|(loopPos<<8); -#endif - sample->loopStart=16*(loopPos/9); - sample->loopEnd=sample->samples; - sample->loop=true; - sample->loopMode=DIV_SAMPLE_LOOP_FORWARD; - } - len-=2; - if (len==0) { - fclose(f); - BUSY_END; - lastError="BRR sample is empty!"; - delete sample; - return NULL; - } - } else if ((len%9)!=0) { - fclose(f); - BUSY_END; - lastError="possibly corrupt BRR sample!"; - delete sample; - return NULL; - } - } - - if (fread(dataBuf,1,len,f)==0) { - fclose(f); - BUSY_END; - lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); - delete sample; - return NULL; - } - BUSY_END; - return sample; - } - } - -#ifndef HAVE_SNDFILE - lastError="Furnace was not compiled with libsndfile!"; - return NULL; -#else - SF_INFO si; - SFWrapper sfWrap; - memset(&si,0,sizeof(SF_INFO)); - SNDFILE* f=sfWrap.doOpen(path,SFM_READ,&si); - if (f==NULL) { - BUSY_END; - int err=sf_error(NULL); - if (err==SF_ERR_SYSTEM) { - lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); - } else { - lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err)); - } - return NULL; - } - if (si.frames>16777215) { - lastError="this sample is too big! max sample size is 16777215."; - sfWrap.doClose(); - BUSY_END; - return NULL; - } - void* buf=NULL; - sf_count_t sampleLen=sizeof(short); - if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { - logD("sample is 8-bit unsigned"); - buf=new unsigned char[si.channels*si.frames]; - sampleLen=sizeof(unsigned char); - } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { - logD("sample is 32-bit float"); - buf=new float[si.channels*si.frames]; - sampleLen=sizeof(float); - } else { - logD("sample is 16-bit signed"); - buf=new short[si.channels*si.frames]; - sampleLen=sizeof(short); - } - if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { - if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { - logW("sample read size mismatch!"); - } - } else { - if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) { - logW("sample read size mismatch!"); - } - } - DivSample* sample=new DivSample; - int sampleCount=(int)song.sample.size(); - sample->name=stripPath; - - int index=0; - if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { - sample->depth=DIV_SAMPLE_DEPTH_8BIT; - } else { - sample->depth=DIV_SAMPLE_DEPTH_16BIT; - } - sample->init(si.frames); - if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { - for (int i=0; idata8[index++]=averaged; - } - delete[] (unsigned char*)buf; - } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { - for (int i=0; i32767.0) averaged=32767.0; - sample->data16[index++]=averaged; - } - delete[] (float*)buf; - } else { - for (int i=0; idata16[index++]=averaged; - } - delete[] (short*)buf; - } - - sample->rate=si.samplerate; - if (sample->rate<4000) sample->rate=4000; - if (sample->rate>96000) sample->rate=96000; - sample->centerRate=si.samplerate; - - SF_INSTRUMENT inst; - if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE) - { - // There's no documentation on libsndfile detune range, but the code - // implies -50..50. Yet when loading a file you can get a >50 value. - if(inst.detune > 50) - inst.detune = inst.detune - 100; - short pitch = ((0x3c-inst.basenote)*100) + inst.detune; - sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0)); - if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD) - { - sample->loop=true; - sample->loopMode=(DivSampleLoopMode)(inst.loops[0].mode-SF_LOOP_FORWARD); - sample->loopStart=inst.loops[0].start; - sample->loopEnd=inst.loops[0].end; - if(inst.loops[0].end < (unsigned int)sampleCount) - sampleCount=inst.loops[0].end; - } - else - sample->loop=false; - } - - if (sample->centerRate<4000) sample->centerRate=4000; - if (sample->centerRate>64000) sample->centerRate=64000; - sfWrap.doClose(); - BUSY_END; - return sample; -#endif -} - -DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles) { - if (song.sample.size()>=256) { - lastError="too many samples!"; - return NULL; - } - if (channels<1) { - channels=1; - } - if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) { - if (channels!=1) { - channels=1; - } - } - BUSY_BEGIN; - warnings=""; - - const char* pathRedux=strrchr(path,DIR_SEPARATOR); - if (pathRedux==NULL) { - pathRedux=path; - } else { - pathRedux++; - } - String stripPath; - const char* pathReduxEnd=strrchr(pathRedux,'.'); - if (pathReduxEnd==NULL) { - stripPath=pathRedux; - } else { - for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { - stripPath+=*i; - } - } - - size_t len=0; - size_t lenDivided=0; - DivSample* sample=new DivSample; - sample->name=stripPath; - - FILE* f=ps_fopen(path,"rb"); - if (f==NULL) { - BUSY_END; - lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); - delete sample; - return NULL; - } - - if (fseek(f,0,SEEK_END)<0) { - fclose(f); - BUSY_END; - lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); - delete sample; - return NULL; - } - - len=ftell(f); - - if (len==0) { - fclose(f); - BUSY_END; - lastError="file is empty!"; - delete sample; - return NULL; - } - - if (len==(SIZE_MAX>>1)) { - fclose(f); - BUSY_END; - lastError="file is invalid!"; - delete sample; - return NULL; - } - - if (fseek(f,0,SEEK_SET)<0) { - fclose(f); - BUSY_END; - lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); - delete sample; - return NULL; - } - - lenDivided=len/channels; - - unsigned int samples=lenDivided; - switch (depth) { - case DIV_SAMPLE_DEPTH_1BIT: - case DIV_SAMPLE_DEPTH_1BIT_DPCM: - samples=lenDivided*8; - break; - case DIV_SAMPLE_DEPTH_YMZ_ADPCM: - case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: - case DIV_SAMPLE_DEPTH_ADPCM_A: - case DIV_SAMPLE_DEPTH_ADPCM_B: - case DIV_SAMPLE_DEPTH_VOX: - samples=lenDivided*2; - break; - case DIV_SAMPLE_DEPTH_8BIT: - case DIV_SAMPLE_DEPTH_MULAW: - samples=lenDivided; - break; - case DIV_SAMPLE_DEPTH_BRR: - samples=16*((lenDivided+8)/9); - break; - case DIV_SAMPLE_DEPTH_16BIT: - samples=(lenDivided+1)/2; - break; - default: - break; - } - - if (samples>16777215) { - fclose(f); - BUSY_END; - lastError="this sample is too big! max sample size is 16777215."; - delete sample; - return NULL; - } - - sample->rate=32000; - sample->centerRate=32000; - sample->depth=depth; - sample->init(samples); - - unsigned char* buf=new unsigned char[len]; - if (fread(buf,1,len,f)==0) { - fclose(f); - BUSY_END; - lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); - delete[] buf; - delete sample; - return NULL; - } - - fclose(f); - - // import sample - size_t pos=0; - if (depth==DIV_SAMPLE_DEPTH_16BIT) { - for (unsigned int i=0; i=len) break; - if (bigEndian) { - accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0)); - } else { - accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0)); - } - pos+=2; - } - accum/=channels; - sample->data16[i]=accum; - } - } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { - for (unsigned int i=0; i=len) break; - accum+=(signed char)(buf[pos++]^(unsign?0x80:0)); - } - accum/=channels; - sample->data8[i]=accum; - } - } else { - memcpy(sample->getCurBuf(),buf,len); - } - delete[] buf; - - if (swapNibbles) { - unsigned char* b=(unsigned char*)sample->getCurBuf(); - switch (depth) { - case DIV_SAMPLE_DEPTH_1BIT: - case DIV_SAMPLE_DEPTH_1BIT_DPCM: - // reverse bit order - for (unsigned int i=0; igetCurBufLen(); i++) { - b[i]=( - ((b[i]&128)?1:0)| - ((b[i]&64)?2:0)| - ((b[i]&32)?4:0)| - ((b[i]&16)?8:0)| - ((b[i]&8)?16:0)| - ((b[i]&4)?32:0)| - ((b[i]&2)?64:0)| - ((b[i]&1)?128:0) - ); - } - break; - case DIV_SAMPLE_DEPTH_YMZ_ADPCM: - case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: - case DIV_SAMPLE_DEPTH_ADPCM_A: - case DIV_SAMPLE_DEPTH_ADPCM_B: - case DIV_SAMPLE_DEPTH_VOX: - // swap nibbles - for (unsigned int i=0; igetCurBufLen(); i++) { - b[i]=(b[i]<<4)|(b[i]>>4); - } - break; - case DIV_SAMPLE_DEPTH_MULAW: - // Namco to G.711 - // Namco: smmmmxxx - // G.711: sxxxmmmm (^0xff) - for (unsigned int i=0; igetCurBufLen(); i++) { - b[i]=(((b[i]&7)<<4)|(((b[i]>>3)&15)^((b[i]&0x80)?15:0))|(b[i]&0x80))^0xff; - } - break; - default: - break; - } - } - - BUSY_END; - return sample; -} - void DivEngine::delSample(int index) { BUSY_BEGIN; sPreview.sample=-1; @@ -4227,23 +2827,26 @@ void DivEngine::autoPatchbay() { unsigned int outs=disCont[i].dispatch->getOutputCount(); if (outs>16) outs=16; if (outs<2) { + song.patchbay.reserve(DIV_MAX_OUTPUTS); for (unsigned int j=0; jlistAudioDevices(); want.deviceName=getConfString("audioDevice",""); @@ -4749,8 +3355,10 @@ bool DivEngine::initAudioBackend() { if (want.outChans<1) want.outChans=1; if (want.outChans>16) want.outChans=16; + logV("setting callback"); output->setCallback(process,this); + logV("calling init"); if (!output->init(want,got)) { logE("error while initializing audio!"); delete output; @@ -4759,6 +3367,7 @@ bool DivEngine::initAudioBackend() { return false; } + logV("allocating oscBuf..."); for (int i=0; iinitMidi(false)) { midiIns=output->midiIn->listDevices(); midiOuts=output->midiOut->listDevices(); @@ -4797,6 +3407,7 @@ bool DivEngine::initAudioBackend() { } } + logV("initAudioBackend done"); return true; } @@ -4840,6 +3451,13 @@ void DivEngine::preInit() { logI("Furnace version " DIV_VERSION "."); loadConf(); + +#ifdef HAVE_SDL2 + String audioDriver=getConfString("sdlAudioDriver",""); + if (!audioDriver.empty()) { + SDL_SetHint("SDL_HINT_AUDIODRIVER",audioDriver.c_str()); + } +#endif } bool DivEngine::init() { @@ -4877,6 +3495,8 @@ bool DivEngine::init() { haveAudio=true; } + logV("creating blip_buf"); + samp_bb=blip_new(32768); if (samp_bb==NULL) { logE("not enough memory!"); @@ -4890,6 +3510,8 @@ bool DivEngine::init() { metroBuf=new float[8192]; metroBufLen=8192; + + logV("setting blip rate of samp_bb (%f)",got.rate); blip_set_rates(samp_bb,44100,got.rate); diff --git a/src/engine/engine.h b/src/engine/engine.h index 28e8ac7f8..7fb6b5116 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,10 +54,10 @@ #define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; -#define DIV_UNSTABLE +//#define DIV_UNSTABLE -#define DIV_VERSION "dev166" -#define DIV_ENGINE_VERSION 166 +#define DIV_VERSION "0.6pre9" +#define DIV_ENGINE_VERSION 169 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -497,6 +497,7 @@ class DivEngine { void playSub(bool preserveDrift, int goalRow=0); void runMidiClock(int totalCycles=1); void runMidiTime(int totalCycles=1); + bool shallSwitchCores(); void testFunction(); @@ -614,6 +615,8 @@ class DivEngine { void waitAudioFile(); // stop audio file export bool haltAudioFile(); + // return back to playback cores if necessary + void finishAudioFile(); // notify instrument parameter change void notifyInsChange(int ins); // notify wavetable change @@ -973,6 +976,9 @@ class DivEngine { // get macro interpreter DivMacroInt* getMacroInt(int chan); + // get channel panning + unsigned short getChanPan(int chan); + // get sample position DivSamplePos getSamplePos(int chan); diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index dc836f33e..56d3814a7 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -266,6 +266,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { } // finish + ret.reserve(5); ret.push_back(DivROMExportOutput("sbook.bin",sbook)); ret.push_back(DivROMExportOutput("wbook.bin",wbook)); ret.push_back(DivROMExportOutput("sample.bin",sample)); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 9cf94547b..746f59046 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -183,6 +183,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.brokenPortaArp=false; ds.snNoLowPeriods=true; ds.disableSampleMacro=true; + ds.preNoteNoEffect=true; ds.delayBehavior=0; ds.jumpTreatment=2; @@ -341,6 +342,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.insLen=16; } logI("reading instruments (%d)...",ds.insLen); + ds.ins.reserve(ds.insLen); for (int i=0; i0x0b) { ds.waveLen=(unsigned char)reader.readC(); logI("reading wavetables (%d)...",ds.waveLen); + ds.wave.reserve(ds.waveLen); for (int i=0; ilen=(unsigned char)reader.readI(); @@ -837,6 +840,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // it appears this byte stored the YMU759 sample rate ymuSampleRate=reader.readC(); } + ds.sample.reserve(ds.sampleLen); for (int i=0; iordersLen); @@ -2341,6 +2349,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // patchbay unsigned int conns=reader.readI(); + ds.patchbay.reserve(conns); for (unsigned int i=0; i=168) { + ds.preNoteNoEffect=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<5; i++) { reader.readC(); } } @@ -2368,6 +2382,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // grooves unsigned char grooveCount=reader.readC(); + ds.grooves.reserve(grooveCount); for (int i=0; i=95) { + ds.subsong.reserve(numberOfSubSongs); for (int i=0; idepth=DIV_SAMPLE_DEPTH_8BIT; @@ -3364,6 +3384,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } // instrument creation + ds.ins.reserve(insCount); for(int i=0; itype=DIV_INS_AMIGA; @@ -3642,6 +3663,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) { } // load instruments/samples + ds.ins.reserve(ds.insLen); for (int i=0; idepth=DIV_SAMPLE_DEPTH_8BIT; @@ -3972,6 +3999,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { return false; } logD("reading wavetables..."); + ds.wave.reserve(80); for (int i=0; i<80; i++) { DivWavetable* w=new DivWavetable; w->min=0; @@ -4000,6 +4028,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { } } else { // generate preset waves + ds.wave.reserve(48); for (int i=0; i<48; i++) { DivWavetable* w=new DivWavetable; generateFCPresetWave(i,w); @@ -4147,6 +4176,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { // volume sequence ins->std.volMacro.len=0; + ds.ins.reserve(64 - 5); for (int j=5; j<64; j++) { loopMap[j]=ins->std.volMacro.len; if (m.val[j]==0xe1) { // end @@ -4544,6 +4574,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { CHECK_BLOCK_VERSION(4); unsigned char totalSongs=reader.readC(); logV("%d songs:",totalSongs+1); + ds.subsong.reserve(totalSongs); for (int i=0; i<=totalSongs; i++) { String subSongName=reader.readString(); ds.subsong.push_back(new DivSubSong); @@ -5078,12 +5109,14 @@ DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vectorwriteC(song.brokenPortaLegato); - for (int i=0; i<7; i++) { + w->writeC(song.brokenFMOff); + w->writeC(song.preNoteNoEffect); + for (int i=0; i<5; i++) { w->writeC(0); } @@ -5414,6 +5449,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) { w->seek(0,SEEK_END); /// SUBSONGS + subSongPtr.reserve(song.subsong.size() - 1); for (subSongIndex=1; subSongIndextell()); @@ -5475,6 +5511,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) { } /// CHIP FLAGS + sysFlagsPtr.reserve(song.systemLen); for (int i=0; itell()); @@ -5511,6 +5549,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) { } /// WAVETABLE + wavePtr.reserve(song.waveLen); for (int i=0; itell()); @@ -5518,6 +5557,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) { } /// SAMPLE + samplePtr.reserve(song.sampleLen); for (int i=0; itell()); @@ -5525,6 +5565,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) { } /// PATTERN + patPtr.reserve(patsToWrite.size()); for (PatToWrite& i: patsToWrite) { DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); diff --git a/src/engine/fileOpsSample.cpp b/src/engine/fileOpsSample.cpp new file mode 100644 index 000000000..4d4b3a0e8 --- /dev/null +++ b/src/engine/fileOpsSample.cpp @@ -0,0 +1,511 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 "engine.h" +#include "../ta-log.h" +#include "../fileutils.h" +#ifdef HAVE_SNDFILE +#include "sfWrapper.h" +#endif + +DivSample* DivEngine::sampleFromFile(const char* path) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return NULL; + } + BUSY_BEGIN; + warnings=""; + + const char* pathRedux=strrchr(path,DIR_SEPARATOR); + if (pathRedux==NULL) { + pathRedux=path; + } else { + pathRedux++; + } + String stripPath; + const char* pathReduxEnd=strrchr(pathRedux,'.'); + if (pathReduxEnd==NULL) { + stripPath=pathRedux; + } else { + for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { + stripPath+=*i; + } + } + + const char* ext=strrchr(path,'.'); + if (ext!=NULL) { + String extS; + for (; *ext; ext++) { + char i=*ext; + if (i>='A' && i<='Z') { + i+='a'-'A'; + } + extS+=i; + } + if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr + size_t len=0; + DivSample* sample=new DivSample; + sample->name=stripPath; + + FILE* f=ps_fopen(path,"rb"); + if (f==NULL) { + BUSY_END; + lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_END)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + len=ftell(f); + + if (len==0) { + fclose(f); + BUSY_END; + lastError="file is empty!"; + delete sample; + return NULL; + } + + if (len==(SIZE_MAX>>1)) { + fclose(f); + BUSY_END; + lastError="file is invalid!"; + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_SET)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + if (extS==".dmc") { + sample->rate=33144; + sample->centerRate=33144; + sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM; + sample->init(len*8); + } else if (extS==".brr") { + sample->rate=32000; + sample->centerRate=32000; + sample->depth=DIV_SAMPLE_DEPTH_BRR; + sample->init(16*(len/9)); + } else { + fclose(f); + BUSY_END; + lastError="wait... is that right? no I don't think so..."; + delete sample; + return NULL; + } + + unsigned char* dataBuf=sample->dataDPCM; + if (extS==".brr") { + dataBuf=sample->dataBRR; + if ((len%9)==2) { + // read loop position + unsigned short loopPos=0; + logD("BRR file has loop position"); + if (fread(&loopPos,1,2,f)!=2) { + logW("could not read loop position! %s",strerror(errno)); + } else { +#ifdef TA_BIG_ENDIAN + loopPos=(loopPos>>8)|(loopPos<<8); +#endif + sample->loopStart=16*(loopPos/9); + sample->loopEnd=sample->samples; + sample->loop=true; + sample->loopMode=DIV_SAMPLE_LOOP_FORWARD; + } + len-=2; + if (len==0) { + fclose(f); + BUSY_END; + lastError="BRR sample is empty!"; + delete sample; + return NULL; + } + } else if ((len%9)!=0) { + fclose(f); + BUSY_END; + lastError="possibly corrupt BRR sample!"; + delete sample; + return NULL; + } + } + + if (fread(dataBuf,1,len,f)==0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + BUSY_END; + return sample; + } + } + +#ifndef HAVE_SNDFILE + lastError="Furnace was not compiled with libsndfile!"; + return NULL; +#else + SF_INFO si; + SFWrapper sfWrap; + memset(&si,0,sizeof(SF_INFO)); + SNDFILE* f=sfWrap.doOpen(path,SFM_READ,&si); + if (f==NULL) { + BUSY_END; + int err=sf_error(NULL); + if (err==SF_ERR_SYSTEM) { + lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); + } else { + lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err)); + } + return NULL; + } + if (si.frames>16777215) { + lastError="this sample is too big! max sample size is 16777215."; + sfWrap.doClose(); + BUSY_END; + return NULL; + } + void* buf=NULL; + sf_count_t sampleLen=sizeof(short); + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { + logD("sample is 8-bit unsigned"); + buf=new unsigned char[si.channels*si.frames]; + sampleLen=sizeof(unsigned char); + } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + logD("sample is 32-bit float"); + buf=new float[si.channels*si.frames]; + sampleLen=sizeof(float); + } else { + logD("sample is 16-bit signed"); + buf=new short[si.channels*si.frames]; + sampleLen=sizeof(short); + } + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { + logW("sample read size mismatch!"); + } + } else { + if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) { + logW("sample read size mismatch!"); + } + } + DivSample* sample=new DivSample; + int sampleCount=(int)song.sample.size(); + sample->name=stripPath; + + int index=0; + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { + sample->depth=DIV_SAMPLE_DEPTH_8BIT; + } else { + sample->depth=DIV_SAMPLE_DEPTH_16BIT; + } + sample->init(si.frames); + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { + for (int i=0; idata8[index++]=averaged; + } + delete[] (unsigned char*)buf; + } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { + for (int i=0; i32767.0) averaged=32767.0; + sample->data16[index++]=averaged; + } + delete[] (float*)buf; + } else { + for (int i=0; idata16[index++]=averaged; + } + delete[] (short*)buf; + } + + sample->rate=si.samplerate; + if (sample->rate<4000) sample->rate=4000; + if (sample->rate>96000) sample->rate=96000; + sample->centerRate=si.samplerate; + + SF_INSTRUMENT inst; + if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE) + { + // There's no documentation on libsndfile detune range, but the code + // implies -50..50. Yet when loading a file you can get a >50 value. + if(inst.detune > 50) + inst.detune = inst.detune - 100; + short pitch = ((0x3c-inst.basenote)*100) + inst.detune; + sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0)); + if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD) + { + sample->loop=true; + sample->loopMode=(DivSampleLoopMode)(inst.loops[0].mode-SF_LOOP_FORWARD); + sample->loopStart=inst.loops[0].start; + sample->loopEnd=inst.loops[0].end; + if(inst.loops[0].end < (unsigned int)sampleCount) + sampleCount=inst.loops[0].end; + } + else + sample->loop=false; + } + + if (sample->centerRate<4000) sample->centerRate=4000; + if (sample->centerRate>64000) sample->centerRate=64000; + sfWrap.doClose(); + BUSY_END; + return sample; +#endif +} + +DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return NULL; + } + if (channels<1) { + channels=1; + } + if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) { + if (channels!=1) { + channels=1; + } + } + BUSY_BEGIN; + warnings=""; + + const char* pathRedux=strrchr(path,DIR_SEPARATOR); + if (pathRedux==NULL) { + pathRedux=path; + } else { + pathRedux++; + } + String stripPath; + const char* pathReduxEnd=strrchr(pathRedux,'.'); + if (pathReduxEnd==NULL) { + stripPath=pathRedux; + } else { + for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { + stripPath+=*i; + } + } + + size_t len=0; + size_t lenDivided=0; + DivSample* sample=new DivSample; + sample->name=stripPath; + + FILE* f=ps_fopen(path,"rb"); + if (f==NULL) { + BUSY_END; + lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_END)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + len=ftell(f); + + if (len==0) { + fclose(f); + BUSY_END; + lastError="file is empty!"; + delete sample; + return NULL; + } + + if (len==(SIZE_MAX>>1)) { + fclose(f); + BUSY_END; + lastError="file is invalid!"; + delete sample; + return NULL; + } + + if (fseek(f,0,SEEK_SET)<0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); + delete sample; + return NULL; + } + + lenDivided=len/channels; + + unsigned int samples=lenDivided; + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + samples=lenDivided*8; + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + case DIV_SAMPLE_DEPTH_ADPCM_A: + case DIV_SAMPLE_DEPTH_ADPCM_B: + case DIV_SAMPLE_DEPTH_VOX: + samples=lenDivided*2; + break; + case DIV_SAMPLE_DEPTH_8BIT: + case DIV_SAMPLE_DEPTH_MULAW: + samples=lenDivided; + break; + case DIV_SAMPLE_DEPTH_BRR: + samples=16*((lenDivided+8)/9); + break; + case DIV_SAMPLE_DEPTH_16BIT: + samples=(lenDivided+1)/2; + break; + default: + break; + } + + if (samples>16777215) { + fclose(f); + BUSY_END; + lastError="this sample is too big! max sample size is 16777215."; + delete sample; + return NULL; + } + + sample->rate=32000; + sample->centerRate=32000; + sample->depth=depth; + sample->init(samples); + + unsigned char* buf=new unsigned char[len]; + if (fread(buf,1,len,f)==0) { + fclose(f); + BUSY_END; + lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); + delete[] buf; + delete sample; + return NULL; + } + + fclose(f); + + // import sample + size_t pos=0; + if (depth==DIV_SAMPLE_DEPTH_16BIT) { + for (unsigned int i=0; i=len) break; + if (bigEndian) { + accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0)); + } else { + accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0)); + } + pos+=2; + } + accum/=channels; + sample->data16[i]=accum; + } + } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { + for (unsigned int i=0; i=len) break; + accum+=(signed char)(buf[pos++]^(unsign?0x80:0)); + } + accum/=channels; + sample->data8[i]=accum; + } + } else { + memcpy(sample->getCurBuf(),buf,len); + } + delete[] buf; + + if (swapNibbles) { + unsigned char* b=(unsigned char*)sample->getCurBuf(); + switch (depth) { + case DIV_SAMPLE_DEPTH_1BIT: + case DIV_SAMPLE_DEPTH_1BIT_DPCM: + // reverse bit order + for (unsigned int i=0; igetCurBufLen(); i++) { + b[i]=( + ((b[i]&128)?1:0)| + ((b[i]&64)?2:0)| + ((b[i]&32)?4:0)| + ((b[i]&16)?8:0)| + ((b[i]&8)?16:0)| + ((b[i]&4)?32:0)| + ((b[i]&2)?64:0)| + ((b[i]&1)?128:0) + ); + } + break; + case DIV_SAMPLE_DEPTH_YMZ_ADPCM: + case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: + case DIV_SAMPLE_DEPTH_ADPCM_A: + case DIV_SAMPLE_DEPTH_ADPCM_B: + case DIV_SAMPLE_DEPTH_VOX: + // swap nibbles + for (unsigned int i=0; igetCurBufLen(); i++) { + b[i]=(b[i]<<4)|(b[i]>>4); + } + break; + case DIV_SAMPLE_DEPTH_MULAW: + // Namco to G.711 + // Namco: smmmmxxx + // G.711: sxxxmmmm (^0xff) + for (unsigned int i=0; igetCurBufLen(); i++) { + b[i]=(((b[i]&7)<<4)|(((b[i]>>3)&15)^((b[i]&0x80)?15:0))|(b[i]&0x80))^0xff; + } + break; + default: + break; + } + } + + BUSY_END; + return sample; +} diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 3931a6f50..842b88fc2 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -2261,6 +2261,19 @@ void DivInstrument::readFeatureOx(SafeReader& reader, int op, short version) { } break; } + + // <167 TL macro compat + if (macroCode==6 && version<167) { + if (target->open&6) { + for (int j=0; j<2; j++) { + target->val[j]^=0x7f; + } + } else { + for (int j=0; jlen; j++) { + target->val[j]^=0x7f; + } + } + } } READ_FEAT_END; @@ -3319,6 +3332,21 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) { } } + // <167 TL macro compat + if (version<167) { + for (int i=0; i<4; i++) { + if (std.opMacros[i].tlMacro.open&6) { + for (int j=0; j<2; j++) { + std.opMacros[i].tlMacro.val[j]^=0x7f; + } + } else { + for (int j=0; j0) { delay--; if (!linger) had=false; @@ -401,15 +402,13 @@ void DivMacroInt::init(DivInstrument* which) { if (macroSource[i]!=NULL) { macroList[i]->prepare(*macroSource[i],e); // check ADSR mode - if ((macroSource[i]->open&6)==4) { - hasRelease=false; - } else if ((macroSource[i]->open&6)==2) { + if ((macroSource[i]->open&6)==2) { + if (macroSource[i]->val[8]>0) { + hasRelease=true; + } + } else if (macroSource[i]->rellen) { hasRelease=true; - } else { - hasRelease=(macroSource[i]->rellen); } - } else { - hasRelease=false; } } } diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 2be22d1f4..a06d9caf0 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -33,6 +33,10 @@ void* DivDispatch::getChanState(int chan) { return NULL; } +unsigned short DivDispatch::getPan(int chan) { + return 0; +} + DivMacroInt* DivDispatch::getChanMacroInt(int chan) { return NULL; } diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 20110b9db..95a3d7750 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -299,7 +299,7 @@ void DivPlatformArcade::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (!op.enable) { rWrite(baseAddr+ADDR_TL,127); } else if (KVS(i,j)) { @@ -857,6 +857,10 @@ DivMacroInt* DivPlatformArcade::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformArcade::getPan(int ch) { + return (chan[ch].chVolL<<8)|(chan[ch].chVolR); +} + DivDispatchOscBuffer* DivPlatformArcade::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index b5720f197..39ae3705e 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -75,6 +75,7 @@ class DivPlatformArcade: public DivPlatformOPM { void tick(bool sysTick=true); void muteChannel(int ch, bool mute); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); void notifyInsChange(int ins); void notifyInsDeletion(void* ins); void setFlags(const DivConfig& flags); diff --git a/src/engine/platform/c140.cpp b/src/engine/platform/c140.cpp index c9e83338c..ba80284a7 100644 --- a/src/engine/platform/c140.cpp +++ b/src/engine/platform/c140.cpp @@ -344,6 +344,10 @@ DivMacroInt* DivPlatformC140::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformC140::getPan(int ch) { + return (chan[ch].chPanL<<8)|(chan[ch].chPanR); +} + DivDispatchOscBuffer* DivPlatformC140::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/c140.h b/src/engine/platform/c140.h index bb34dac65..1c8ed9079 100644 --- a/src/engine/platform/c140.h +++ b/src/engine/platform/c140.h @@ -74,6 +74,7 @@ class DivPlatformC140: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 4f8745bda..d5eb38a0b 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -167,7 +167,7 @@ void DivPlatformES5506::acquire(short** buf, size_t len) { buf[(o<<1)|1][h]=es5506.rout(o); } for (int i=chanMax; i>=0; i--) { - oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>6; + oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>5; } } } @@ -1057,6 +1057,10 @@ DivMacroInt* DivPlatformES5506::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformES5506::getPan(int ch) { + return ((chan[ch].lVol>>4)<<8)|(chan[ch].rVol>>4); +} + void DivPlatformES5506::reset() { while (!hostIntf32.empty()) hostIntf32.pop(); while (!hostIntf8.empty()) hostIntf8.pop(); diff --git a/src/engine/platform/es5506.h b/src/engine/platform/es5506.h index b7658c52f..07bdb2380 100644 --- a/src/engine/platform/es5506.h +++ b/src/engine/platform/es5506.h @@ -295,6 +295,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf { virtual int dispatch(DivCommand c) override; virtual void* getChanState(int chan) override; virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual unsigned short getPan(int chan) override; virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; virtual unsigned char* getRegisterPool() override; virtual int getRegisterPoolSize() override; diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index 8d3d059c4..32ea4c002 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -155,6 +155,7 @@ class DivPlatformOPN: public DivPlatformFMBase { unsigned int ayDiv; unsigned char csmChan; unsigned char lfoValue; + unsigned char lastExtChPan; unsigned short ssgVol; unsigned short fmVol; bool extSys, useCombo, fbAllOps; @@ -175,6 +176,7 @@ class DivPlatformOPN: public DivPlatformFMBase { ayDiv(a), csmChan(cc), lfoValue(0), + lastExtChPan(3), ssgVol(128), fmVol(256), extSys(isExtSys), diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 59c950d18..741fe2c3c 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -578,6 +578,11 @@ DivMacroInt* DivPlatformGB::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformGB::getPan(int ch) { + unsigned char p=lastPan&(0x11<5) ch=5; + return ((chan[ch].pan&2)<<7)|(chan[ch].pan&1); +} + DivSamplePos DivPlatformGenesis::getSamplePos(int ch) { if (!chan[5].dacMode) return DivSamplePos(); if (ch<5) return DivSamplePos(); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index d618c6892..8c9181dc9 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -106,6 +106,7 @@ class DivPlatformGenesis: public DivPlatformOPN { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + virtual unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index e6a9cd148..a147545cb 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -159,6 +159,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } } rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); + lastExtChPan=opChan[ch].pan; break; } case DIV_CMD_PITCH: { @@ -599,7 +600,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isOpMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { @@ -756,7 +757,7 @@ void DivPlatformGenesisExt::forceIns() { } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (i==2) { - rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(lastExtChPan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } else { rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } @@ -800,6 +801,19 @@ DivMacroInt* DivPlatformGenesisExt::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformGenesisExt::getPan(int ch) { + if (ch==csmChan) return 0; + if (ch>=4+extChanOffs) return DivPlatformGenesis::getPan(ch-3); + if (ch>=extChanOffs) { + if (extMode) { + return ((lastExtChPan&2)<<7)|(lastExtChPan&1); + } else { + return DivPlatformGenesis::getPan(extChanOffs); + } + } + return DivPlatformGenesis::getPan(ch); +} + DivDispatchOscBuffer* DivPlatformGenesisExt::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; @@ -816,6 +830,8 @@ void DivPlatformGenesisExt::reset() { opChan[i].outVol=127; } + lastExtChPan=3; + // channel 3 mode immWrite(0x27,0x40); extMode=true; diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index c668d5104..63112c069 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -34,6 +34,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index d1037988a..c1e314f53 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -78,15 +78,21 @@ void DivPlatformK007232::acquire(short** buf, size_t len) { const signed int rout[2]={(k007232.output(0)*((vol1>>4)&0xf)),(k007232.output(1)*((vol2>>4)&0xf))}; buf[0][h]=(lout[0]+lout[1])<<4; buf[1][h]=(rout[0]+rout[1])<<4; - for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<3; + if (++oscDivider>=8) { + oscDivider=0; + for (int i=0; i<2; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<3; + } } } else { const unsigned char vol=regPool[0xc]; const signed int out[2]={(k007232.output(0)*(vol&0xf)),(k007232.output(1)*((vol>>4)&0xf))}; buf[0][h]=(out[0]+out[1])<<4; - for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<4; + if (++oscDivider>=8) { + oscDivider=0; + for (int i=0; i<2; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<4; + } } } } @@ -424,6 +430,10 @@ DivMacroInt* DivPlatformK007232::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformK007232::getPan(int ch) { + return ((chan[ch].panning&15)<<8)|((chan[ch].panning&0xf0)>>4); +} + DivDispatchOscBuffer* DivPlatformK007232::getOscBuffer(int ch) { return oscBuf[ch]; } @@ -480,7 +490,7 @@ void DivPlatformK007232::setFlags(const DivConfig& flags) { stereo=flags.getBool("stereo",false); for (int i=0; i<2; i++) { chan[i].volumeChanged=true; - oscBuf[i]->rate=rate; + oscBuf[i]->rate=rate/8; } } @@ -571,6 +581,7 @@ int DivPlatformK007232::init(DivEngine* p, int channels, int sugRate, const DivC } sampleMem=new unsigned char[getSampleMemCapacity()]; sampleMemLen=0; + oscDivider=0; setFlags(flags); reset(); diff --git a/src/engine/platform/k007232.h b/src/engine/platform/k007232.h index b1025f574..e3409b31f 100644 --- a/src/engine/platform/k007232.h +++ b/src/engine/platform/k007232.h @@ -68,7 +68,7 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf { bool sampleLoaded[256]; int delay; - unsigned char lastLoop, lastVolume; + unsigned char lastLoop, lastVolume, oscDivider; bool stereo; unsigned char* sampleMem; @@ -85,6 +85,7 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/k053260.cpp b/src/engine/platform/k053260.cpp index 5791d2426..17ff6db48 100644 --- a/src/engine/platform/k053260.cpp +++ b/src/engine/platform/k053260.cpp @@ -370,6 +370,10 @@ DivMacroInt* DivPlatformK053260::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformK053260::getPan(int ch) { + return parent->convertPanLinearToSplit(chan[ch].panning,8,7); +} + DivDispatchOscBuffer* DivPlatformK053260::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/k053260.h b/src/engine/platform/k053260.h index ce531f3d5..27b2a7910 100644 --- a/src/engine/platform/k053260.h +++ b/src/engine/platform/k053260.h @@ -64,6 +64,7 @@ class DivPlatformK053260: public DivDispatch, public k053260_intf { virtual int dispatch(DivCommand c) override; virtual void* getChanState(int chan) override; virtual DivMacroInt* getChanMacroInt(int ch) override; + virtual unsigned short getPan(int chan) override; virtual DivDispatchOscBuffer* getOscBuffer(int chan) override; virtual unsigned char* getRegisterPool() override; virtual int getRegisterPoolSize() override; diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 6e87fea78..c8a34d461 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -238,7 +238,9 @@ void DivPlatformLynx::tick(bool sysTick) { chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; - WRITE_FEEDBACK(i, chan[i].duty.feedback); + if (!chan[i].pcm) { + WRITE_FEEDBACK(i, chan[i].duty.feedback); + } } WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); WRITE_BACKUP( i, chan[i].fd.backup ); @@ -257,9 +259,21 @@ void DivPlatformLynx::tick(bool sysTick) { int DivPlatformLynx::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { + bool prevPCM=chan[c.chan].pcm; DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY); chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample); + if (chan[c.chan].pcm!=prevPCM) { + if (chan[c.chan].pcm) { + WRITE_FEEDBACK(c.chan,0); + WRITE_CONTROL(c.chan,0x18); + WRITE_BACKUP(c.chan,0); + } else { + WRITE_FEEDBACK(c.chan,chan[c.chan].duty.feedback); + WRITE_CONTROL(c.chan,(chan[c.chan].fd.clockDivider|0x18|chan[c.chan].duty.int_feedback7)); + WRITE_BACKUP(c.chan,chan[c.chan].fd.backup); + } + } if (c.value!=DIV_NOTE_NULL) { if (chan[c.chan].pcm) { chan[c.chan].sample=ins->amiga.getSample(c.value); @@ -416,6 +430,10 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformLynx::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivSamplePos DivPlatformLynx::getSamplePos(int ch) { if (ch>=4) return DivSamplePos(); if (!chan[ch].pcm) return DivSamplePos(); diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index c68106dec..e081f7ff7 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index e5de16456..cb0ed05ed 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -282,6 +282,10 @@ DivMacroInt* DivPlatformMSM6258::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformMSM6258::getPan(int ch) { + return ((chan[ch].pan&2)<<7)|(chan[ch].pan&1); +} + DivDispatchOscBuffer* DivPlatformMSM6258::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/msm6258.h b/src/engine/platform/msm6258.h index 6be120c2b..21ff2a9f2 100644 --- a/src/engine/platform/msm6258.h +++ b/src/engine/platform/msm6258.h @@ -62,6 +62,7 @@ class DivPlatformMSM6258: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 5ac493f19..f6b6d062a 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -473,6 +473,11 @@ DivMacroInt* DivPlatformNamcoWSG::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformNamcoWSG::getPan(int ch) { + if (devType!=30) return 0; + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivDispatchOscBuffer* DivPlatformNamcoWSG::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/namcowsg.h b/src/engine/platform/namcowsg.h index 6aaef0952..7d6943323 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -62,6 +62,7 @@ class DivPlatformNamcoWSG: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 6c3fa416d..c8c5c944c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -436,7 +436,7 @@ void DivPlatformOPL::tick(bool sysTick) { } if (m.tl.had) { - op.tl=63-m.tl.val; + op.tl=m.tl.val&63; } if (m.ksl.had) { op.ksl=m.ksl.val; @@ -1564,6 +1564,18 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformOPL::getPan(int ch) { + if (totalOutputs<=1) return 0; + /*if (chan[ch&(~1)].fourOp) { + if (ch&1) { + return ((chan[ch-1].pan&2)<<7)|(chan[ch-1].pan&1); + } else { + return ((chan[ch+1].pan&2)<<7)|(chan[ch+1].pan&1); + } + }*/ + return ((chan[ch].pan&1)<<8)|((chan[ch].pan&2)>>1); +} + DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { if (oplType==759 || chipType==8950) { if (ch>=totalChans+1) return NULL; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index a417b5088..2298f6132 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -114,6 +114,7 @@ class DivPlatformOPL: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 2d283d9a6..1c7dc72e1 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -195,7 +195,7 @@ void DivPlatformOPLL::tick(bool sysTick) { rWrite(0x06+j,(op.sl<<4)|(op.rr)); } if (m.tl.had) { - op.tl=((j==1)?15:63)-m.tl.val; + op.tl=m.tl.val&((j==1)?15:63); if (j==1) { if (i<9) { rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4)); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index b6f43e2da..2daba109e 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -508,6 +508,10 @@ DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformPCE::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivSamplePos DivPlatformPCE::getSamplePos(int ch) { if (ch>=6) return DivSamplePos(); if (!chan[ch].pcm) return DivSamplePos(); diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index baca77701..f989034af 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -82,6 +82,7 @@ class DivPlatformPCE: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 3a00e431a..1ddd22dae 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -229,7 +229,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { } else { output=output*chan[0].vol*chan[0].envVol/16384; } - oscBuf->data[oscBuf->needle++]=output>>1; + oscBuf->data[oscBuf->needle++]=((output>>depthScale)<>1; if (outStereo) { buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<>(depthScale+8))<=1) return DivSamplePos(); return DivSamplePos( diff --git a/src/engine/platform/pcmdac.h b/src/engine/platform/pcmdac.h index 8ef10149c..17513690a 100644 --- a/src/engine/platform/pcmdac.h +++ b/src/engine/platform/pcmdac.h @@ -78,6 +78,7 @@ class DivPlatformPCMDAC: public DivDispatch { void muteChannel(int ch, bool mute); int getOutputCount(); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); void setFlags(const DivConfig& flags); void notifyInsChange(int ins); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index d7f908f5b..5c98990b4 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -623,6 +623,10 @@ DivMacroInt* DivPlatformQSound::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformQSound::getPan(int ch) { + return parent->convertPanLinearToSplit(chan[ch].panning,8,32); +} + DivDispatchOscBuffer* DivPlatformQSound::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/qsound.h b/src/engine/platform/qsound.h index aff53f679..6daccc120 100644 --- a/src/engine/platform/qsound.h +++ b/src/engine/platform/qsound.h @@ -66,6 +66,7 @@ class DivPlatformQSound: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 84522c74f..9319de5d3 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -322,6 +322,10 @@ DivMacroInt* DivPlatformRF5C68::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformRF5C68::getPan(int ch) { + return ((chan[ch].panning&15)<<8)|((chan[ch].panning&0xf0)>>4); +} + DivDispatchOscBuffer* DivPlatformRF5C68::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/rf5c68.h b/src/engine/platform/rf5c68.h index 4ba40318c..9c706ccd5 100644 --- a/src/engine/platform/rf5c68.h +++ b/src/engine/platform/rf5c68.h @@ -59,6 +59,7 @@ class DivPlatformRF5C68: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 803a6cad3..b1ba48826 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -365,6 +365,10 @@ DivMacroInt* DivPlatformSAA1099::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformSAA1099::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivDispatchOscBuffer* DivPlatformSAA1099::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/saa.h b/src/engine/platform/saa.h index ffd79db72..36db57cb7 100644 --- a/src/engine/platform/saa.h +++ b/src/engine/platform/saa.h @@ -79,6 +79,7 @@ class DivPlatformSAA1099: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index b9af8a912..5e584e016 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -394,6 +394,10 @@ DivMacroInt* DivPlatformSegaPCM::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformSegaPCM::getPan(int ch) { + return (chan[ch].chPanL<<8)|chan[ch].chPanR; +} + DivSamplePos DivPlatformSegaPCM::getSamplePos(int ch) { if (ch>=16) return DivSamplePos(); if (chan[ch].pcm.sample<0 || chan[ch].pcm.sample>=parent->song.sampleLen) return DivSamplePos(); diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index 067054fe1..639d875b4 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -89,6 +89,7 @@ class DivPlatformSegaPCM: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 76aa62fcb..7aa0455ca 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -452,6 +452,12 @@ DivMacroInt* DivPlatformSMS::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformSMS::getPan(int ch) { + if (!stereo) return 0; + unsigned char p=lastPan&(0x11<=8) return DivSamplePos(); if (!chan[ch].active) return DivSamplePos(); diff --git a/src/engine/platform/snes.h b/src/engine/platform/snes.h index cec51c0c1..a799c7a5d 100644 --- a/src/engine/platform/snes.h +++ b/src/engine/platform/snes.h @@ -100,6 +100,7 @@ class DivPlatformSNES: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 3c591cdaf..0aae51a8c 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -458,6 +458,10 @@ DivMacroInt* DivPlatformSoundUnit::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformSoundUnit::getPan(int ch) { + return parent->convertPanLinearToSplit(chan[ch].pan,8,255); +} + DivDispatchOscBuffer* DivPlatformSoundUnit::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/su.h b/src/engine/platform/su.h index d83ae4777..99b784df3 100644 --- a/src/engine/platform/su.h +++ b/src/engine/platform/su.h @@ -105,6 +105,7 @@ class DivPlatformSoundUnit: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 04039e6e5..4e5fb4cca 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -476,6 +476,10 @@ DivMacroInt* DivPlatformSwan::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformSwan::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 72ddae394..1e0fbeef7 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -62,6 +62,7 @@ class DivPlatformSwan: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/t6w28.cpp b/src/engine/platform/t6w28.cpp index 90140da99..f2dfe7120 100644 --- a/src/engine/platform/t6w28.cpp +++ b/src/engine/platform/t6w28.cpp @@ -300,6 +300,10 @@ DivMacroInt* DivPlatformT6W28::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformT6W28::getPan(int ch) { + return (chan[ch].panL<<8)|chan[ch].panR; +} + DivDispatchOscBuffer* DivPlatformT6W28::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/t6w28.h b/src/engine/platform/t6w28.h index 33c03a886..4bb3cda2a 100644 --- a/src/engine/platform/t6w28.h +++ b/src/engine/platform/t6w28.h @@ -63,6 +63,7 @@ class DivPlatformT6W28: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 6e70895b2..52f71aedc 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -206,7 +206,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0x40:0)|(chan[i].chVolR<<7)); if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -223,7 +223,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { } if (chan[i].std.fb.had) { chan[i].state.fb=chan[i].std.fb.val; - immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0x40:0)|(chan[i].chVolR<<7)); } if (chan[i].std.fms.had) { chan[i].state.fms=chan[i].std.fms.val; @@ -262,7 +262,7 @@ void DivPlatformTX81Z::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isMuted[i] || !op.enable) { rWrite(baseAddr+ADDR_TL,127); } else { @@ -965,6 +965,10 @@ DivMacroInt* DivPlatformTX81Z::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformTX81Z::getPan(int ch) { + return (chan[ch].chVolL<<8)|(chan[ch].chVolR); +} + DivDispatchOscBuffer* DivPlatformTX81Z::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index d0bc759c1..c14bc0117 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -65,6 +65,7 @@ class DivPlatformTX81Z: public DivPlatformOPM { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index 9edaf2db4..588676aff 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -419,6 +419,10 @@ DivMacroInt* DivPlatformVB::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformVB::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivDispatchOscBuffer* DivPlatformVB::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/vb.h b/src/engine/platform/vb.h index 2efcdd1b8..2282f62fe 100644 --- a/src/engine/platform/vb.h +++ b/src/engine/platform/vb.h @@ -69,6 +69,7 @@ class DivPlatformVB: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index fa0446ca6..6d97c9c4e 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -444,6 +444,10 @@ DivMacroInt* DivPlatformVERA::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformVERA::getPan(int ch) { + return ((chan[ch].pan&1)<<8)|((chan[ch].pan&2)>>1); +} + DivDispatchOscBuffer* DivPlatformVERA::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 227512a73..515e17e56 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -64,6 +64,7 @@ class DivPlatformVERA: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 29601ae92..d1422439e 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -863,6 +863,11 @@ DivMacroInt* DivPlatformX1_010::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformX1_010::getPan(int ch) { + if (!stereo) return 0; + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + DivDispatchOscBuffer* DivPlatformX1_010::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index a3af7b291..cc698b650 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -131,6 +131,7 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 02e92e191..2e805f3cd 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -402,7 +402,7 @@ void DivPlatformYM2203::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isMuted[i] || !op.enable) { rWrite(baseAddr+ADDR_TL,127); } else { diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 4791d513e..349c8a820 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -502,7 +502,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isOpMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 183533f81..93c09fd67 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -606,7 +606,7 @@ void DivPlatformYM2608::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isMuted[i] || !op.enable) { rWrite(baseAddr+ADDR_TL,127); } else { @@ -1461,6 +1461,11 @@ DivMacroInt* DivPlatformYM2608::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformYM2608::getPan(int ch) { + if (ch>=psgChanOffs && ch=4+extChanOffs) return DivPlatformYM2608::getPan(ch-3); + if (ch>=extChanOffs) { + if (extMode) { + return ((lastExtChPan&2)<<7)|(lastExtChPan&1); + } else { + return DivPlatformYM2608::getPan(extChanOffs); + } + } + return DivPlatformYM2608::getPan(ch); +} + DivDispatchOscBuffer* DivPlatformYM2608Ext::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; @@ -766,7 +779,9 @@ void DivPlatformYM2608Ext::reset() { opChan[i].outVol=127; } - // channel 2 mode + lastExtChPan=3; + + // channel 3 mode immWrite(0x27,0x40); extMode=true; } diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index c9348b48f..0c9c1a418 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -33,6 +33,7 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 5a1d80b58..b7dcdb231 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -539,7 +539,7 @@ void DivPlatformYM2610::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isMuted[i] || !op.enable) { rWrite(baseAddr+ADDR_TL,127); } else { @@ -1421,6 +1421,11 @@ DivMacroInt* DivPlatformYM2610::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformYM2610::getPan(int ch) { + if (ch>=psgChanOffs && ch=psgChanOffs && ch=4+extChanOffs) return DivPlatformYM2610B::getPan(ch-3); + if (ch>=extChanOffs) { + if (extMode) { + return ((lastExtChPan&2)<<7)|(lastExtChPan&1); + } else { + return DivPlatformYM2610B::getPan(extChanOffs); + } + } + return DivPlatformYM2610B::getPan(ch); +} + DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { if (ch>=(extChanOffs+4)) return oscBuf[ch-3]; if (ch<(extChanOffs+1)) return oscBuf[ch]; @@ -756,7 +769,9 @@ void DivPlatformYM2610BExt::reset() { opChan[i].outVol=127; } - // channel 2 mode + lastExtChPan=3; + + // channel 3 mode immWrite(0x27,0x40); extMode=true; } diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index b14ba99c4..024119cff 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -33,6 +33,7 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 3c18a6e4c..f0ab46990 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -152,6 +152,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { } } rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); + lastExtChPan=opChan[ch].pan; break; } case DIV_CMD_PITCH: { @@ -544,7 +545,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } if (m.tl.had) { - op.tl=127-m.tl.val; + op.tl=m.tl.val; if (isOpMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { @@ -695,7 +696,7 @@ void DivPlatformYM2610Ext::forceIns() { } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (i==extChanOffs) { - rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(lastExtChPan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } else { rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } @@ -740,6 +741,18 @@ DivMacroInt* DivPlatformYM2610Ext::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformYM2610Ext::getPan(int ch) { + if (ch>=4+extChanOffs) return DivPlatformYM2610::getPan(ch-3); + if (ch>=extChanOffs) { + if (extMode) { + return ((lastExtChPan&2)<<7)|(lastExtChPan&1); + } else { + return DivPlatformYM2610::getPan(extChanOffs); + } + } + return DivPlatformYM2610::getPan(ch); +} + DivDispatchOscBuffer* DivPlatformYM2610Ext::getOscBuffer(int ch) { if (ch>=(extChanOffs+4)) return oscBuf[ch-3]; if (ch<(extChanOffs+1)) return oscBuf[ch]; @@ -756,6 +769,8 @@ void DivPlatformYM2610Ext::reset() { opChan[i].outVol=127; } + lastExtChPan=3; + // channel 2 mode immWrite(0x27,0x40); extMode=true; diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 2ce887c36..f860a36cd 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -33,6 +33,7 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index 496f35686..12f2251da 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -359,6 +359,10 @@ DivMacroInt* DivPlatformYMZ280B::getChanMacroInt(int ch) { return &chan[ch].std; } +unsigned short DivPlatformYMZ280B::getPan(int ch) { + return parent->convertPanLinearToSplit(chan[ch].panning,8,15); +} + DivDispatchOscBuffer* DivPlatformYMZ280B::getOscBuffer(int ch) { return oscBuf[ch]; } diff --git a/src/engine/platform/ymz280b.h b/src/engine/platform/ymz280b.h index 14a67dc98..913b8ec6e 100644 --- a/src/engine/platform/ymz280b.h +++ b/src/engine/platform/ymz280b.h @@ -59,6 +59,7 @@ class DivPlatformYMZ280B: public DivDispatch { int dispatch(DivCommand c); void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f379b7219..7483a972a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1164,7 +1164,11 @@ void DivEngine::nextRow() { } if (haltOn==DIV_HALT_PATTERN) halted=true; } else if (playing) if (++curRow>=curSubSong->patLen) { - nextOrder(); + if (shallStopSched) { + curRow=curSubSong->patLen-1; + } else { + nextOrder(); + } if (haltOn==DIV_HALT_PATTERN) halted=true; } @@ -1208,8 +1212,26 @@ void DivEngine::nextRow() { if (disCont[dispatchOfChan[i]].dispatch!=NULL) { wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote(); if (wantPreNote) { + bool doPreparePreNote=true; int addition=0; + for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { + doPreparePreNote=false; + break; + } + if (pat->data[curRow][4+(j<<1)]==0x06) { + doPreparePreNote=false; + break; + } + if (pat->data[curRow][4+(j<<1)]==0xea) { + if (pat->data[curRow][5+(j<<1)]>0) { + doPreparePreNote=false; + break; + } + } + } if (pat->data[curRow][4+(j<<1)]==0xed) { if (pat->data[curRow][5+(j<<1)]>0) { addition=pat->data[curRow][5+(j<<1)]&255; @@ -1217,7 +1239,7 @@ void DivEngine::nextRow() { } } } - dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks+addition)); + if (doPreparePreNote) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks+addition)); } } diff --git a/src/engine/song.h b/src/engine/song.h index 803f87360..f05100798 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -375,6 +375,7 @@ struct DivSong { bool patchbayAuto; bool brokenPortaLegato; bool brokenFMOff; + bool preNoteNoEffect; std::vector ins; std::vector wave; @@ -493,7 +494,8 @@ struct DivSong { oldArpStrategy(false), patchbayAuto(true), brokenPortaLegato(false), - brokenFMOff(false) { + brokenFMOff(false), + preNoteNoEffect(false) { for (int i=0; irunExportThread(); +} + +bool DivEngine::isExporting() { + return exporting; +} + +#ifdef HAVE_SNDFILE +void DivEngine::runExportThread() { + size_t fadeOutSamples=got.rate*exportFadeOut; + size_t curFadeOutSample=0; + bool isFadingOut=false; + + switch (exportMode) { + case DIV_EXPORT_MODE_ONE: { + SNDFILE* sf; + SF_INFO si; + SFWrapper sfWrap; + si.samplerate=got.rate; + si.channels=2; + si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + + sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si); + if (sf==NULL) { + logE("could not open file for writing! (%s)",sf_strerror(NULL)); + exporting=false; + return; + } + + float* outBuf[3]; + outBuf[0]=new float[EXPORT_BUFSIZE]; + outBuf[1]=new float[EXPORT_BUFSIZE]; + outBuf[2]=new float[EXPORT_BUFSIZE*2]; + + // take control of audio output + deinitAudioBackend(); + playSub(false); + + logI("rendering to file..."); + + while (playing) { + size_t total=0; + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int i=0; i<(int)totalProcessed; i++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul; + outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul; + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i])); + outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i])); + if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { + logE("error: failed to write entire buffer!"); + break; + } + } + + delete[] outBuf[0]; + delete[] outBuf[1]; + delete[] outBuf[2]; + + if (sfWrap.doClose()!=0) { + logE("could not close audio file!"); + } + exporting=false; + + if (initAudioBackend()) { + for (int i=0; isetRun(true)) { + logE("error while activating audio!"); + } + } + logI("done!"); + break; + } + case DIV_EXPORT_MODE_MANY_SYS: { + SNDFILE* sf[DIV_MAX_CHIPS]; + SF_INFO si[DIV_MAX_CHIPS]; + String fname[DIV_MAX_CHIPS]; + SFWrapper sfWrap[DIV_MAX_CHIPS]; + for (int i=0; igetOutputCount(); + si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + } + + for (int i=0; igetOutputCount()]; + } + + // take control of audio output + deinitAudioBackend(); + playSub(false); + + logI("rendering to files..."); + + while (playing) { + size_t total=0; + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int j=0; j<(int)totalProcessed; j++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + for (int i=0; i=fadeOutSamples) { + playing=false; + break; + } + } else { + for (int i=0; i-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + for (int i=0; isetRun(true)) { + logE("error while activating audio!"); + } + } + logI("done!"); + break; + } + case DIV_EXPORT_MODE_MANY_CHAN: { + // take control of audio output + deinitAudioBackend(); + + float* outBuf[3]; + outBuf[0]=new float[EXPORT_BUFSIZE]; + outBuf[1]=new float[EXPORT_BUFSIZE]; + outBuf[2]=new float[EXPORT_BUFSIZE*2]; + int loopCount=remainingLoops; + + logI("rendering to files..."); + + for (int i=0; imuteChannel(dispatchChanOfChan[j],isMuted[j]); + } + } + + curOrder=0; + prevOrder=0; + curFadeOutSample=0; + lastLoopPos=-1; + totalLoops=0; + isFadingOut=false; + if (exportFadeOut<=0.01) { + remainingLoops=loopCount; + } else { + remainingLoops=-1; + } + playSub(false); + + while (playing) { + size_t total=0; + nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); + if (totalProcessed>EXPORT_BUFSIZE) { + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; + } + for (int j=0; j<(int)totalProcessed; j++) { + total++; + if (isFadingOut) { + double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); + outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]))*mul; + outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]))*mul; + if (++curFadeOutSample>=fadeOutSamples) { + playing=false; + break; + } + } else { + outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j])); + outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j])); + if (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { + logE("error: failed to write entire buffer!"); + break; + } + } + + if (sfWrap.doClose()!=0) { + logE("could not close audio file!"); + } + + if (getChannelType(i)==5) { + i++; + while (true) { + if (i>=chans) break; + if (getChannelType(i)!=5) break; + i++; + } + i--; + } + + if (stopExport) break; + } + exporting=false; + + delete[] outBuf[0]; + delete[] outBuf[1]; + delete[] outBuf[2]; + + for (int i=0; imuteChannel(dispatchChanOfChan[i],false); + } + } + + if (initAudioBackend()) { + for (int i=0; isetRun(true)) { + logE("error while activating audio!"); + } + } + logI("done!"); + break; + } + } + + stopExport=false; +} +#else +void DivEngine::runExportThread() { +} +#endif + +bool DivEngine::shallSwitchCores() { + // TODO: detect whether we should + return true; +} + +bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) { +#ifndef HAVE_SNDFILE + logE("Furnace was not compiled with libsndfile. cannot export!"); + return false; +#else + exportPath=path; + exportMode=mode; + exportFadeOut=fadeOutTime; + if (exportMode!=DIV_EXPORT_MODE_ONE) { + // remove extension + String lowerCase=exportPath; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + size_t extPos=lowerCase.rfind(".wav"); + if (extPos!=String::npos) { + exportPath=exportPath.substr(0,extPos); + } + } + exporting=true; + stopExport=false; + stop(); + repeatPattern=false; + setOrder(0); + if (exportFadeOut<=0.01) { + remainingLoops=loops; + } else { + remainingLoops=-1; + } + + if (shallSwitchCores()) { + bool isMutedBefore[DIV_MAX_CHANS]; + memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); + quitDispatch(); + initDispatch(true); + renderSamplesP(); + for (int i=0; ijoin(); + } +} + +bool DivEngine::haltAudioFile() { + stopExport=true; + stop(); + waitAudioFile(); + finishAudioFile(); + return true; +} + +void DivEngine::finishAudioFile() { + if (shallSwitchCores()) { + bool isMutedBefore[DIV_MAX_CHANS]; + memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); + quitDispatch(); + initDispatch(false); + renderSamplesP(); + for (int i=0; i256) width=256; } -void DivWaveSynth::changeWave1(int num) { +#define SHALL_UPDATE_OUT (!state.enabled || force || (state.enabled && effectOnlyAltersOutput(state.effect))) + +void DivWaveSynth::changeWave1(int num, bool force) { DivWavetable* w1=e->getWave(num); if (width<1) return; for (int i=0; imax<1 || w1->len<1) { wave1[i]=0; - output[i]=0; + if (SHALL_UPDATE_OUT) output[i]=0; } else { int data=w1->data[i*w1->len/width]*height/w1->max; if (data<0) data=0; if (data>height) data=height; wave1[i]=data; - output[i]=data; + if (SHALL_UPDATE_OUT) output[i]=data; } } first=true; @@ -280,9 +298,9 @@ void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { divCounter=0; subDivCounter=0; - changeWave1(state.wave1); + changeWave1(state.wave1,true); changeWave2(state.wave2); - tick(true); + //tick(true); // ??? first=true; } } diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index 81a3cbe19..21b6055ff 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -55,8 +55,9 @@ class DivWaveSynth { /** * change the first wave. * @param num wavetable number. + * @param force whether to force overwriting the current wave. */ - void changeWave1(int num); + void changeWave1(int num, bool force=false); /** * change the second wave. * @param num wavetable number. diff --git a/src/gui/about.cpp b/src/gui/about.cpp index c111be193..2151f5c61 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -203,6 +203,7 @@ const char* aboutLine[]={ "", "greetings to:", "NEOART Costa Rica", + "Xenium Demoparty", "all members of Deflers of Noice!", "", "copyright © 2021-2023 tildearrow", diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index aa7c91b5b..5ca97f479 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -448,6 +448,7 @@ void FurnaceGUI::drawChanOsc() { float maxLevel=-1.0f; float dcOff=0.0f; unsigned short needlePos=buf->needle; + //unsigned short needlePosOrig=needlePos; for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; } @@ -475,10 +476,7 @@ void FurnaceGUI::drawChanOsc() { } chanOscPitch[ch]=(float)point/32.0f; - /* - String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); - dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - */ + needlePos-=displaySize; for (unsigned short i=0; i=needlePosOrig)?"WARN":"OK"); + //dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); } ImU32 color=ImGui::GetColorU32(chanOscColor); if (chanOscUseGrad) { diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 0010fc899..efa88feec 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -184,6 +184,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre5"); } + ImGui::Checkbox("Pre-note does not take effects into consideration",&e->song.preNoteNoEffect); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6pre9"); + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem(".mod import")) { diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 15f9e53ff..da2028454 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -374,7 +374,9 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) { } void FurnaceGUI::moveCursorTop(bool select) { - finishSelection(); + if (!select) { + finishSelection(); + } curNibble=false; if (cursor.y==0) { DETERMINE_FIRST; @@ -384,16 +386,18 @@ void FurnaceGUI::moveCursorTop(bool select) { } else { cursor.y=0; } - selStart=cursor; if (!select) { - selEnd=cursor; + selStart=cursor; } + selEnd=cursor; e->setMidiBaseChan(cursor.xCoarse); updateScroll(cursor.y); } void FurnaceGUI::moveCursorBottom(bool select) { - finishSelection(); + if (!select) { + finishSelection(); + } curNibble=false; if (cursor.y==e->curSubSong->patLen-1) { DETERMINE_LAST; diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index b373f72af..1506c5ff3 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -105,8 +105,10 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) { bool insPressed=ImGui::IsItemActivated(); if (insReleased || (!insListDir && insPressed)) { curIns=i; - wavePreviewInit=true; - updateFMPreview=true; + if (!insReleased || insListDir) { + wavePreviewInit=true; + updateFMPreview=true; + } lastAssetType=0; if (settings.insFocusesPattern && patternOpen) nextWindow=GUI_WINDOW_PATTERN; @@ -892,15 +894,11 @@ void FurnaceGUI::drawSampleList(bool asChild) { doAction(GUI_ACTION_SAMPLE_LIST_PREVIEW); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Preview"); + ImGui::SetTooltip("Preview (right click to stop)"); } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSampleL")) { + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { doAction(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Stop preview"); - } ImGui::SameLine(); pushDestColor(); if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 9d59e44d2..b3c07b071 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -308,6 +308,21 @@ void FurnaceGUI::drawDebug() { } ImGui::TreePop(); } + if (ImGui::TreeNode("Do Action")) { + char bindID[1024]; + for (int j=0; j* ok, std::vector (*errorOutput)=true; break; default: - logE("NFD unknown return code %d!\n",ret); + logE("NFD unknown return code %d!\n",(int)ret); break; } (*ok)=true; diff --git a/src/gui/fmPreview.cpp b/src/gui/fmPreview.cpp index eaf449ed9..5586d1c20 100644 --- a/src/gui/fmPreview.cpp +++ b/src/gui/fmPreview.cpp @@ -20,8 +20,14 @@ #define _USE_MATH_DEFINES #include "gui.h" #include "../../extern/opn/ym3438.h" +#include "../../extern/opm/opm.h" +#include "../../extern/opl/opl3.h" +extern "C" { +#include "../../extern/Nuked-OPLL/opll.h" +} +#include "../engine/platform/sound/ymfm/ymfm_opz.h" -#define FM_WRITE(addr,val) \ +#define OPN_WRITE(addr,val) \ OPN2_Write((ym3438_t*)fmPreviewOPN,0,(addr)); \ do { \ OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \ @@ -29,22 +35,23 @@ OPN2_Write((ym3438_t*)fmPreviewOPN,1,(val)); \ do { \ OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \ - } while (((ym3438_t*)fmPreviewOPN)->write_busy); \ + } while (((ym3438_t*)fmPreviewOPN)->write_busy); const unsigned char dtTableFMP[8]={ 7,6,5,0,1,2,3,4 }; -void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) { +void FurnaceGUI::renderFMPreviewOPN(const DivInstrumentFM& params, int pos) { if (fmPreviewOPN==NULL) { fmPreviewOPN=new ym3438_t; + pos=0; } short out[2]; int aOut=0; bool mult0=false; if (pos==0) { - OPN2_Reset((ym3438_t*)fmPreviewOPN); + OPN2_Reset((ym3438_t*)fmPreviewOPN); OPN2_SetChipType((ym3438_t*)fmPreviewOPN,ym3438_mode_opn); // set params @@ -57,19 +64,19 @@ void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) { for (int i=0; i<4; i++) { const DivInstrumentFM::Operator& op=params.op[i]; unsigned short baseAddr=i*4; - FM_WRITE(baseAddr+0x40,op.tl); - FM_WRITE(baseAddr+0x30,(op.mult&15)|(dtTableFMP[op.dt&7]<<4)); - FM_WRITE(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); - FM_WRITE(baseAddr+0x60,(op.dr&31)|(op.am<<7)); - FM_WRITE(baseAddr+0x70,op.d2r&31); - FM_WRITE(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); - FM_WRITE(baseAddr+0x90,op.ssgEnv&15); + OPN_WRITE(baseAddr+0x40,op.tl); + OPN_WRITE(baseAddr+0x30,(op.mult&15)|(dtTableFMP[op.dt&7]<<4)); + OPN_WRITE(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + OPN_WRITE(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + OPN_WRITE(baseAddr+0x70,op.d2r&31); + OPN_WRITE(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + OPN_WRITE(baseAddr+0x90,op.ssgEnv&15); } - FM_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3)); - FM_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4)); - FM_WRITE(0xa4,mult0?0x1c:0x14); // frequency - FM_WRITE(0xa0,0); - FM_WRITE(0x28,0xf0); // key on + OPN_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3)); + OPN_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4)); + OPN_WRITE(0xa4,mult0?0x1c:0x14); // frequency + OPN_WRITE(0xa0,0); + OPN_WRITE(0x28,0xf0); // key on } // render @@ -84,3 +91,270 @@ void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) { fmPreview[i]=aOut; } } + +#define OPM_WRITE(addr,val) \ + OPM_Write((opm_t*)fmPreviewOPM,0,(addr)); \ + do { \ + OPM_Clock((opm_t*)fmPreviewOPM,out,NULL,NULL,NULL); \ + OPM_Clock((opm_t*)fmPreviewOPM,out,NULL,NULL,NULL); \ + } while (((opm_t*)fmPreviewOPM)->write_busy); \ + OPM_Write((opm_t*)fmPreviewOPM,1,(val)); \ + do { \ + OPM_Clock((opm_t*)fmPreviewOPM,out,NULL,NULL,NULL); \ + OPM_Clock((opm_t*)fmPreviewOPM,out,NULL,NULL,NULL); \ + } while (((opm_t*)fmPreviewOPM)->write_busy); + +void FurnaceGUI::renderFMPreviewOPM(const DivInstrumentFM& params, int pos) { + if (fmPreviewOPM==NULL) { + fmPreviewOPM=new opm_t; + pos=0; + } + int out[2]; + int aOut=0; + bool mult0=false; + + if (pos==0) { + OPM_Reset((opm_t*)fmPreviewOPM); + + // set params + for (int i=0; i<4; i++) { + if ((params.op[i].mult&15)==0) { + mult0=true; + break; + } + } + for (int i=0; i<4; i++) { + const DivInstrumentFM::Operator& op=params.op[i]; + unsigned short baseAddr=i*8; + OPM_WRITE(baseAddr+0x40,(op.mult&15)|(dtTableFMP[op.dt&7]<<4)); + OPM_WRITE(baseAddr+0x60,op.tl); + OPM_WRITE(baseAddr+0x80,(op.ar&31)|(op.rs<<6)); + OPM_WRITE(baseAddr+0xa0,(op.dr&31)|(op.am<<7)); + OPM_WRITE(baseAddr+0xc0,(op.d2r&31)|(op.dt2<<6)); + OPM_WRITE(baseAddr+0xe0,(op.rr&15)|(op.sl<<4)); + } + OPM_WRITE(0x20,(params.alg&7)|((params.fb&7)<<3)|0xc0); + OPM_WRITE(0x38,((params.fms&7)<<4)|(params.ams&3)); + OPM_WRITE(0x28,mult0?0x39:0x29); // frequency + OPM_WRITE(0x30,0xe6); + OPM_WRITE(0x08,0x78); // key on + } + + // render + for (int i=0; i32767) aOut=32767; + fmPreview[i]=aOut; + } +} + +#define OPLL_WRITE(addr,val) \ + OPLL_Write((opll_t*)fmPreviewOPLL,0,(addr)); \ + for (int _i=0; _i<3; _i++) { \ + OPLL_Clock((opll_t*)fmPreviewOPLL,out); \ + } \ + OPLL_Write((opll_t*)fmPreviewOPLL,1,(val)); \ + for (int _i=0; _i<21; _i++) { \ + OPLL_Clock((opll_t*)fmPreviewOPLL,out); \ + } + +void FurnaceGUI::renderFMPreviewOPLL(const DivInstrumentFM& params, int pos) { + if (fmPreviewOPLL==NULL) { + fmPreviewOPLL=new opm_t; + pos=0; + } + int out[2]; + int aOut=0; + bool mult0=false; + + if (pos==0) { + OPLL_Reset((opll_t*)fmPreviewOPLL,opll_type_ym2413); + + // set params + const DivInstrumentFM::Operator& mod=params.op[0]; + const DivInstrumentFM::Operator& car=params.op[1]; + if (params.opllPreset==0) { + for (int i=0; i<2; i++) { + if ((params.op[i].mult&15)==0) { + mult0=true; + break; + } + } + OPLL_WRITE(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); + OPLL_WRITE(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); + OPLL_WRITE(0x02,(mod.ksl<<6)|(mod.tl&63)); + OPLL_WRITE(0x03,(car.ksl<<6)|((params.fms&1)<<4)|((params.ams&1)<<3)|(params.fb&7)); + OPLL_WRITE(0x04,(mod.ar<<4)|(mod.dr)); + OPLL_WRITE(0x05,(car.ar<<4)|(car.dr)); + OPLL_WRITE(0x06,(mod.sl<<4)|(mod.rr)); + OPLL_WRITE(0x07,(car.sl<<4)|(car.rr)); + } + OPLL_WRITE(0x10,0); + OPLL_WRITE(0x30,(params.opllPreset<<4)|(car.tl&15)); + OPLL_WRITE(0x20,(params.alg?0x20:0)|(mult0?0x15:0x13)); + } + + // render + for (int i=0; i32767) aOut=32767; + fmPreview[i]=aOut; + } +} + +#define OPL_WRITE(addr,val) \ + OPL3_WriteReg((opl3_chip*)fmPreviewOPL,(addr),(val)); \ + OPL3_Generate4Ch((opl3_chip*)fmPreviewOPL,out); + +const unsigned char lPreviewSlots[4]={ + 0, 3, 8, 11 +}; + +const unsigned char lOpMap[4]={ + 0, 2, 1, 3 +}; + +void FurnaceGUI::renderFMPreviewOPL(const DivInstrumentFM& params, int pos) { + if (fmPreviewOPL==NULL) { + fmPreviewOPL=new opl3_chip; + pos=0; + } + short out[4]; + bool mult0=false; + + if (pos==0) { + OPL3_Reset((opl3_chip*)fmPreviewOPL,49716); + + // set params + int ops=(params.ops==4)?4:2; + for (int i=0; i>1)&1)|(params.fb<<1)|0x10); + } + OPL_WRITE(0xa0,0); + if (ops==4) { + OPL_WRITE(0xa3,0); + } + OPL_WRITE(0xb0,mult0?0x2a:0x26); + if (ops==4) { + OPL_WRITE(0xb3,mult0?0x2a:0x26); + } + } + + // render + for (int i=0; iwrite(0,(addr)); \ + ((ymfm::ym2414*)fmPreviewOPZ)->write(1,(val)); \ + ((ymfm::ym2414*)fmPreviewOPZ)->generate(&out,1); + +void FurnaceGUI::renderFMPreviewOPZ(const DivInstrumentFM& params, int pos) { + if (fmPreviewOPZ==NULL) { + fmPreviewOPZInterface=new ymfm::ymfm_interface(); + fmPreviewOPZ=new ymfm::ym2414(*(ymfm::ymfm_interface*)fmPreviewOPZInterface); + pos=0; + } + ymfm::ymfm_output<2> out; + int aOut=0; + bool mult0=false; + + if (pos==0) { + ((ymfm::ym2414*)fmPreviewOPZ)->reset(); + + // set params + for (int i=0; i<4; i++) { + if ((params.op[i].mult&15)==0) { + mult0=true; + break; + } + } + for (int i=0; i<4; i++) { + const DivInstrumentFM::Operator& op=params.op[i]; + unsigned short baseAddr=i*8; + OPZ_WRITE(baseAddr+0x40,(op.mult&15)|((op.egt?(op.dt&7):dtTableFMP[op.dt&7])<<4)); + OPZ_WRITE(baseAddr+0x40,(op.dvb&15)|((op.ws&7)<<4)|0x80); + OPZ_WRITE(baseAddr+0x60,op.tl); + OPZ_WRITE(baseAddr+0x80,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + OPZ_WRITE(baseAddr+0xa0,(op.dr&31)|(op.am<<7)); + OPZ_WRITE(baseAddr+0xc0,(op.d2r&31)|(op.dt2<<6)); + OPZ_WRITE(baseAddr+0xc0,(op.dam&7)|(op.ksl<<6)|0x20); + OPZ_WRITE(baseAddr+0xe0,(op.rr&15)|(op.sl<<4)); + } + OPZ_WRITE(0x38,((params.fms&7)<<4)|(params.ams&3)); + OPZ_WRITE(0x38,((params.fms2&7)<<4)|(params.ams2&3)|0x84); + OPZ_WRITE(0x28,mult0?0x39:0x29); // frequency + OPZ_WRITE(0x30,0xe7); + OPZ_WRITE(0x20,(params.alg&7)|((params.fb&7)<<3)|0x40); // key on + } + + // render + for (int i=0; igenerate(&out,1); + aOut+=out.data[0]; + if (aOut<-32768) aOut=-32768; + if (aOut>32767) aOut=32767; + fmPreview[i]=aOut; + } +} + +void FurnaceGUI::renderFMPreview(const DivInstrument* ins, int pos) { + switch (ins->type) { + case DIV_INS_FM: + renderFMPreviewOPN(ins->fm,pos); + break; + case DIV_INS_OPM: + renderFMPreviewOPM(ins->fm,pos); + break; + case DIV_INS_OPLL: + renderFMPreviewOPLL(ins->fm,pos); + break; + case DIV_INS_OPL: + renderFMPreviewOPL(ins->fm,pos); + break; + case DIV_INS_OPZ: + renderFMPreviewOPZ(ins->fm,pos); + break; + default: + break; + } +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d07a1792e..d6763f769 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2115,6 +2115,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } pushRecentFile(path); + pushRecentSys(path.c_str()); logD("save complete."); return 0; } @@ -2233,6 +2234,13 @@ void FurnaceGUI::pushRecentFile(String path) { } } +void FurnaceGUI::pushRecentSys(const char* path) { +#ifdef _WIN32 + WString widePath=utf8To16(path); + SHAddToRecentDocs(SHARD_PATHW,widePath.c_str()); +#endif +} + void FurnaceGUI::delFirstBackup(String name) { std::vector listOfFiles; #ifdef _WIN32 @@ -3523,10 +3531,12 @@ bool FurnaceGUI::loop() { case SDL_DISPLAYEVENT_CONNECTED: logD("display %d connected!",ev.display.display); updateWindow=true; + shallDetectScale=16; break; case SDL_DISPLAYEVENT_DISCONNECTED: logD("display %d disconnected!",ev.display.display); updateWindow=true; + shallDetectScale=16; break; case SDL_DISPLAYEVENT_ORIENTATION: logD("display oriented to %d",ev.display.data1); @@ -4828,29 +4838,39 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_INS_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { - e->song.ins[curIns]->save(copyOfName.c_str(),false,&e->song); + if (e->song.ins[curIns]->save(copyOfName.c_str(),false,&e->song)) { + pushRecentSys(copyOfName.c_str()); + } } break; case GUI_FILE_INS_SAVE_DMP: if (curIns>=0 && curIns<(int)e->song.ins.size()) { if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { showError("error while saving instrument! make sure your instrument is compatible."); + } else { + pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { - e->song.wave[curWave]->save(copyOfName.c_str()); + if (e->song.wave[curWave]->save(copyOfName.c_str())) { + pushRecentSys(copyOfName.c_str()); + } } break; case GUI_FILE_WAVE_SAVE_DMW: if (curWave>=0 && curWave<(int)e->song.wave.size()) { - e->song.wave[curWave]->saveDMW(copyOfName.c_str()); + if (e->song.wave[curWave]->saveDMW(copyOfName.c_str())) { + pushRecentSys(copyOfName.c_str()); + } } break; case GUI_FILE_WAVE_SAVE_RAW: if (curWave>=0 && curWave<(int)e->song.wave.size()) { - e->song.wave[curWave]->saveRaw(copyOfName.c_str()); + if (e->song.wave[curWave]->saveRaw(copyOfName.c_str())) { + pushRecentSys(copyOfName.c_str()); + } } break; case GUI_FILE_SAMPLE_OPEN: { @@ -4914,6 +4934,8 @@ bool FurnaceGUI::loop() { if (curSample>=0 && curSample<(int)e->song.sample.size()) { if (!e->song.sample[curSample]->save(copyOfName.c_str())) { showError("could not save sample! open Log Viewer for more information."); + } else { + pushRecentSys(copyOfName.c_str()); } } break; @@ -4921,6 +4943,8 @@ bool FurnaceGUI::loop() { if (curSample>=0 && curSample<(int)e->song.sample.size()) { if (!e->song.sample[curSample]->saveRaw(copyOfName.c_str())) { showError("could not save sample! open Log Viewer for more information."); + } else { + pushRecentSys(copyOfName.c_str()); } } break; @@ -5070,6 +5094,7 @@ bool FurnaceGUI::loop() { if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); + pushRecentSys(copyOfName.c_str()); } else { showError("could not open file!"); } @@ -5090,6 +5115,7 @@ bool FurnaceGUI::loop() { if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); + pushRecentSys(copyOfName.c_str()); } else { showError("could not open file!"); } @@ -5116,6 +5142,7 @@ bool FurnaceGUI::loop() { if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); + pushRecentSys(copyOfName.c_str()); } else { showError("could not open file!"); } @@ -5280,6 +5307,7 @@ bool FurnaceGUI::loop() { } } if (!e->isExporting()) { + e->finishAudioFile(); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -6115,6 +6143,14 @@ bool FurnaceGUI::loop() { willCommit=false; } + if (shallDetectScale) { + if (--shallDetectScale<1) { + if (settings.dpiScale<0.5f) { + applyUISettings(); + } + } + } + if (fontsFailed) { showError("it appears I couldn't load these fonts. any setting you can check?"); logE("couldn't load fonts"); @@ -6889,6 +6925,8 @@ FurnaceGUI::FurnaceGUI(): waveEditStyle(0), displayInsTypeListMakeInsSample(-1), mobileEditPage(0), + wheelCalmDown(0), + shallDetectScale(0), mobileMenuPos(0.0f), autoButtonSize(0.0f), mobileEditAnim(0.0f), @@ -6899,6 +6937,11 @@ FurnaceGUI::FurnaceGUI(): fmPreviewOn(false), fmPreviewPaused(false), fmPreviewOPN(NULL), + fmPreviewOPM(NULL), + fmPreviewOPL(NULL), + fmPreviewOPLL(NULL), + fmPreviewOPZ(NULL), + fmPreviewOPZInterface(NULL), editString(NULL), pendingRawSampleDepth(8), pendingRawSampleChannels(1), diff --git a/src/gui/gui.h b/src/gui/gui.h index a48326361..c3b4a4e33 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1351,6 +1351,8 @@ class FurnaceGUI { int waveEditStyle; int displayInsTypeListMakeInsSample; int mobileEditPage; + int wheelCalmDown; + int shallDetectScale; float mobileMenuPos, autoButtonSize, mobileEditAnim; ImVec2 mobileEditButtonPos, mobileEditButtonSize; const int* curSysSection; @@ -1358,6 +1360,11 @@ class FurnaceGUI { short fmPreview[FM_PREVIEW_SIZE]; bool updateFMPreview, fmPreviewOn, fmPreviewPaused; void* fmPreviewOPN; + void* fmPreviewOPM; + void* fmPreviewOPL; + void* fmPreviewOPLL; + void* fmPreviewOPZ; + void* fmPreviewOPZInterface; String* editString; String pendingRawSample; @@ -2145,9 +2152,14 @@ class FurnaceGUI { void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, float maxRr, const ImVec2& size, unsigned short instType); void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size); bool drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange, bool fromMenu=false); - void kvsConfig(DivInstrument* ins); + void kvsConfig(DivInstrument* ins, bool supportsKVS=true); void drawFMPreview(const ImVec2& size); - void renderFMPreview(const DivInstrumentFM& params, int pos=0); + void renderFMPreview(const DivInstrument* ins, int pos=0); + void renderFMPreviewOPN(const DivInstrumentFM& params, int pos=0); + void renderFMPreviewOPM(const DivInstrumentFM& params, int pos=0); + void renderFMPreviewOPLL(const DivInstrumentFM& params, int pos=0); + void renderFMPreviewOPL(const DivInstrumentFM& params, int pos=0); + void renderFMPreviewOPZ(const DivInstrumentFM& params, int pos=0); // these ones offer ctrl-wheel fine value changes. bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0); @@ -2343,6 +2355,7 @@ class FurnaceGUI { int loadStream(String path); void openRecentFile(String path); void pushRecentFile(String path); + void pushRecentSys(const char* path); void exportAudio(String path, DivAudioExportModes mode); void delFirstBackup(String name); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e6056d036..98d02e3a2 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -19,6 +19,7 @@ #define _USE_MATH_DEFINES #include "gui.h" +#include "../ta-log.h" #include "imgui_internal.h" #include "../engine/macroInt.h" #include "IconsFontAwesome4.h" @@ -1283,8 +1284,8 @@ inline bool enBit30(const int val) { } -void FurnaceGUI::kvsConfig(DivInstrument* ins) { - if (ins->type==DIV_INS_FM && fmPreviewOn) { +void FurnaceGUI::kvsConfig(DivInstrument* ins, bool supportsKVS) { + if (fmPreviewOn) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("left click to restart\nmiddle click to pause\nright click to see algorithm"); } @@ -1294,15 +1295,23 @@ void FurnaceGUI::kvsConfig(DivInstrument* ins) { if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { fmPreviewPaused=!fmPreviewPaused; } - } else { + } else if (supportsKVS) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("left click to configure TL scaling\nright click to see FM preview"); } + } else { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("right click to see FM preview"); + } } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && ins->type==DIV_INS_FM) { + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { fmPreviewOn=!fmPreviewOn; } - if (!fmPreviewOn || ins->type!=DIV_INS_FM) { + if (ImGui::IsItemHovered() && CHECK_LONG_HOLD) { + NOTIFY_LONG_HOLD; + fmPreviewOn=!fmPreviewOn; + } + if (!fmPreviewOn && supportsKVS) { int opCount=4; if (ins->type==DIV_INS_OPLL) opCount=2; if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; @@ -1568,6 +1577,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Bottom"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1590,6 +1600,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Attack"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1609,6 +1620,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Hold"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1628,6 +1640,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Decay"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1650,6 +1663,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextColumn(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Release"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1671,6 +1685,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Bottom"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1693,6 +1708,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Speed"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1711,6 +1727,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail } rightClickable ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Shape"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -1750,8 +1767,12 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail \ /* if ADSR/LFO, populate min/max */ \ if (i.macro->open&6) { \ - i.macro->val[0]=i.min; \ - i.macro->val[1]=i.max; \ + if (i.macro->val[0]==0 && i.macro->val[1]==0) { \ + i.macro->val[0]=i.min; \ + i.macro->val[1]=i.max; \ + } \ + i.macro->val[0]=CLAMP(i.macro->val[0],i.min,i.max); \ + i.macro->val[1]=CLAMP(i.macro->val[1],i.min,i.max); \ } \ } \ PARAMETER; \ @@ -2281,7 +2302,7 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; if (updateFMPreview) { - renderFMPreview(ins->fm); + renderFMPreview(ins); updateFMPreview=false; } if (settings.insEditColorize) { @@ -2469,10 +2490,10 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); - if (ins->type==DIV_INS_FM && fmPreviewOn) { + if (fmPreviewOn) { drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); if (!fmPreviewPaused) { - renderFMPreview(ins->fm,1); + renderFMPreview(ins,1); WAKE_UP; } } else { @@ -2490,7 +2511,15 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable P(CWSliderScalar(FM_NAME(FM_AMS2),ImGuiDataType_U8,&ins->fm.ams2,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); - drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (fmPreviewOn) { + drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (!fmPreviewPaused) { + renderFMPreview(ins,1); + WAKE_UP; + } + } else { + drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + } kvsConfig(ins); if (ImGui::Button("Request from TX81Z")) { @@ -2524,7 +2553,15 @@ void FurnaceGUI::drawInsEdit() { } } ImGui::TableNextColumn(); - drawAlgorithm(ins->fm.alg&algMax,fourOp?FM_ALGS_4OP_OPL:FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (fmPreviewOn) { + drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (!fmPreviewPaused) { + renderFMPreview(ins,1); + WAKE_UP; + } + } else { + drawAlgorithm(ins->fm.alg&algMax,fourOp?FM_ALGS_4OP_OPL:FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + } kvsConfig(ins); break; } @@ -2549,7 +2586,16 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndDisabled(); ImGui::TableNextColumn(); - drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale)); + if (fmPreviewOn) { + drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale)); + if (!fmPreviewPaused) { + renderFMPreview(ins,1); + WAKE_UP; + } + } else { + drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale)); + } + kvsConfig(ins,false); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); @@ -5120,10 +5166,9 @@ void FurnaceGUI::drawInsEdit() { ins->snes.sus=3; } } else { - if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + if (ImGui::BeginTable("SNESGainParams",2,ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -5132,9 +5177,6 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT("Gain"); ImGui::TextUnformatted("Gain"); - ImGui::TableNextColumn(); - CENTER_TEXT("Envelope"); - ImGui::TextUnformatted("Envelope"); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -5164,11 +5206,11 @@ void FurnaceGUI::drawInsEdit() { if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("Envelope goes here..."); - ImGui::EndTable(); } + if (ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR || ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG) { + ImGui::TextWrapped("using decrease modes will not produce any sound at all, unless you know what you are doing.\nit is recommended to use the Gain macro for decrease instead."); + } } ImGui::EndTabItem(); } @@ -5233,180 +5275,205 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled)) { wavePreviewInit=true; } - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ins->ws.effect&0x80) { - if ((ins->ws.effect&0x7f)>=DIV_WS_DUAL_MAX) { - ins->ws.effect=0; + if (ins->ws.enabled) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ins->ws.effect&0x80) { + if ((ins->ws.effect&0x7f)>=DIV_WS_DUAL_MAX) { + ins->ws.effect=0; + wavePreviewInit=true; + } + } else { + if ((ins->ws.effect&0x7f)>=DIV_WS_SINGLE_MAX) { + ins->ws.effect=0; + wavePreviewInit=true; + } + } + if (ImGui::BeginCombo("##WSEffect",(ins->ws.effect&0x80)?dualWSEffects[ins->ws.effect&0x7f]:singleWSEffects[ins->ws.effect&0x7f])) { + ImGui::Text("Single-waveform"); + ImGui::Indent(); + for (int i=0; iws.effect=i; + wavePreviewInit=true; + } + } + ImGui::Unindent(); + ImGui::Text("Dual-waveform"); + ImGui::Indent(); + for (int i=129; iws.effect=i; + wavePreviewInit=true; + } + } + ImGui::Unindent(); + ImGui::EndCombo(); + } + const bool isSingleWaveFX=(ins->ws.effect>=128); + if (ImGui::BeginTable("WSPreview",isSingleWaveFX?3:2)) { + DivWavetable* wave1=e->getWave(ins->ws.wave1); + DivWavetable* wave2=e->getWave(ins->ws.wave2); + if (wavePreviewInit) { + wavePreview.init(ins,wavePreviewLen,wavePreviewHeight,true); + wavePreviewInit=false; + } + float wavePreview1[257]; + float wavePreview2[257]; + float wavePreview3[257]; + for (int i=0; ilen; i++) { + if (wave1->data[i]>wave1->max) { + wavePreview1[i]=wave1->max; + } else { + wavePreview1[i]=wave1->data[i]; + } + } + if (wave1->len>0) { + wavePreview1[wave1->len]=wave1->data[wave1->len-1]; + } + for (int i=0; ilen; i++) { + if (wave2->data[i]>wave2->max) { + wavePreview2[i]=wave2->max; + } else { + wavePreview2[i]=wave2->data[i]; + } + } + if (wave2->len>0) { + wavePreview2[wave2->len]=wave2->data[wave2->len-1]; + } + if (ins->ws.enabled && (!wavePreviewPaused || wavePreviewInit)) { + wavePreview.tick(true); + WAKE_UP; + } + for (int i=0; i0) { + wavePreview3[wavePreviewLen]=wavePreview3[wavePreviewLen-1]; + } + + float ySize=(isSingleWaveFX?96.0f:128.0f)*dpiScale; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,ySize); + PlotNoLerp("##WaveformP1",wavePreview1,wave1->len+1,0,"Wave 1",0,wave1->max,size1); + if (isSingleWaveFX) { + ImGui::TableNextColumn(); + ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,ySize); + PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,"Wave 2",0,wave2->max,size2); + } + ImGui::TableNextColumn(); + ImVec2 size3=ImVec2(ImGui::GetContentRegionAvail().x,ySize); + PlotNoLerp("##WaveformP3",wavePreview3,wavePreviewLen+1,0,"Result",0,wavePreviewHeight,size3); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ins->std.waveMacro.len>0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Wave 1 " ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("waveform macro is controlling wave 1!\nthis value will be ineffective."); + } + } else { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Wave 1"); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave1",&ins->ws.wave1,1,4)) { + if (ins->ws.wave1<0) ins->ws.wave1=0; + if (ins->ws.wave1>=(int)e->song.wave.size()) ins->ws.wave1=e->song.wave.size()-1; + wavePreviewInit=true; + } + if (ins->std.waveMacro.len>0) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("waveform macro is controlling wave 1!\nthis value will be ineffective."); + } + } + if (isSingleWaveFX) { + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Wave 2"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) { + if (ins->ws.wave2<0) ins->ws.wave2=0; + if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1; + wavePreviewInit=true; + } + } + ImGui::TableNextColumn(); + if (ImGui::Button(wavePreviewPaused?(ICON_FA_PLAY "##WSPause"):(ICON_FA_PAUSE "##WSPause"))) { + wavePreviewPaused=!wavePreviewPaused; + } + if (ImGui::IsItemHovered()) { + if (wavePreviewPaused) { + ImGui::SetTooltip("Resume preview"); + } else { + ImGui::SetTooltip("Pause preview"); + } + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_REPEAT "##WSRestart")) { + wavePreviewInit=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Restart preview"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UPLOAD "##WSCopy")) { + curWave=e->addWave(); + if (curWave==-1) { + showError("too many wavetables!"); + } else { + wantScrollList=true; + MARK_MODIFIED; + RESET_WAVE_MACRO_ZOOM; + nextWindow=GUI_WINDOW_WAVE_EDIT; + + DivWavetable* copyWave=e->song.wave[curWave]; + copyWave->len=wavePreviewLen; + copyWave->max=wavePreviewHeight; + memcpy(copyWave->data,wavePreview.output,256*sizeof(int)); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Copy to new wavetable"); + } + ImGui::SameLine(); + ImGui::Text("(%d×%d)",wavePreviewLen,wavePreviewHeight+1); + ImGui::EndTable(); + } + + if (ImGui::InputScalar("Update Rate",ImGuiDataType_U8,&ins->ws.rateDivider,&_ONE,&_SEVEN)) { + wavePreviewInit=true; + } + int speed=ins->ws.speed+1; + if (ImGui::InputInt("Speed",&speed,1,16)) { + if (speed<1) speed=1; + if (speed>256) speed=256; + ins->ws.speed=speed-1; + wavePreviewInit=true; + } + + if (ImGui::InputScalar("Amount",ImGuiDataType_U8,&ins->ws.param1,&_ONE,&_SEVEN)) { + wavePreviewInit=true; + } + + if (ins->ws.effect==DIV_WS_PHASE_MOD) { + if (ImGui::InputScalar("Power",ImGuiDataType_U8,&ins->ws.param2,&_ONE,&_SEVEN)) { + wavePreviewInit=true; + } + } + + if (ImGui::Checkbox("Global",&ins->ws.global)) { wavePreviewInit=true; } } else { - if ((ins->ws.effect&0x7f)>=DIV_WS_SINGLE_MAX) { - ins->ws.effect=0; - wavePreviewInit=true; - } - } - if (ImGui::BeginCombo("##WSEffect",(ins->ws.effect&0x80)?dualWSEffects[ins->ws.effect&0x7f]:singleWSEffects[ins->ws.effect&0x7f])) { - ImGui::Text("Single-waveform"); - ImGui::Indent(); - for (int i=0; iws.effect=i; - wavePreviewInit=true; - } - } - ImGui::Unindent(); - ImGui::Text("Dual-waveform"); - ImGui::Indent(); - for (int i=129; iws.effect=i; - wavePreviewInit=true; - } - } - ImGui::Unindent(); - ImGui::EndCombo(); - } - const bool isSingleWaveFX=(ins->ws.effect>=128); - if (ImGui::BeginTable("WSPreview",isSingleWaveFX?3:2)) { - DivWavetable* wave1=e->getWave(ins->ws.wave1); - DivWavetable* wave2=e->getWave(ins->ws.wave2); - if (wavePreviewInit) { - wavePreview.init(ins,wavePreviewLen,wavePreviewHeight,true); - wavePreviewInit=false; - } - float wavePreview1[256]; - float wavePreview2[256]; - float wavePreview3[256]; - for (int i=0; ilen; i++) { - if (wave1->data[i]>wave1->max) { - wavePreview1[i]=wave1->max; - } else { - wavePreview1[i]=wave1->data[i]; - } - } - for (int i=0; ilen; i++) { - if (wave2->data[i]>wave2->max) { - wavePreview2[i]=wave2->max; - } else { - wavePreview2[i]=wave2->data[i]; - } - } - if (ins->ws.enabled && (!wavePreviewPaused || wavePreviewInit)) { - wavePreview.tick(true); - } - for (int i=0; idata[i]>wavePreviewHeight) { - wavePreview3[i]=wavePreviewHeight; - } else { - wavePreview3[i]=wavePreview.output[i]; - } - } - - float ySize=(isSingleWaveFX?96.0f:128.0f)*dpiScale; - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,ySize); - PlotNoLerp("##WaveformP1",wavePreview1,wave1->len+1,0,"Wave 1",0,wave1->max,size1); - if (isSingleWaveFX) { - ImGui::TableNextColumn(); - ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,ySize); - PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,"Wave 2",0,wave2->max,size2); - } - ImGui::TableNextColumn(); - ImVec2 size3=ImVec2(ImGui::GetContentRegionAvail().x,ySize); - PlotNoLerp("##WaveformP3",wavePreview3,wavePreviewLen,0,"Result",0,wavePreviewHeight,size3); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Wave 1"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SelWave1",&ins->ws.wave1,1,4)) { - if (ins->ws.wave1<0) ins->ws.wave1=0; - if (ins->ws.wave1>=(int)e->song.wave.size()) ins->ws.wave1=e->song.wave.size()-1; - wavePreviewInit=true; - } - if (isSingleWaveFX) { - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Wave 2"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) { - if (ins->ws.wave2<0) ins->ws.wave2=0; - if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1; - wavePreviewInit=true; - } - } - ImGui::TableNextColumn(); - if (ImGui::Button(wavePreviewPaused?(ICON_FA_PLAY "##WSPause"):(ICON_FA_PAUSE "##WSPause"))) { - wavePreviewPaused=!wavePreviewPaused; - } - if (ImGui::IsItemHovered()) { - if (wavePreviewPaused) { - ImGui::SetTooltip("Resume preview"); - } else { - ImGui::SetTooltip("Pause preview"); - } - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_REPEAT "##WSRestart")) { - wavePreviewInit=true; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Restart preview"); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_UPLOAD "##WSCopy")) { - curWave=e->addWave(); - if (curWave==-1) { - showError("too many wavetables!"); - } else { - wantScrollList=true; - MARK_MODIFIED; - RESET_WAVE_MACRO_ZOOM; - nextWindow=GUI_WINDOW_WAVE_EDIT; - - DivWavetable* copyWave=e->song.wave[curWave]; - copyWave->len=wavePreviewLen; - copyWave->max=wavePreviewHeight; - memcpy(copyWave->data,wavePreview.output,256*sizeof(int)); - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Copy to new wavetable"); - } - ImGui::SameLine(); - ImGui::Text("(%d×%d)",wavePreviewLen,wavePreviewHeight+1); - ImGui::EndTable(); - } - - if (ImGui::InputScalar("Update Rate",ImGuiDataType_U8,&ins->ws.rateDivider,&_ONE,&_SEVEN)) { - wavePreviewInit=true; - } - int speed=ins->ws.speed+1; - if (ImGui::InputInt("Speed",&speed,1,16)) { - if (speed<1) speed=1; - if (speed>256) speed=256; - ins->ws.speed=speed-1; - wavePreviewInit=true; - } - - if (ImGui::InputScalar("Amount",ImGuiDataType_U8,&ins->ws.param1,&_ONE,&_SEVEN)) { - wavePreviewInit=true; - } - - if (ins->ws.effect==DIV_WS_PHASE_MOD) { - if (ImGui::InputScalar("Power",ImGuiDataType_U8,&ins->ws.param2,&_ONE,&_SEVEN)) { - wavePreviewInit=true; - } - } - - if (ImGui::Checkbox("Global",&ins->ws.global)) { - wavePreviewInit=true; + ImGui::TextWrapped("wavetable synthesizer disabled.\nuse the Waveform macro to set the wave for this instrument."); } ImGui::EndTabItem(); @@ -5636,6 +5703,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_ES5506) waveMax=0; if (ins->type==DIV_INS_GA20) waveMax=0; if (ins->type==DIV_INS_K053260) waveMax=0; + if (ins->type==DIV_INS_BEEPER) waveMax=0; if (ins->type==DIV_INS_POKEMINI) waveMax=0; if (ins->type==DIV_INS_TED) waveMax=0; if (ins->type==DIV_INS_C140) waveMax=0; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index b5e92b598..6c84c8d76 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -437,7 +437,7 @@ void FurnaceGUI::drawPattern() { ImGui::SetCursorPosX(ImGui::GetCursorPosX()+centerOff); } } - if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX|ImGuiTableFlags_NoBordersInFrozenArea|(settings.cursorFollowsWheel?ImGuiTableFlags_NoScrollWithMouse:0))) { + if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX|ImGuiTableFlags_NoBordersInFrozenArea|((settings.cursorFollowsWheel || wheelCalmDown)?ImGuiTableFlags_NoScrollWithMouse:0))) { ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed); char chanID[2048]; float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale); @@ -800,13 +800,14 @@ void FurnaceGUI::drawPattern() { if (e->isRunning()) { DivChannelState* cs=e->getChanState(i); - float stereoPan=(float)(e->convertPanSplitToLinearLR(cs->panL,cs->panR,256)-128)/128.0; + unsigned short chanPan=e->getChanPan(i); + float stereoPan=(float)(e->convertPanSplitToLinear(chanPan,8,256)-128)/128.0; switch (settings.channelVolStyle) { case 1: // simple - xRight=((float)(e->getChanState(i)->volume>>8)/(float)e->getMaxVolumeChan(i))*0.9+(keyHit1[i]*0.1f); + xRight=((float)(cs->volume>>8)/(float)e->getMaxVolumeChan(i))*0.9+(keyHit1[i]*0.1f); break; case 2: { // stereo - float amount=((float)(e->getChanState(i)->volume>>8)/(float)e->getMaxVolumeChan(i))*0.4+(keyHit1[i]*0.1f); + float amount=((float)(cs->volume>>8)/(float)e->getMaxVolumeChan(i))*0.4+(keyHit1[i]*0.1f); xRight=0.5+amount*(1.0+MIN(0.0,stereoPan)); xLeft=0.5-amount*(1.0-MAX(0.0,stereoPan)); break; @@ -955,8 +956,8 @@ void FurnaceGUI::drawPattern() { } // overflow changes order - // TODO: this is very unreliable and sometimes it can warp you out of the song - if (settings.scrollChangesOrder && (!e->isPlaying() || !followPattern) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && !settings.cursorFollowsWheel) { + if (--wheelCalmDown<0) wheelCalmDown=0; + if (settings.scrollChangesOrder && (!e->isPlaying() || !followPattern) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && !settings.cursorFollowsWheel && !wheelCalmDown) { if (wheelY!=0) { if (wheelY>0) { if (ImGui::GetScrollY()<=0) { @@ -965,10 +966,12 @@ void FurnaceGUI::drawPattern() { setOrder(curOrder-1); ImGui::SetScrollY(ImGui::GetScrollMaxY()); updateScroll(e->curSubSong->patLen); + wheelCalmDown=2; } else if (settings.scrollChangesOrder==2) { setOrder(e->curSubSong->ordersLen-1); ImGui::SetScrollY(ImGui::GetScrollMaxY()); updateScroll(e->curSubSong->patLen); + wheelCalmDown=2; } haveHitBounds=false; } else { @@ -984,10 +987,12 @@ void FurnaceGUI::drawPattern() { setOrder(curOrder+1); ImGui::SetScrollY(0); updateScroll(0); + wheelCalmDown=2; } else if (settings.scrollChangesOrder==2) { setOrder(0); ImGui::SetScrollY(0); updateScroll(0); + wheelCalmDown=2; } haveHitBounds=false; } else { diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 25a4aa3b0..02d0aed58 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -293,6 +293,9 @@ void FurnaceGUI::drawSampleEdit() { if (sample->samples>131070) { SAMPLE_WARN(warnLength,"Amiga: maximum sample length is 131070"); } + if (dispatch!=NULL) { + MAX_RATE("Amiga",31250.0); + } break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 2035ac4df..2884b5364 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -29,6 +29,7 @@ #include "IconsFontAwesome4.h" #include "furIcons.h" #include "misc/cpp/imgui_stdlib.h" +#include "scaling.h" #include #include @@ -1303,6 +1304,7 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##PCSOutMethod",&settings.pcSpeakerOutMethod,pcspkrOutMethods,5); + /* ImGui::Separator(); ImGui::Text("Sample ROMs:"); @@ -1332,6 +1334,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::Button(ICON_FA_FOLDER "##MU5Load")) { openFileDialog(GUI_FILE_MU5_ROM_OPEN); } + */ END_SECTION; } @@ -4066,7 +4069,20 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { setupLabel(settings.emptyLabel.c_str(),emptyLabel,3); setupLabel(settings.emptyLabel2.c_str(),emptyLabel2,2); - if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; + // get scale factor + const char* videoBackend=SDL_GetCurrentVideoDriver(); + if (settings.dpiScale>=0.5f) { + logD("setting UI scale factor from config (%f).",settings.dpiScale); + dpiScale=settings.dpiScale; + } else { + logD("auto-detecting UI scale factor."); + dpiScale=getScaleFactor(videoBackend); + logD("scale factor: %f",dpiScale); + if (dpiScale<0.1f) { + logW("scale what?"); + dpiScale=1.0f; + } + } // colors if (updateFonts) { @@ -4248,11 +4264,17 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImFontConfig fontConf; ImFontConfig fontConfP; + ImFontConfig fontConfB; + ImFontConfig fontConfH; fontConf.OversampleV=1; fontConf.OversampleH=2; fontConfP.OversampleV=1; fontConfP.OversampleH=2; + fontConfB.OversampleV=1; + fontConfB.OversampleH=1; + fontConfH.OversampleV=1; + fontConfH.OversampleH=1; //fontConf.RasterizerMultiply=1.5; //fontConfP.RasterizerMultiply=1.5; @@ -4300,6 +4322,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImFontConfig fc1; fc1.MergeMode=true; + // save memory + fc1.OversampleH=1; + fc1.OversampleV=1; if (settings.mainFont==6) { // custom font if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),MAX(1,e->getConfInt("mainFontSize",18)*dpiScale),&fontConf,fontRange))==NULL) { @@ -4336,6 +4361,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImFontConfig fc; fc.MergeMode=true; + fc.OversampleH=1; + fc.OversampleV=1; + fc.PixelSnapH=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,MAX(1,e->getConfInt("iconSize",16)*dpiScale),&fc,fontRangeIcon))==NULL) { @@ -4384,7 +4412,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { // 0x39B = Λ static const ImWchar bigFontRange[]={0x20,0xFF,0x39b,0x39b,0}; - if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,MAX(1,40*dpiScale),NULL,bigFontRange))==NULL) { + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,MAX(1,40*dpiScale),&fontConfB,bigFontRange))==NULL) { logE("could not load big UI font!"); } @@ -4393,21 +4421,21 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { headFont=mainFont; } else { if (settings.headFont==6) { // custom font - if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.headFontPath.c_str(),MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.headFontPath.c_str(),MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { logW("could not load header font! reverting to default font"); settings.headFont=0; - if ((headFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { logE("could not load header font! falling back to IBM Plex Sans."); headFont=ImGui::GetIO().Fonts->AddFontDefault(); } } } else if (settings.headFont==5) { // system font - if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_HEAD_FONT_PATH_1,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { - if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_HEAD_FONT_PATH_2,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { - if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_HEAD_FONT_PATH_3,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_HEAD_FONT_PATH_1,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_HEAD_FONT_PATH_2,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_HEAD_FONT_PATH_3,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { logW("could not load header font! reverting to default font"); settings.headFont=0; - if ((headFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { logE("could not load header font! falling back to IBM Plex Sans."); headFont=ImGui::GetIO().Fonts->AddFontDefault(); } @@ -4415,7 +4443,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { } } } else { - if ((headFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),NULL,upTo800))==NULL) { + if ((headFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH,upTo800))==NULL) { logE("could not load header font!"); headFont=ImGui::GetIO().Fonts->AddFontDefault(); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index d32e43aba..1609ca7b0 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -484,10 +484,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } - case DIV_SYSTEM_NES: - case DIV_SYSTEM_VRC6: - case DIV_SYSTEM_FDS: - case DIV_SYSTEM_MMC5: { + case DIV_SYSTEM_NES: { int clockSel=flags.getInt("clockSel",0); bool dpcmMode=flags.getBool("dpcmMode",true); @@ -529,6 +526,35 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } + case DIV_SYSTEM_VRC6: + case DIV_SYSTEM_FDS: + case DIV_SYSTEM_MMC5: { + int clockSel=flags.getInt("clockSel",0); + + ImGui::Text("Clock rate:"); + + ImGui::Indent(); + if (ImGui::RadioButton("NTSC (1.79MHz)",clockSel==0)) { + clockSel=0; + altered=true; + } + if (ImGui::RadioButton("PAL (1.67MHz)",clockSel==1)) { + clockSel=1; + altered=true; + } + if (ImGui::RadioButton("Dendy (1.77MHz)",clockSel==2)) { + clockSel=2; + altered=true; + } + ImGui::Unindent(); + + if (altered) { + e->lockSave([&]() { + flags.set("clockSel",clockSel); + }); + } + break; + } case DIV_SYSTEM_C64_8580: case DIV_SYSTEM_C64_6581: { int clockSel=flags.getInt("clockSel",0); diff --git a/src/gui/tutorial.cpp b/src/gui/tutorial.cpp index c1be89127..ea753205a 100644 --- a/src/gui/tutorial.cpp +++ b/src/gui/tutorial.cpp @@ -255,12 +255,6 @@ void FurnaceGUI::drawTutorial() { ImGui::Text("welcome to Furnace, the biggest open-source chiptune tracker!"); - ImGui::TextWrapped( - "did I say that 0.6pre5 will have a tutorial? well, it doesn't...\n" - "the reason is because 0.6pre5 fixes a critical bug which may cause config loss in some machines.\n" - "furthermore, it dramatically improves the backup system. couldn't put this version on hold anymore." - ); - ImGui::Separator(); ImGui::TextWrapped("here are some tips to get you started:"); @@ -280,7 +274,7 @@ void FurnaceGUI::drawTutorial() { ImGui::TextWrapped( "if you need help, you may:\n" "- read the (incomplete) manual: https://github.com/tildearrow/furnace/blob/master/doc/README.md\n" - "- ask for help in Discussions (https://github.com/tildearrow/furnace/discussions) or the Furnace Discord (https://discord.gg/EfrwT2wq7z)" + "- ask for help in Discussions (https://github.com/tildearrow/furnace/discussions), the Furnace Discord (https://discord.gg/EfrwT2wq7z) or Furnace in Revolt (https://rvlt.gg/GRPS6tmc)" ); ImGui::Separator(); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 3905b7614..de7cee606 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -1093,7 +1093,7 @@ void FurnaceGUI::drawWaveEdit() { MARK_MODIFIED; }); } - if (ImGui::Button("Invert",buttonSize)) { + if (ImGui::Button("Invert",buttonSizeHalf)) { e->lockEngine([this,wave]() { for (int i=0; ilen; i++) { wave->data[i]=wave->max-wave->data[i]; @@ -1101,6 +1101,18 @@ void FurnaceGUI::drawWaveEdit() { MARK_MODIFIED; }); } + ImGui::SameLine(); + if (ImGui::Button("Reverse",buttonSizeHalf)) { + e->lockEngine([this,wave]() { + int origData[256]; + memcpy(origData,wave->data,wave->len*sizeof(int)); + + for (int i=0; ilen; i++) { + wave->data[i]=origData[wave->len-1-i]; + } + MARK_MODIFIED; + }); + } if (ImGui::Button("Half",buttonSizeHalf)) { int origData[256];