Merge branch 'master' of https://github.com/tildearrow/furnace into vgsound_emu_update
This commit is contained in:
commit
52476ec1a6
|
@ -14,6 +14,7 @@ linuxbuild/
|
||||||
test/songs/
|
test/songs/
|
||||||
test/delta/
|
test/delta/
|
||||||
test/result/
|
test/result/
|
||||||
|
test/assert_delta
|
||||||
android/.gradle/
|
android/.gradle/
|
||||||
android/app/build/
|
android/app/build/
|
||||||
android/app/.cxx/
|
android/app/.cxx/
|
||||||
|
|
|
@ -412,7 +412,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
|
||||||
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
|
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
|
||||||
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
|
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/tia/TIASnd.cpp
|
src/engine/platform/sound/tia/AudioChannel.cpp
|
||||||
|
src/engine/platform/sound/tia/Audio.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
||||||
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
||||||
|
|
|
@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted.
|
||||||
|
|
||||||
the coding style is described here:
|
the coding style is described here:
|
||||||
|
|
||||||
- indentation: two spaces
|
- indentation: two spaces. **strictly** spaces. do NOT use tabs.
|
||||||
- modified 1TBS style:
|
- modified 1TBS style:
|
||||||
- no spaces in function calls
|
- no spaces in function calls
|
||||||
- spaces between arguments in function declarations
|
- spaces between arguments in function declarations
|
||||||
|
|
12
README.md
12
README.md
|
@ -6,7 +6,7 @@ the biggest multi-system chiptune tracker ever made!
|
||||||
|
|
||||||
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
|
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
|
||||||
|
|
||||||
***
|
---
|
||||||
## downloads
|
## downloads
|
||||||
|
|
||||||
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
|
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
|
||||||
|
@ -74,7 +74,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
||||||
- loads .dmf modules from all versions (beta 1 to 1.1.3)
|
- loads .dmf modules from all versions (beta 1 to 1.1.3)
|
||||||
- saves .dmf modules - both modern and legacy
|
- saves .dmf modules - both modern and legacy
|
||||||
- Furnace doubles as a module downgrader
|
- Furnace doubles as a module downgrader
|
||||||
- loads .dmp instruments and .dmw wavetables as well
|
- loads/saves .dmp instruments and .dmw wavetables as well
|
||||||
- clean-room design (guesswork and ABX tests only, no decompilation involved)
|
- clean-room design (guesswork and ABX tests only, no decompilation involved)
|
||||||
- bug/quirk implementation for increased playback accuracy through compatibility flags
|
- bug/quirk implementation for increased playback accuracy through compatibility flags
|
||||||
- VGM export
|
- VGM export
|
||||||
|
@ -103,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
||||||
- built-in visualizer in pattern view
|
- built-in visualizer in pattern view
|
||||||
- open-source under GPLv2 or later.
|
- open-source under GPLv2 or later.
|
||||||
|
|
||||||
***
|
---
|
||||||
# quick references
|
# quick references
|
||||||
|
|
||||||
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
|
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
|
||||||
|
@ -119,7 +119,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
|
||||||
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
|
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
|
||||||
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
|
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
|
||||||
|
|
||||||
***
|
---
|
||||||
# developer info
|
# developer info
|
||||||
|
|
||||||
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
|
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
|
||||||
|
@ -228,7 +228,7 @@ this will play a compatible file and enable the commands view.
|
||||||
|
|
||||||
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
|
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
|
||||||
|
|
||||||
***
|
---
|
||||||
# frequently asked questions
|
# frequently asked questions
|
||||||
|
|
||||||
> woah! 50 sound chips?! I can't believe it!
|
> woah! 50 sound chips?! I can't believe it!
|
||||||
|
@ -274,7 +274,7 @@ the DefleMask format has several limitations. save in Furnace song format instea
|
||||||
|
|
||||||
right click on the channel name.
|
right click on the channel name.
|
||||||
|
|
||||||
***
|
---
|
||||||
# footnotes
|
# footnotes
|
||||||
|
|
||||||
copyright (C) 2021-2022 tildearrow and contributors.
|
copyright (C) 2021-2022 tildearrow and contributors.
|
||||||
|
|
8
TODO.md
8
TODO.md
|
@ -1,5 +1,7 @@
|
||||||
# to-do for 0.6pre1.5-0.6pre2
|
# to-do for 0.6pre1.5
|
||||||
|
|
||||||
- volume commands should work on Game Boy
|
|
||||||
- stereo separation control for AY
|
- stereo separation control for AY
|
||||||
- "paste with instrument"
|
- "paste with instrument"
|
||||||
|
- FM operator muting
|
||||||
|
- FM operator swap
|
||||||
|
- bug fixes
|
||||||
|
|
|
@ -15,8 +15,8 @@ android {
|
||||||
}
|
}
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 112
|
versionCode 113
|
||||||
versionName "dev112"
|
versionName "dev113"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.tildearrow.furnace"
|
package="org.tildearrow.furnace"
|
||||||
android:versionCode="93"
|
android:versionCode="113"
|
||||||
android:versionName="0.6pre1"
|
android:versionName="dev113"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<!-- OpenGL ES 2.0 -->
|
<!-- OpenGL ES 2.0 -->
|
||||||
|
@ -83,13 +83,17 @@
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Drop file event -->
|
<!-- Drop file event -->
|
||||||
<!--
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="*/*" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file"/>
|
||||||
|
<data android:scheme="content"/>
|
||||||
|
<data android:host="*"/>
|
||||||
|
<data android:mimeType="*/*"/>
|
||||||
|
<data android:pathPattern=".*\\.fur"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
-->
|
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7
|
Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250
|
|
@ -1,5 +1,5 @@
|
||||||
/* Nuked OPM
|
/* Nuked OPM
|
||||||
* Copyright (C) 2020 Nuke.YKT
|
* Copyright (C) 2022 Nuke.YKT
|
||||||
*
|
*
|
||||||
* This file is part of Nuked OPM.
|
* This file is part of Nuked OPM.
|
||||||
*
|
*
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
* siliconpr0n.org(digshadow, John McMaster):
|
* siliconpr0n.org(digshadow, John McMaster):
|
||||||
* YM2151 and other FM chip decaps and die shots.
|
* YM2151 and other FM chip decaps and die shots.
|
||||||
*
|
*
|
||||||
* version: 0.9.2 beta
|
* version: 0.9.3 beta
|
||||||
*/
|
*/
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -651,7 +651,7 @@ static inline void OPM_EnvelopePhase4(opm_t *chip)
|
||||||
chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]);
|
chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]);
|
||||||
|
|
||||||
eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0;
|
eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0;
|
||||||
slreach = (chip->eg_level[slot] >> 5) == chip->eg_sl[1];
|
slreach = (chip->eg_level[slot] >> 4) == (chip->eg_sl[1] << 1);
|
||||||
eg_zero = chip->eg_level[slot] == 0;
|
eg_zero = chip->eg_level[slot] == 0;
|
||||||
|
|
||||||
chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon;
|
chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon;
|
||||||
|
|
|
@ -64,7 +64,3 @@ In there, you can modify certain data pertaining to your sample, such as the:
|
||||||
- and many more.
|
- and many more.
|
||||||
|
|
||||||
The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text.
|
The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text.
|
||||||
|
|
||||||
# tips
|
|
||||||
|
|
||||||
if you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file,
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
||||||
|
|
||||||
the format versions are:
|
the format versions are:
|
||||||
|
|
||||||
|
- 114: Furnace dev114
|
||||||
|
- 113: Furnace dev113
|
||||||
- 112: Furnace dev112
|
- 112: Furnace dev112
|
||||||
- 111: Furnace dev111
|
- 111: Furnace dev111
|
||||||
- 110: Furnace dev110
|
- 110: Furnace dev110
|
||||||
|
@ -254,6 +256,7 @@ size | description
|
||||||
| - 0xc3: OPN CSM - 10 channels
|
| - 0xc3: OPN CSM - 10 channels
|
||||||
| - 0xc4: PC-98 CSM - 20 channels
|
| - 0xc4: PC-98 CSM - 20 channels
|
||||||
| - 0xc5: YM2610B CSM - 20 channels
|
| - 0xc5: YM2610B CSM - 20 channels
|
||||||
|
| - 0xc6: MSM5232 - 8 channels
|
||||||
| - 0xde: YM2610B extended - 19 channels
|
| - 0xde: YM2610B extended - 19 channels
|
||||||
| - 0xe0: QSound - 19 channels
|
| - 0xe0: QSound - 19 channels
|
||||||
| - 0xfd: Dummy System - 8 channels
|
| - 0xfd: Dummy System - 8 channels
|
||||||
|
@ -338,7 +341,8 @@ size | description
|
||||||
1 | broken initial position of porta after arp (>=101) or reserved
|
1 | broken initial position of porta after arp (>=101) or reserved
|
||||||
1 | SN periods under 8 are treated as 1 (>=108) or reserved
|
1 | SN periods under 8 are treated as 1 (>=108) or reserved
|
||||||
1 | cut/delay effect policy (>=110) or reserved
|
1 | cut/delay effect policy (>=110) or reserved
|
||||||
5 | reserved
|
1 | 0B/0D effect treatment (>=113) or reserved
|
||||||
|
4 | reserved
|
||||||
--- | **virtual tempo data**
|
--- | **virtual tempo data**
|
||||||
2 | virtual tempo numerator of first song (>=96) or reserved
|
2 | virtual tempo numerator of first song (>=96) or reserved
|
||||||
2 | virtual tempo denominator of first song (>=96) or reserved
|
2 | virtual tempo denominator of first song (>=96) or reserved
|
||||||
|
@ -494,7 +498,8 @@ size | description
|
||||||
1 | vib
|
1 | vib
|
||||||
1 | ws
|
1 | ws
|
||||||
1 | ksr
|
1 | ksr
|
||||||
12 | reserved
|
1 | operator enabled (>=114) or reserved
|
||||||
|
11 | reserved
|
||||||
--- | **Game Boy instrument data**
|
--- | **Game Boy instrument data**
|
||||||
1 | volume
|
1 | volume
|
||||||
1 | direction
|
1 | direction
|
||||||
|
|
|
@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double value) {
|
||||||
conf[key]=fmt::sprintf("%f",value);
|
conf[key]=fmt::sprintf("%f",value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivEngine::setConf(String key, const char* value) {
|
||||||
|
conf[key]=String(value);
|
||||||
|
}
|
||||||
|
|
||||||
void DivEngine::setConf(String key, String value) {
|
void DivEngine::setConf(String key, String value) {
|
||||||
conf[key]=value;
|
conf[key]=value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
||||||
int nextOrder=-1;
|
int nextOrder=-1;
|
||||||
int nextRow=0;
|
int nextRow=0;
|
||||||
int effectVal=0;
|
int effectVal=0;
|
||||||
|
int lastSuspectedLoopEnd=-1;
|
||||||
DivPattern* pat[DIV_MAX_CHANS];
|
DivPattern* pat[DIV_MAX_CHANS];
|
||||||
|
unsigned char wsWalked[8192];
|
||||||
|
memset(wsWalked,0,8192);
|
||||||
for (int i=0; i<curSubSong->ordersLen; i++) {
|
for (int i=0; i<curSubSong->ordersLen; i++) {
|
||||||
for (int j=0; j<chans; j++) {
|
for (int j=0; j<chans; j++) {
|
||||||
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
|
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
|
||||||
}
|
}
|
||||||
|
if (i>lastSuspectedLoopEnd) {
|
||||||
|
lastSuspectedLoopEnd=i;
|
||||||
|
}
|
||||||
for (int j=nextRow; j<curSubSong->patLen; j++) {
|
for (int j=nextRow; j<curSubSong->patLen; j++) {
|
||||||
nextRow=0;
|
nextRow=0;
|
||||||
|
bool changingOrder=false;
|
||||||
|
bool jumpingOrder=false;
|
||||||
|
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
|
||||||
|
loopOrder=i;
|
||||||
|
loopRow=j;
|
||||||
|
loopEnd=lastSuspectedLoopEnd;
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int k=0; k<chans; k++) {
|
for (int k=0; k<chans; k++) {
|
||||||
for (int l=0; l<curPat[k].effectCols; l++) {
|
for (int l=0; l<curPat[k].effectCols; l++) {
|
||||||
effectVal=pat[k]->data[j][5+(l<<1)];
|
effectVal=pat[k]->data[j][5+(l<<1)];
|
||||||
if (effectVal<0) effectVal=0;
|
if (effectVal<0) effectVal=0;
|
||||||
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||||
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
if (song.jumpTreatment==2) {
|
||||||
nextOrder=i+1;
|
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||||
nextRow=effectVal;
|
nextOrder=i+1;
|
||||||
|
nextRow=effectVal;
|
||||||
|
jumpingOrder=true;
|
||||||
|
}
|
||||||
|
} else if (song.jumpTreatment==1) {
|
||||||
|
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||||
|
nextOrder=i+1;
|
||||||
|
nextRow=effectVal;
|
||||||
|
jumpingOrder=true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||||
|
if (!changingOrder) {
|
||||||
|
nextOrder=i+1;
|
||||||
|
}
|
||||||
|
jumpingOrder=true;
|
||||||
|
nextRow=effectVal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
|
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
|
||||||
if (nextOrder==-1) {
|
if (nextOrder==-1 || song.jumpTreatment==0) {
|
||||||
nextOrder=effectVal;
|
nextOrder=effectVal;
|
||||||
nextRow=0;
|
if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) {
|
||||||
|
nextRow=0;
|
||||||
|
}
|
||||||
|
changingOrder=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
|
||||||
|
|
||||||
if (nextOrder!=-1) {
|
if (nextOrder!=-1) {
|
||||||
if (nextOrder<=i) {
|
|
||||||
loopOrder=nextOrder;
|
|
||||||
loopRow=nextRow;
|
|
||||||
loopEnd=i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i=nextOrder-1;
|
i=nextOrder-1;
|
||||||
nextOrder=-1;
|
nextOrder=-1;
|
||||||
break;
|
break;
|
||||||
|
@ -1658,6 +1689,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
||||||
speedAB=false;
|
speedAB=false;
|
||||||
playing=true;
|
playing=true;
|
||||||
skipping=true;
|
skipping=true;
|
||||||
|
memset(walked,0,8192);
|
||||||
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
|
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
|
||||||
while (playing && curOrder<goal) {
|
while (playing && curOrder<goal) {
|
||||||
if (nextTick(preserveDrift)) {
|
if (nextTick(preserveDrift)) {
|
||||||
|
@ -3421,9 +3453,8 @@ void DivEngine::setConsoleMode(bool enable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivEngine::switchMaster() {
|
bool DivEngine::switchMaster() {
|
||||||
deinitAudioBackend();
|
logI("switching output...");
|
||||||
quitDispatch();
|
deinitAudioBackend(true);
|
||||||
initDispatch();
|
|
||||||
if (initAudioBackend()) {
|
if (initAudioBackend()) {
|
||||||
for (int i=0; i<song.systemLen; i++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
disCont[i].setRates(got.rate);
|
disCont[i].setRates(got.rate);
|
||||||
|
@ -3567,6 +3598,7 @@ void DivEngine::quitDispatch() {
|
||||||
|
|
||||||
bool DivEngine::initAudioBackend() {
|
bool DivEngine::initAudioBackend() {
|
||||||
// load values
|
// load values
|
||||||
|
logI("initializing audio.");
|
||||||
if (audioEngine==DIV_AUDIO_NULL) {
|
if (audioEngine==DIV_AUDIO_NULL) {
|
||||||
if (getConfString("audioEngine","SDL")=="JACK") {
|
if (getConfString("audioEngine","SDL")=="JACK") {
|
||||||
audioEngine=DIV_AUDIO_JACK;
|
audioEngine=DIV_AUDIO_JACK;
|
||||||
|
@ -3672,8 +3704,9 @@ bool DivEngine::initAudioBackend() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivEngine::deinitAudioBackend() {
|
bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
|
||||||
if (output!=NULL) {
|
if (output!=NULL) {
|
||||||
|
logI("closing audio output.");
|
||||||
output->quit();
|
output->quit();
|
||||||
if (output->midiIn) {
|
if (output->midiIn) {
|
||||||
if (output->midiIn->isDeviceOpen()) {
|
if (output->midiIn->isDeviceOpen()) {
|
||||||
|
@ -3690,7 +3723,9 @@ bool DivEngine::deinitAudioBackend() {
|
||||||
output->quitMidi();
|
output->quitMidi();
|
||||||
delete output;
|
delete output;
|
||||||
output=NULL;
|
output=NULL;
|
||||||
//audioEngine=DIV_AUDIO_NULL;
|
if (dueToSwitchMaster) {
|
||||||
|
audioEngine=DIV_AUDIO_NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,8 @@
|
||||||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||||
|
|
||||||
#define DIV_VERSION "dev112"
|
#define DIV_VERSION "dev114"
|
||||||
#define DIV_ENGINE_VERSION 112
|
#define DIV_ENGINE_VERSION 114
|
||||||
|
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
#define DIV_VERSION_FC 0xff02
|
#define DIV_VERSION_FC 0xff02
|
||||||
|
@ -358,6 +357,8 @@ class DivEngine {
|
||||||
double exportFadeOut;
|
double exportFadeOut;
|
||||||
std::map<String,String> conf;
|
std::map<String,String> conf;
|
||||||
std::deque<DivNoteEvent> pendingNotes;
|
std::deque<DivNoteEvent> pendingNotes;
|
||||||
|
// bitfield
|
||||||
|
unsigned char walked[8192];
|
||||||
bool isMuted[DIV_MAX_CHANS];
|
bool isMuted[DIV_MAX_CHANS];
|
||||||
std::mutex isBusy, saveLock;
|
std::mutex isBusy, saveLock;
|
||||||
String configPath;
|
String configPath;
|
||||||
|
@ -451,7 +452,7 @@ class DivEngine {
|
||||||
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
||||||
|
|
||||||
bool initAudioBackend();
|
bool initAudioBackend();
|
||||||
bool deinitAudioBackend();
|
bool deinitAudioBackend(bool dueToSwitchMaster=false);
|
||||||
|
|
||||||
void registerSystems();
|
void registerSystems();
|
||||||
void initSongWithDesc(const int* description);
|
void initSongWithDesc(const int* description);
|
||||||
|
@ -543,6 +544,7 @@ class DivEngine {
|
||||||
void setConf(String key, int value);
|
void setConf(String key, int value);
|
||||||
void setConf(String key, float value);
|
void setConf(String key, float value);
|
||||||
void setConf(String key, double value);
|
void setConf(String key, double value);
|
||||||
|
void setConf(String key, const char* value);
|
||||||
void setConf(String key, String value);
|
void setConf(String key, String value);
|
||||||
|
|
||||||
// calculate base frequency/period
|
// calculate base frequency/period
|
||||||
|
@ -1072,6 +1074,7 @@ class DivEngine {
|
||||||
memset(reversePitchTable,0,4096*sizeof(int));
|
memset(reversePitchTable,0,4096*sizeof(int));
|
||||||
memset(pitchTable,0,4096*sizeof(int));
|
memset(pitchTable,0,4096*sizeof(int));
|
||||||
memset(sysDefs,0,256*sizeof(void*));
|
memset(sysDefs,0,256*sizeof(void*));
|
||||||
|
memset(walked,0,8192);
|
||||||
|
|
||||||
for (int i=0; i<256; i++) {
|
for (int i=0; i<256; i++) {
|
||||||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||||
|
|
|
@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
||||||
ds.brokenPortaArp=false;
|
ds.brokenPortaArp=false;
|
||||||
ds.snNoLowPeriods=true;
|
ds.snNoLowPeriods=true;
|
||||||
ds.delayBehavior=0;
|
ds.delayBehavior=0;
|
||||||
|
ds.jumpTreatment=2;
|
||||||
|
|
||||||
// 1.1 compat flags
|
// 1.1 compat flags
|
||||||
if (ds.version>24) {
|
if (ds.version>24) {
|
||||||
|
@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
if (ds.version<110) {
|
if (ds.version<110) {
|
||||||
ds.delayBehavior=1;
|
ds.delayBehavior=1;
|
||||||
}
|
}
|
||||||
|
if (ds.version<113) {
|
||||||
|
ds.jumpTreatment=1;
|
||||||
|
}
|
||||||
ds.isDMF=false;
|
ds.isDMF=false;
|
||||||
|
|
||||||
reader.readS(); // reserved
|
reader.readS(); // reserved
|
||||||
|
@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
} else {
|
} else {
|
||||||
reader.readC();
|
reader.readC();
|
||||||
}
|
}
|
||||||
for (int i=0; i<5; i++) {
|
if (ds.version>=113) {
|
||||||
|
ds.jumpTreatment=reader.readC();
|
||||||
|
} else {
|
||||||
|
reader.readC();
|
||||||
|
}
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
reader.readC();
|
reader.readC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3747,7 +3756,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
||||||
w->writeC(song.brokenPortaArp);
|
w->writeC(song.brokenPortaArp);
|
||||||
w->writeC(song.snNoLowPeriods);
|
w->writeC(song.snNoLowPeriods);
|
||||||
w->writeC(song.delayBehavior);
|
w->writeC(song.delayBehavior);
|
||||||
for (int i=0; i<5; i++) {
|
w->writeC(song.jumpTreatment);
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
w->writeC(0);
|
w->writeC(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
ins->type=DIV_INS_FM;
|
ins->type=DIV_INS_FM;
|
||||||
logD("instrument type is Arcade");
|
logD("instrument type is Arcade");
|
||||||
break;
|
break;
|
||||||
|
case 9: // Neo Geo
|
||||||
|
ins->type=DIV_INS_FM;
|
||||||
|
logD("instrument type is Neo Geo");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logD("instrument type is unknown");
|
logD("instrument type is unknown");
|
||||||
lastError="unknown instrument type!";
|
lastError=fmt::sprintf("unknown instrument type %d!",sys);
|
||||||
delete ins;
|
delete ins;
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
@ -171,11 +175,21 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
mode=reader.readC();
|
mode=reader.readC();
|
||||||
logD("instrument mode is %d",mode);
|
logD("instrument mode is %d",mode);
|
||||||
if (mode==0) {
|
if (mode==0) {
|
||||||
if (version<11) {
|
if (ins->type==DIV_INS_FM) {
|
||||||
ins->type=DIV_INS_STD;
|
if (sys==9) {
|
||||||
|
ins->type=DIV_INS_AY;
|
||||||
|
} else {
|
||||||
|
ins->type=DIV_INS_STD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ins->type=DIV_INS_FM;
|
if (sys==3 || sys==6) {
|
||||||
|
ins->type=DIV_INS_OPLL;
|
||||||
|
} else if (sys==1) {
|
||||||
|
ins->type=DIV_INS_OPL;
|
||||||
|
} else {
|
||||||
|
ins->type=DIV_INS_FM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ins->type=DIV_INS_FM;
|
ins->type=DIV_INS_FM;
|
||||||
|
@ -232,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
ins->fm.op[j].dvb=reader.readC();
|
ins->fm.op[j].dvb=reader.readC();
|
||||||
ins->fm.op[j].dam=reader.readC();
|
ins->fm.op[j].dam=reader.readC();
|
||||||
} else {
|
} else {
|
||||||
ins->fm.op[j].rs=reader.readC();
|
if (sys==3 || sys==6) { // OPLL/VRC7
|
||||||
ins->fm.op[j].dt=reader.readC();
|
ins->fm.op[j].ksr=reader.readC()?1:0;
|
||||||
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
|
ins->fm.op[j].vib=reader.readC();
|
||||||
ins->fm.op[j].dt&=15;
|
if (j==0) {
|
||||||
ins->fm.op[j].d2r=reader.readC();
|
ins->fm.opllPreset=ins->fm.op[j].vib>>4;
|
||||||
ins->fm.op[j].ssgEnv=reader.readC();
|
}
|
||||||
|
ins->fm.op[j].vib=ins->fm.op[j].vib?1:0;
|
||||||
|
ins->fm.op[j].ksl=reader.readC()?1:0;
|
||||||
|
ins->fm.op[j].ssgEnv=reader.readC();
|
||||||
|
} else {
|
||||||
|
ins->fm.op[j].rs=reader.readC();
|
||||||
|
ins->fm.op[j].dt=reader.readC();
|
||||||
|
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
|
||||||
|
ins->fm.op[j].dt&=15;
|
||||||
|
ins->fm.op[j].d2r=reader.readC();
|
||||||
|
ins->fm.op[j].ssgEnv=reader.readC();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // STD
|
} else { // STD
|
||||||
|
@ -247,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
if (version>5) {
|
if (version>5) {
|
||||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||||
ins->std.volMacro.val[i]=reader.readI();
|
ins->std.volMacro.val[i]=reader.readI();
|
||||||
|
if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS
|
||||||
|
ins->type=DIV_INS_FDS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||||
|
|
|
@ -71,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
||||||
w->writeC(op.ws);
|
w->writeC(op.ws);
|
||||||
w->writeC(op.ksr);
|
w->writeC(op.ksr);
|
||||||
|
|
||||||
|
w->writeC(op.enable);
|
||||||
|
|
||||||
// reserved
|
// reserved
|
||||||
for (int k=0; k<12; k++) {
|
for (int k=0; k<11; k++) {
|
||||||
w->writeC(0);
|
w->writeC(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
||||||
op.ws=reader.readC();
|
op.ws=reader.readC();
|
||||||
op.ksr=reader.readC();
|
op.ksr=reader.readC();
|
||||||
|
|
||||||
|
if (version>=114) {
|
||||||
|
op.enable=reader.readC();
|
||||||
|
} else {
|
||||||
|
reader.readC();
|
||||||
|
}
|
||||||
|
|
||||||
// reserved
|
// reserved
|
||||||
for (int k=0; k<12; k++) reader.readC();
|
for (int k=0; k<11; k++) reader.readC();
|
||||||
}
|
}
|
||||||
|
|
||||||
// GB
|
// GB
|
||||||
|
|
|
@ -67,7 +67,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
||||||
w.addrOrVal=true;
|
w.addrOrVal=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||||
|
@ -77,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
||||||
for (int i=0; i<8; i++) {
|
for (int i=0; i<8; i++) {
|
||||||
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
|
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o[0]<-32768) o[0]=-32768;
|
if (o[0]<-32768) o[0]=-32768;
|
||||||
if (o[0]>32767) o[0]=32767;
|
if (o[0]>32767) o[0]=32767;
|
||||||
|
|
||||||
if (o[1]<-32768) o[1]=-32768;
|
if (o[1]<-32768) o[1]=-32768;
|
||||||
if (o[1]>32767) o[1]=32767;
|
if (o[1]>32767) o[1]=32767;
|
||||||
|
|
||||||
bufL[h]=o[0];
|
bufL[h]=o[0];
|
||||||
bufR[h]=o[1];
|
bufR[h]=o[1];
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
||||||
delay=1;
|
delay=1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fm_ymfm->generate(&out_ymfm);
|
fm_ymfm->generate(&out_ymfm);
|
||||||
|
|
||||||
for (int i=0; i<8; i++) {
|
for (int i=0; i<8; i++) {
|
||||||
|
@ -120,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
||||||
os[1]=out_ymfm.data[1];
|
os[1]=out_ymfm.data[1];
|
||||||
if (os[1]<-32768) os[1]=-32768;
|
if (os[1]<-32768) os[1]=-32768;
|
||||||
if (os[1]>32767) os[1]=32767;
|
if (os[1]>32767) os[1]=32767;
|
||||||
|
|
||||||
bufL[h]=os[0];
|
bufL[h]=os[0];
|
||||||
bufR[h]=os[1];
|
bufR[h]=os[1];
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -347,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) {
|
||||||
immWrite(i+0x30,chan[i].freq<<2);
|
immWrite(i+0x30,chan[i].freq<<2);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
immWrite(0x08,0x78|i);
|
immWrite(0x08,(chan[i].opMask<<3)|i);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
chan[c.chan].macroInit(ins);
|
chan[c.chan].macroInit(ins);
|
||||||
|
@ -502,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_FM_LFO: {
|
case DIV_CMD_FM_LFO: {
|
||||||
|
if(c.value==0) {
|
||||||
|
rWrite(0x01,0x02);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rWrite(0x01,0x00);
|
||||||
|
}
|
||||||
rWrite(0x18,c.value);
|
rWrite(0x18,c.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -825,6 +841,8 @@ void DivPlatformArcade::reset() {
|
||||||
pmDepth=0x7f;
|
pmDepth=0x7f;
|
||||||
|
|
||||||
//rWrite(0x18,0x10);
|
//rWrite(0x18,0x10);
|
||||||
|
immWrite(0x01,0x02); // LFO Off
|
||||||
|
immWrite(0x18,0x00); // LFO Freq Off
|
||||||
immWrite(0x19,amDepth);
|
immWrite(0x19,amDepth);
|
||||||
immWrite(0x19,0x80|pmDepth);
|
immWrite(0x19,0x80|pmDepth);
|
||||||
//rWrite(0x1b,0x00);
|
//rWrite(0x1b,0x00);
|
||||||
|
|
|
@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM {
|
||||||
int freq, baseFreq, pitch, pitch2, note;
|
int freq, baseFreq, pitch, pitch2, note;
|
||||||
int ins;
|
int ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
unsigned char chVolL, chVolR;
|
unsigned char chVolL, chVolR, opMask;
|
||||||
void macroInit(DivInstrument* which) {
|
void macroInit(DivInstrument* which) {
|
||||||
std.init(which);
|
std.init(which);
|
||||||
pitch2=0;
|
pitch2=0;
|
||||||
|
@ -68,10 +68,12 @@ class DivPlatformArcade: public DivPlatformOPM {
|
||||||
portaPause(false),
|
portaPause(false),
|
||||||
furnacePCM(false),
|
furnacePCM(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(0),
|
outVol(0),
|
||||||
chVolL(127),
|
chVolL(127),
|
||||||
chVolR(127) {}
|
chVolR(127),
|
||||||
|
opMask(15) {}
|
||||||
};
|
};
|
||||||
Channel chan[8];
|
Channel chan[8];
|
||||||
DivDispatchOscBuffer* oscBuf[8];
|
DivDispatchOscBuffer* oscBuf[8];
|
||||||
|
|
|
@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
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));
|
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));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
|
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
chan[c.chan].macroInit(ins);
|
chan[c.chan].macroInit(ins);
|
||||||
|
|
|
@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
||||||
int ins;
|
int ins;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
unsigned char pan;
|
unsigned char pan, opMask;
|
||||||
|
|
||||||
bool dacMode;
|
bool dacMode;
|
||||||
int dacPeriod;
|
int dacPeriod;
|
||||||
|
@ -82,9 +82,11 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
||||||
furnaceDac(false),
|
furnaceDac(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(0),
|
outVol(0),
|
||||||
pan(3),
|
pan(3),
|
||||||
|
opMask(15),
|
||||||
dacMode(false),
|
dacMode(false),
|
||||||
dacPeriod(0),
|
dacPeriod(0),
|
||||||
dacRate(0),
|
dacRate(0),
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#define CHIP_DIVIDER fmDivBase
|
#define CHIP_DIVIDER fmDivBase
|
||||||
|
|
||||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||||
|
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
|
||||||
|
|
||||||
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
if (c.chan<2) {
|
if (c.chan<2) {
|
||||||
|
@ -69,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+0x70,op.d2r&31);
|
rWrite(baseAddr+0x70,op.d2r&31);
|
||||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||||
|
opChan[ch].mask=op.enable;
|
||||||
}
|
}
|
||||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||||
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
|
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
|
||||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
opChan[ch].insChanged=false;
|
opChan[ch].insChanged=false;
|
||||||
|
|
||||||
|
@ -123,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
opChan[i].pan=opChan[ch].pan;
|
opChan[i].pan=opChan[ch].pan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_PITCH: {
|
case DIV_CMD_PITCH: {
|
||||||
|
@ -397,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
||||||
rWrite(baseAddr+0x40,op.tl);
|
rWrite(baseAddr+0x40,op.tl);
|
||||||
immWrite(baseAddr+0x40,op.tl);
|
immWrite(baseAddr+0x40,op.tl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int opChanOffsL[4]={
|
static int opChanOffsL[4]={
|
||||||
|
@ -412,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
||||||
bool writeSomething=false;
|
bool writeSomething=false;
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||||
writeSomething=true;
|
writeSomething=true;
|
||||||
writeMask&=~(1<<(4+i));
|
writeMask&=~(1<<(4+i));
|
||||||
|
@ -459,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
writeNoteOn=true;
|
writeNoteOn=true;
|
||||||
writeMask|=1<<(4+i);
|
if (opChan[i].mask) {
|
||||||
|
writeMask|=1<<(4+i);
|
||||||
|
}
|
||||||
opChan[i].keyOn=false;
|
opChan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -544,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
|
||||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||||
}
|
}
|
||||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||||
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));
|
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));
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
if (chan[i].active) {
|
if (chan[i].active) {
|
||||||
chan[i].keyOn=true;
|
chan[i].keyOn=true;
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
|
|
|
@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
OpChannel():
|
OpChannel():
|
||||||
|
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
||||||
keyOff(false),
|
keyOff(false),
|
||||||
portaPause(false),
|
portaPause(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
|
mask(true),
|
||||||
vol(0),
|
vol(0),
|
||||||
pan(3) {}
|
pan(3) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -249,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
||||||
if (os[1]>32767) os[1]=32767;
|
if (os[1]>32767) os[1]=32767;
|
||||||
|
|
||||||
bufL[h]=os[0];
|
bufL[h]=os[0];
|
||||||
bufR[h]=os[1];
|
if (oplType==3 || oplType==759) {
|
||||||
|
bufR[h]=os[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1520,7 +1522,7 @@ void DivPlatformOPL::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformOPL::isStereo() {
|
bool DivPlatformOPL::isStereo() {
|
||||||
return true;
|
return (oplType==3 || oplType==759);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include "Audio.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr double R_MAX = 30.;
|
||||||
|
constexpr double R = 1.;
|
||||||
|
|
||||||
|
short mixingTableEntry(unsigned char v, unsigned char vMax)
|
||||||
|
{
|
||||||
|
return static_cast<short>(
|
||||||
|
floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Audio::Audio()
|
||||||
|
{
|
||||||
|
for (unsigned char i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e);
|
||||||
|
for (unsigned char i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f);
|
||||||
|
|
||||||
|
reset(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::reset(bool st)
|
||||||
|
{
|
||||||
|
myCounter = 0;
|
||||||
|
mySampleIndex = 0;
|
||||||
|
stereo = st;
|
||||||
|
|
||||||
|
myCurrentSample[0]=0;
|
||||||
|
myCurrentSample[1]=0;
|
||||||
|
|
||||||
|
myChannelOut[0]=0;
|
||||||
|
myChannelOut[1]=0;
|
||||||
|
|
||||||
|
myChannel0.reset();
|
||||||
|
myChannel1.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::tick()
|
||||||
|
{
|
||||||
|
switch (myCounter) {
|
||||||
|
case 9:
|
||||||
|
case 81:
|
||||||
|
myChannel0.phase0();
|
||||||
|
myChannel1.phase0();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 37:
|
||||||
|
case 149:
|
||||||
|
phase1();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++myCounter == 228) myCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::write(unsigned char addr, unsigned char val) {
|
||||||
|
switch (addr&0x3f) {
|
||||||
|
case 0x15:
|
||||||
|
myChannel0.audc(val);
|
||||||
|
break;
|
||||||
|
case 0x16:
|
||||||
|
myChannel1.audc(val);
|
||||||
|
break;
|
||||||
|
case 0x17:
|
||||||
|
myChannel0.audf(val);
|
||||||
|
break;
|
||||||
|
case 0x18:
|
||||||
|
myChannel1.audf(val);
|
||||||
|
break;
|
||||||
|
case 0x19:
|
||||||
|
myChannel0.audv(val);
|
||||||
|
break;
|
||||||
|
case 0x1a:
|
||||||
|
myChannel1.audv(val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::phase1()
|
||||||
|
{
|
||||||
|
unsigned char sample0 = myChannel0.phase1();
|
||||||
|
unsigned char sample1 = myChannel1.phase1();
|
||||||
|
|
||||||
|
addSample(sample0, sample1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::addSample(unsigned char sample0, unsigned char sample1)
|
||||||
|
{
|
||||||
|
if(stereo) {
|
||||||
|
myCurrentSample[0] = myMixingTableIndividual[sample0];
|
||||||
|
myCurrentSample[1] = myMixingTableIndividual[sample1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
myCurrentSample[0] = myMixingTableSum[sample0 + sample1];
|
||||||
|
}
|
||||||
|
|
||||||
|
myChannelOut[0] = myMixingTableIndividual[sample0];
|
||||||
|
myChannelOut[1] = myMixingTableIndividual[sample1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
AudioChannel& Audio::channel0()
|
||||||
|
{
|
||||||
|
return myChannel0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
AudioChannel& Audio::channel1()
|
||||||
|
{
|
||||||
|
return myChannel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#ifndef TIA_AUDIO_HXX
|
||||||
|
#define TIA_AUDIO_HXX
|
||||||
|
|
||||||
|
#include "AudioChannel.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
class Audio
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Audio();
|
||||||
|
|
||||||
|
void reset(bool stereo);
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
|
||||||
|
void write(unsigned char addr, unsigned char val);
|
||||||
|
|
||||||
|
AudioChannel& channel0();
|
||||||
|
|
||||||
|
AudioChannel& channel1();
|
||||||
|
|
||||||
|
short myCurrentSample[2];
|
||||||
|
short myChannelOut[2];
|
||||||
|
|
||||||
|
private:
|
||||||
|
void phase1();
|
||||||
|
void addSample(unsigned char sample0, unsigned char sample1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char myCounter{0};
|
||||||
|
|
||||||
|
AudioChannel myChannel0;
|
||||||
|
AudioChannel myChannel1;
|
||||||
|
|
||||||
|
bool stereo;
|
||||||
|
|
||||||
|
std::array<short, 0x1e + 1> myMixingTableSum;
|
||||||
|
std::array<short, 0x0f + 1> myMixingTableIndividual;
|
||||||
|
|
||||||
|
unsigned int mySampleIndex{0};
|
||||||
|
#ifdef GUI_SUPPORT
|
||||||
|
bool myRewindMode{false};
|
||||||
|
mutable ByteArray mySamples;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
Audio(const Audio&) = delete;
|
||||||
|
Audio(Audio&&) = delete;
|
||||||
|
Audio& operator=(const Audio&) = delete;
|
||||||
|
Audio& operator=(Audio&&) = delete;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TIA_AUDIO_HXX
|
|
@ -0,0 +1,140 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#include "AudioChannel.h"
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::reset()
|
||||||
|
{
|
||||||
|
myAudc = myAudv = myAudf = 0;
|
||||||
|
myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false;
|
||||||
|
myDivCounter = myPulseCounter = myNoiseCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::phase0()
|
||||||
|
{
|
||||||
|
if (myClockEnable) {
|
||||||
|
myNoiseCounterBit4 = myNoiseCounter & 0x01;
|
||||||
|
|
||||||
|
switch (myAudc & 0x03) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x01:
|
||||||
|
myPulseCounterHold = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02:
|
||||||
|
myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03:
|
||||||
|
myPulseCounterHold = !myNoiseCounterBit4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (myAudc & 0x03) {
|
||||||
|
case 0x00:
|
||||||
|
myNoiseFeedback =
|
||||||
|
((myPulseCounter ^ myNoiseCounter) & 0x01) ||
|
||||||
|
!(myNoiseCounter || (myPulseCounter != 0x0a)) ||
|
||||||
|
!(myAudc & 0x0c);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
myNoiseFeedback =
|
||||||
|
(((myNoiseCounter & 0x04) ? 1 : 0) ^ (myNoiseCounter & 0x01)) ||
|
||||||
|
myNoiseCounter == 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myClockEnable = myDivCounter == myAudf;
|
||||||
|
|
||||||
|
if (myDivCounter == myAudf || myDivCounter == 0x1f) {
|
||||||
|
myDivCounter = 0;
|
||||||
|
} else {
|
||||||
|
++myDivCounter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
unsigned char AudioChannel::phase1()
|
||||||
|
{
|
||||||
|
if (myClockEnable) {
|
||||||
|
bool pulseFeedback = false;
|
||||||
|
switch (myAudc >> 2) {
|
||||||
|
case 0x00:
|
||||||
|
pulseFeedback =
|
||||||
|
(((myPulseCounter & 0x02) ? 1 : 0) ^ (myPulseCounter & 0x01)) &&
|
||||||
|
(myPulseCounter != 0x0a) &&
|
||||||
|
(myAudc & 0x03);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01:
|
||||||
|
pulseFeedback = !(myPulseCounter & 0x08);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02:
|
||||||
|
pulseFeedback = !myNoiseCounterBit4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03:
|
||||||
|
pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
myNoiseCounter >>= 1;
|
||||||
|
if (myNoiseFeedback) {
|
||||||
|
myNoiseCounter |= 0x10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myPulseCounterHold) {
|
||||||
|
myPulseCounter = ~(myPulseCounter >> 1) & 0x07;
|
||||||
|
|
||||||
|
if (pulseFeedback) {
|
||||||
|
myPulseCounter |= 0x08;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (myPulseCounter & 0x01) * myAudv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::audc(unsigned char value)
|
||||||
|
{
|
||||||
|
myAudc = value & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::audv(unsigned char value)
|
||||||
|
{
|
||||||
|
myAudv = value & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::audf(unsigned char value)
|
||||||
|
{
|
||||||
|
myAudf = value & 0x1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#ifndef TIA_AUDIO_CHANNEL_HXX
|
||||||
|
#define TIA_AUDIO_CHANNEL_HXX
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
class AudioChannel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioChannel() = default;
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void phase0();
|
||||||
|
|
||||||
|
unsigned char phase1();
|
||||||
|
|
||||||
|
void audc(unsigned char value);
|
||||||
|
|
||||||
|
void audf(unsigned char value);
|
||||||
|
|
||||||
|
void audv(unsigned char value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char myAudc{0};
|
||||||
|
unsigned char myAudv{0};
|
||||||
|
unsigned char myAudf{0};
|
||||||
|
|
||||||
|
bool myClockEnable{false};
|
||||||
|
bool myNoiseFeedback{false};
|
||||||
|
bool myNoiseCounterBit4{false};
|
||||||
|
bool myPulseCounterHold{false};
|
||||||
|
|
||||||
|
unsigned char myDivCounter{0};
|
||||||
|
unsigned char myPulseCounter{0};
|
||||||
|
unsigned char myNoiseCounter{0};
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioChannel(const AudioChannel&) = delete;
|
||||||
|
AudioChannel(AudioChannel&&) = delete;
|
||||||
|
AudioChannel& operator=(const AudioChannel&) = delete;
|
||||||
|
AudioChannel& operator=(AudioChannel&&) = delete;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TIA_AUDIO_CHANNEL_HXX
|
|
@ -1,377 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#include "TIATables.h"
|
|
||||||
#include "TIASnd.h"
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
TIASound::TIASound(int outputFrequency)
|
|
||||||
: myChannelMode(Hardware2Stereo),
|
|
||||||
myOutputFrequency(outputFrequency),
|
|
||||||
myVolumePercentage(100)
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::reset()
|
|
||||||
{
|
|
||||||
// Fill the polynomials
|
|
||||||
polyInit(Bit4, 4, 4, 3);
|
|
||||||
polyInit(Bit5, 5, 5, 3);
|
|
||||||
polyInit(Bit9, 9, 9, 5);
|
|
||||||
|
|
||||||
// Initialize instance variables
|
|
||||||
for(int chan = 0; chan <= 1; ++chan)
|
|
||||||
{
|
|
||||||
myVolume[chan] = 0;
|
|
||||||
myDivNCnt[chan] = 0;
|
|
||||||
myDivNMax[chan] = 0;
|
|
||||||
myDiv3Cnt[chan] = 3;
|
|
||||||
myAUDC[chan] = 0;
|
|
||||||
myAUDF[chan] = 0;
|
|
||||||
myAUDV[chan] = 0;
|
|
||||||
myP4[chan] = 0;
|
|
||||||
myP5[chan] = 0;
|
|
||||||
myP9[chan] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::outputFrequency(int freq)
|
|
||||||
{
|
|
||||||
myOutputFrequency = freq;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
std::string TIASound::channels(unsigned int hardware, bool stereo)
|
|
||||||
{
|
|
||||||
if(hardware == 1)
|
|
||||||
myChannelMode = Hardware1;
|
|
||||||
else
|
|
||||||
myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono;
|
|
||||||
|
|
||||||
switch(myChannelMode)
|
|
||||||
{
|
|
||||||
case Hardware1: return "Hardware1";
|
|
||||||
case Hardware2Mono: return "Hardware2Mono";
|
|
||||||
case Hardware2Stereo: return "Hardware2Stereo";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::set(unsigned short address, unsigned char value)
|
|
||||||
{
|
|
||||||
int chan = ~address & 0x1;
|
|
||||||
switch(address)
|
|
||||||
{
|
|
||||||
case TIARegister::AUDC0:
|
|
||||||
case TIARegister::AUDC1:
|
|
||||||
myAUDC[chan] = value & 0x0f;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TIARegister::AUDF0:
|
|
||||||
case TIARegister::AUDF1:
|
|
||||||
myAUDF[chan] = value & 0x1f;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TIARegister::AUDV0:
|
|
||||||
case TIARegister::AUDV1:
|
|
||||||
myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned short newVal = 0;
|
|
||||||
|
|
||||||
// An AUDC value of 0 is a special case
|
|
||||||
if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5)
|
|
||||||
{
|
|
||||||
// Indicate the clock is zero so no processing will occur,
|
|
||||||
// and set the output to the selected volume
|
|
||||||
newVal = 0;
|
|
||||||
myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Otherwise calculate the 'divide by N' value
|
|
||||||
newVal = myAUDF[chan] + 1;
|
|
||||||
|
|
||||||
// If bits 2 & 3 are set, then multiply the 'div by n' count by 3
|
|
||||||
if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3)
|
|
||||||
newVal *= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only reset those channels that have changed
|
|
||||||
if(newVal != myDivNMax[chan])
|
|
||||||
{
|
|
||||||
// Reset the divide by n counters
|
|
||||||
myDivNMax[chan] = newVal;
|
|
||||||
|
|
||||||
// If the channel is now volume only or was volume only,
|
|
||||||
// reset the counter (otherwise let it complete the previous)
|
|
||||||
if ((myDivNCnt[chan] == 0) || (newVal == 0))
|
|
||||||
myDivNCnt[chan] = newVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
unsigned char TIASound::get(unsigned short address) const
|
|
||||||
{
|
|
||||||
switch(address)
|
|
||||||
{
|
|
||||||
case TIARegister::AUDC0: return myAUDC[0];
|
|
||||||
case TIARegister::AUDC1: return myAUDC[1];
|
|
||||||
case TIARegister::AUDF0: return myAUDF[0];
|
|
||||||
case TIARegister::AUDF1: return myAUDF[1];
|
|
||||||
case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT;
|
|
||||||
case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::volume(unsigned int percent)
|
|
||||||
{
|
|
||||||
if(percent <= 100)
|
|
||||||
myVolumePercentage = percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf)
|
|
||||||
{
|
|
||||||
// Make temporary local copy
|
|
||||||
unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1];
|
|
||||||
unsigned char p5_0 = myP5[0], p5_1 = myP5[1];
|
|
||||||
unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1];
|
|
||||||
short v0 = myVolume[0], v1 = myVolume[1];
|
|
||||||
|
|
||||||
// Take external volume into account
|
|
||||||
short audv0 = (myAUDV[0] * myVolumePercentage) / 100,
|
|
||||||
audv1 = (myAUDV[1] * myVolumePercentage) / 100;
|
|
||||||
|
|
||||||
// Loop until the sample buffer is full
|
|
||||||
while(samples > 0)
|
|
||||||
{
|
|
||||||
// Process channel 0
|
|
||||||
if (div_n_cnt0 > 1)
|
|
||||||
{
|
|
||||||
div_n_cnt0--;
|
|
||||||
}
|
|
||||||
else if (div_n_cnt0 == 1)
|
|
||||||
{
|
|
||||||
int prev_bit5 = Bit5[p5_0];
|
|
||||||
div_n_cnt0 = myDivNMax[0];
|
|
||||||
|
|
||||||
// The P5 counter has multiple uses, so we increment it here
|
|
||||||
p5_0++;
|
|
||||||
if (p5_0 == POLY5_SIZE)
|
|
||||||
p5_0 = 0;
|
|
||||||
|
|
||||||
// Check clock modifier for clock tick
|
|
||||||
if ((audc0 & 0x02) == 0 ||
|
|
||||||
((audc0 & 0x01) == 0 && Div31[p5_0]) ||
|
|
||||||
((audc0 & 0x01) == 1 && Bit5[p5_0]) ||
|
|
||||||
((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5))
|
|
||||||
{
|
|
||||||
if (audc0 & 0x04) // Pure modified clock selected
|
|
||||||
{
|
|
||||||
if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
|
||||||
{
|
|
||||||
if ( Bit5[p5_0] != prev_bit5 )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[0]--;
|
|
||||||
if ( !myDiv3Cnt[0] )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[0] = 3;
|
|
||||||
v0 = v0 ? 0 : audv0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the output was set turn it off, else turn it on
|
|
||||||
v0 = v0 ? 0 : audv0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (audc0 & 0x08) // Check for p5/p9
|
|
||||||
{
|
|
||||||
if (audc0 == POLY9) // Check for poly9
|
|
||||||
{
|
|
||||||
// Increase the poly9 counter
|
|
||||||
myP9[0]++;
|
|
||||||
if (myP9[0] == POLY9_SIZE)
|
|
||||||
myP9[0] = 0;
|
|
||||||
|
|
||||||
v0 = Bit9[myP9[0]] ? audv0 : 0;
|
|
||||||
}
|
|
||||||
else if ( audc0 & 0x02 )
|
|
||||||
{
|
|
||||||
v0 = (v0 || audc0 & 0x01) ? 0 : audv0;
|
|
||||||
}
|
|
||||||
else // Must be poly5
|
|
||||||
{
|
|
||||||
v0 = Bit5[p5_0] ? audv0 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Poly4 is the only remaining option
|
|
||||||
{
|
|
||||||
// Increase the poly4 counter
|
|
||||||
myP4[0]++;
|
|
||||||
if (myP4[0] == POLY4_SIZE)
|
|
||||||
myP4[0] = 0;
|
|
||||||
|
|
||||||
v0 = Bit4[myP4[0]] ? audv0 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process channel 1
|
|
||||||
if (div_n_cnt1 > 1)
|
|
||||||
{
|
|
||||||
div_n_cnt1--;
|
|
||||||
}
|
|
||||||
else if (div_n_cnt1 == 1)
|
|
||||||
{
|
|
||||||
int prev_bit5 = Bit5[p5_1];
|
|
||||||
|
|
||||||
div_n_cnt1 = myDivNMax[1];
|
|
||||||
|
|
||||||
// The P5 counter has multiple uses, so we increment it here
|
|
||||||
p5_1++;
|
|
||||||
if (p5_1 == POLY5_SIZE)
|
|
||||||
p5_1 = 0;
|
|
||||||
|
|
||||||
// Check clock modifier for clock tick
|
|
||||||
if ((audc1 & 0x02) == 0 ||
|
|
||||||
((audc1 & 0x01) == 0 && Div31[p5_1]) ||
|
|
||||||
((audc1 & 0x01) == 1 && Bit5[p5_1]) ||
|
|
||||||
((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5))
|
|
||||||
{
|
|
||||||
if (audc1 & 0x04) // Pure modified clock selected
|
|
||||||
{
|
|
||||||
if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
|
||||||
{
|
|
||||||
if ( Bit5[p5_1] != prev_bit5 )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[1]--;
|
|
||||||
if ( ! myDiv3Cnt[1] )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[1] = 3;
|
|
||||||
v1 = v1 ? 0 : audv1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the output was set turn it off, else turn it on
|
|
||||||
v1 = v1 ? 0 : audv1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (audc1 & 0x08) // Check for p5/p9
|
|
||||||
{
|
|
||||||
if (audc1 == POLY9) // Check for poly9
|
|
||||||
{
|
|
||||||
// Increase the poly9 counter
|
|
||||||
myP9[1]++;
|
|
||||||
if (myP9[1] == POLY9_SIZE)
|
|
||||||
myP9[1] = 0;
|
|
||||||
|
|
||||||
v1 = Bit9[myP9[1]] ? audv1 : 0;
|
|
||||||
}
|
|
||||||
else if ( audc1 & 0x02 )
|
|
||||||
{
|
|
||||||
v1 = (v1 || audc1 & 0x01) ? 0 : audv1;
|
|
||||||
}
|
|
||||||
else // Must be poly5
|
|
||||||
{
|
|
||||||
v1 = Bit5[p5_1] ? audv1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Poly4 is the only remaining option
|
|
||||||
{
|
|
||||||
// Increase the poly4 counter
|
|
||||||
myP4[1]++;
|
|
||||||
if (myP4[1] == POLY4_SIZE)
|
|
||||||
myP4[1] = 0;
|
|
||||||
|
|
||||||
v1 = Bit4[myP4[1]] ? audv1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
short byte = v0 + v1;
|
|
||||||
switch(myChannelMode)
|
|
||||||
{
|
|
||||||
case Hardware2Mono: // mono sampling with 2 hardware channels
|
|
||||||
*(buffer++) = byte;
|
|
||||||
*(buffer++) = byte;
|
|
||||||
samples--;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Hardware2Stereo: // stereo sampling with 2 hardware channels
|
|
||||||
*(buffer++) = v0;
|
|
||||||
*(buffer++) = v1;
|
|
||||||
samples--;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Hardware1: // mono/stereo sampling with only 1 hardware channel
|
|
||||||
*(buffer++) = (v0 + v1) >> 1;
|
|
||||||
samples--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oscBuf!=NULL) {
|
|
||||||
oscBuf[0]->data[oscBuf[0]->needle++]=v0;
|
|
||||||
oscBuf[1]->data[oscBuf[1]->needle++]=v1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save for next round
|
|
||||||
myP5[0] = p5_0;
|
|
||||||
myP5[1] = p5_1;
|
|
||||||
myVolume[0] = v0;
|
|
||||||
myVolume[1] = v1;
|
|
||||||
myDivNCnt[0] = div_n_cnt0;
|
|
||||||
myDivNCnt[1] = div_n_cnt1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1)
|
|
||||||
{
|
|
||||||
int mask = (1 << size) - 1, x = mask;
|
|
||||||
|
|
||||||
for(int i = 0; i < mask; i++)
|
|
||||||
{
|
|
||||||
int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01;
|
|
||||||
int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01;
|
|
||||||
poly[i] = x & 1;
|
|
||||||
// calculate next bit
|
|
||||||
x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
const unsigned char TIASound::Div31[POLY5_SIZE] = {
|
|
||||||
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
};
|
|
|
@ -1,186 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#ifndef TIASOUND_HXX
|
|
||||||
#define TIASOUND_HXX
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "../../../dispatch.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
This class implements a fairly accurate emulation of the TIA sound
|
|
||||||
hardware. This class uses code/ideas from z26 and MESS.
|
|
||||||
|
|
||||||
Currently, the sound generation routines work at 31400Hz only.
|
|
||||||
Resampling can be done by passing in a different output frequency.
|
|
||||||
|
|
||||||
@author Bradford W. Mott, Stephen Anthony, z26 and MESS teams
|
|
||||||
@version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
*/
|
|
||||||
class TIASound
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Create a new TIA Sound object using the specified output frequency
|
|
||||||
*/
|
|
||||||
TIASound(int outputFrequency = 31400);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Reset the sound emulation to its power-on state
|
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set the frequency output samples should be generated at
|
|
||||||
*/
|
|
||||||
void outputFrequency(int freq);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Selects the number of audio channels per sample. There are two factors
|
|
||||||
to consider: hardware capability and desired mixing.
|
|
||||||
|
|
||||||
@param hardware The number of channels supported by the sound system
|
|
||||||
@param stereo Whether to output the internal sound signals into 1
|
|
||||||
or 2 channels
|
|
||||||
|
|
||||||
@return Status of the channel configuration used
|
|
||||||
*/
|
|
||||||
std::string channels(unsigned int hardware, bool stereo);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Sets the specified sound register to the given value
|
|
||||||
|
|
||||||
@param address Register address
|
|
||||||
@param value Value to store in the register
|
|
||||||
*/
|
|
||||||
void set(unsigned short address, unsigned char value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Gets the specified sound register's value
|
|
||||||
|
|
||||||
@param address Register address
|
|
||||||
*/
|
|
||||||
unsigned char get(unsigned short address) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create sound samples based on the current sound register settings
|
|
||||||
in the specified buffer. NOTE: If channels is set to stereo then
|
|
||||||
the buffer will need to be twice as long as the number of samples.
|
|
||||||
|
|
||||||
@param buffer The location to store generated samples
|
|
||||||
@param samples The number of samples to generate
|
|
||||||
*/
|
|
||||||
void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set the volume of the samples created (0-100)
|
|
||||||
*/
|
|
||||||
void volume(unsigned int percent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void polyInit(unsigned char* poly, int size, int f0, int f1);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Definitions for AUDCx (15, 16)
|
|
||||||
enum AUDCxRegister
|
|
||||||
{
|
|
||||||
SET_TO_1 = 0x00, // 0000
|
|
||||||
POLY4 = 0x01, // 0001
|
|
||||||
DIV31_POLY4 = 0x02, // 0010
|
|
||||||
POLY5_POLY4 = 0x03, // 0011
|
|
||||||
PURE1 = 0x04, // 0100
|
|
||||||
PURE2 = 0x05, // 0101
|
|
||||||
DIV31_PURE = 0x06, // 0110
|
|
||||||
POLY5_2 = 0x07, // 0111
|
|
||||||
POLY9 = 0x08, // 1000
|
|
||||||
POLY5 = 0x09, // 1001
|
|
||||||
DIV31_POLY5 = 0x0a, // 1010
|
|
||||||
POLY5_POLY5 = 0x0b, // 1011
|
|
||||||
DIV3_PURE = 0x0c, // 1100
|
|
||||||
DIV3_PURE2 = 0x0d, // 1101
|
|
||||||
DIV93_PURE = 0x0e, // 1110
|
|
||||||
POLY5_DIV3 = 0x0f // 1111
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
POLY4_SIZE = 0x000f,
|
|
||||||
POLY5_SIZE = 0x001f,
|
|
||||||
POLY9_SIZE = 0x01ff,
|
|
||||||
DIV3_MASK = 0x0c,
|
|
||||||
AUDV_SHIFT = 10 // shift 2 positions for AUDV,
|
|
||||||
// then another 8 for 16-bit sound
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ChannelMode {
|
|
||||||
Hardware2Mono, // mono sampling with 2 hardware channels
|
|
||||||
Hardware2Stereo, // stereo sampling with 2 hardware channels
|
|
||||||
Hardware1 // mono/stereo sampling with only 1 hardware channel
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Structures to hold the 6 tia sound control bytes
|
|
||||||
unsigned char myAUDC[2]; // AUDCx (15, 16)
|
|
||||||
unsigned char myAUDF[2]; // AUDFx (17, 18)
|
|
||||||
short myAUDV[2]; // AUDVx (19, 1A)
|
|
||||||
|
|
||||||
short myVolume[2]; // Last output volume for each channel
|
|
||||||
|
|
||||||
unsigned char myP4[2]; // Position pointer for the 4-bit POLY array
|
|
||||||
unsigned char myP5[2]; // Position pointer for the 5-bit POLY array
|
|
||||||
unsigned short myP9[2]; // Position pointer for the 9-bit POLY array
|
|
||||||
|
|
||||||
unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel
|
|
||||||
unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel
|
|
||||||
unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode
|
|
||||||
|
|
||||||
ChannelMode myChannelMode;
|
|
||||||
int myOutputFrequency;
|
|
||||||
unsigned int myVolumePercentage;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialize the bit patterns for the polynomials (at runtime).
|
|
||||||
|
|
||||||
The 4bit and 5bit patterns are the identical ones used in the tia chip.
|
|
||||||
Though the patterns could be packed with 8 bits per byte, using only a
|
|
||||||
single bit per byte keeps the math simple, which is important for
|
|
||||||
efficient processing.
|
|
||||||
*/
|
|
||||||
unsigned char Bit4[POLY4_SIZE];
|
|
||||||
unsigned char Bit5[POLY5_SIZE];
|
|
||||||
unsigned char Bit9[POLY9_SIZE];
|
|
||||||
|
|
||||||
/*
|
|
||||||
The 'Div by 31' counter is treated as another polynomial because of
|
|
||||||
the way it operates. It does not have a 50% duty cycle, but instead
|
|
||||||
has a 13:18 ratio (of course, 13+18 = 31). This could also be
|
|
||||||
implemented by using counters.
|
|
||||||
*/
|
|
||||||
static const unsigned char Div31[POLY5_SIZE];
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Following constructors and assignment operators not supported
|
|
||||||
TIASound(const TIASound&) = delete;
|
|
||||||
TIASound(TIASound&&) = delete;
|
|
||||||
TIASound& operator=(const TIASound&) = delete;
|
|
||||||
TIASound& operator=(TIASound&&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,219 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#ifndef TIA_TABLES_HXX
|
|
||||||
#define TIA_TABLES_HXX
|
|
||||||
|
|
||||||
enum TIABit {
|
|
||||||
P0Bit = 0x01, // Bit for Player 0
|
|
||||||
M0Bit = 0x02, // Bit for Missle 0
|
|
||||||
P1Bit = 0x04, // Bit for Player 1
|
|
||||||
M1Bit = 0x08, // Bit for Missle 1
|
|
||||||
BLBit = 0x10, // Bit for Ball
|
|
||||||
PFBit = 0x20, // Bit for Playfield
|
|
||||||
ScoreBit = 0x40, // Bit for Playfield score mode
|
|
||||||
PriorityBit = 0x80 // Bit for Playfield priority
|
|
||||||
};
|
|
||||||
|
|
||||||
enum TIAColor {
|
|
||||||
BKColor = 0, // Color index for Background
|
|
||||||
PFColor = 1, // Color index for Playfield
|
|
||||||
P0Color = 2, // Color index for Player 0
|
|
||||||
P1Color = 3, // Color index for Player 1
|
|
||||||
M0Color = 4, // Color index for Missle 0
|
|
||||||
M1Color = 5, // Color index for Missle 1
|
|
||||||
BLColor = 6, // Color index for Ball
|
|
||||||
HBLANKColor = 7 // Color index for HMove blank area
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CollisionBit
|
|
||||||
{
|
|
||||||
Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision
|
|
||||||
Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision
|
|
||||||
Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision
|
|
||||||
Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision
|
|
||||||
Cx_P0PF = 1 << 4, // Player0 - Playfield collision
|
|
||||||
Cx_P0BL = 1 << 5, // Player0 - Ball collision
|
|
||||||
Cx_P1PF = 1 << 6, // Player1 - Playfield collision
|
|
||||||
Cx_P1BL = 1 << 7, // Player1 - Ball collision
|
|
||||||
Cx_M0PF = 1 << 8, // Missle0 - Playfield collision
|
|
||||||
Cx_M0BL = 1 << 9, // Missle0 - Ball collision
|
|
||||||
Cx_M1PF = 1 << 10, // Missle1 - Playfield collision
|
|
||||||
Cx_M1BL = 1 << 11, // Missle1 - Ball collision
|
|
||||||
Cx_BLPF = 1 << 12, // Ball - Playfield collision
|
|
||||||
Cx_P0P1 = 1 << 13, // Player0 - Player1 collision
|
|
||||||
Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision
|
|
||||||
};
|
|
||||||
|
|
||||||
// TIA Write/Read register names
|
|
||||||
enum TIARegister {
|
|
||||||
VSYNC = 0x00, // Write: vertical sync set-clear (D1)
|
|
||||||
VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1)
|
|
||||||
WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe)
|
|
||||||
RSYNC = 0x03, // Write: reset hrz. sync counter (strobe)
|
|
||||||
NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0)
|
|
||||||
NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0)
|
|
||||||
COLUP0 = 0x06, // Write: color-lum player 0 (D7-1)
|
|
||||||
COLUP1 = 0x07, // Write: color-lum player 1 (D7-1)
|
|
||||||
COLUPF = 0x08, // Write: color-lum playfield (D7-1)
|
|
||||||
COLUBK = 0x09, // Write: color-lum background (D7-1)
|
|
||||||
CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0)
|
|
||||||
REFP0 = 0x0b, // Write: reflect player 0 (D3)
|
|
||||||
REFP1 = 0x0c, // Write: reflect player 1 (D3)
|
|
||||||
PF0 = 0x0d, // Write: playfield register byte 0 (D7-4)
|
|
||||||
PF1 = 0x0e, // Write: playfield register byte 1 (D7-0)
|
|
||||||
PF2 = 0x0f, // Write: playfield register byte 2 (D7-0)
|
|
||||||
RESP0 = 0x10, // Write: reset player 0 (strobe)
|
|
||||||
RESP1 = 0x11, // Write: reset player 1 (strobe)
|
|
||||||
RESM0 = 0x12, // Write: reset missle 0 (strobe)
|
|
||||||
RESM1 = 0x13, // Write: reset missle 1 (strobe)
|
|
||||||
RESBL = 0x14, // Write: reset ball (strobe)
|
|
||||||
AUDC0 = 0x15, // Write: audio control 0 (D3-0)
|
|
||||||
AUDC1 = 0x16, // Write: audio control 1 (D4-0)
|
|
||||||
AUDF0 = 0x17, // Write: audio frequency 0 (D4-0)
|
|
||||||
AUDF1 = 0x18, // Write: audio frequency 1 (D3-0)
|
|
||||||
AUDV0 = 0x19, // Write: audio volume 0 (D3-0)
|
|
||||||
AUDV1 = 0x1a, // Write: audio volume 1 (D3-0)
|
|
||||||
GRP0 = 0x1b, // Write: graphics player 0 (D7-0)
|
|
||||||
GRP1 = 0x1c, // Write: graphics player 1 (D7-0)
|
|
||||||
ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1)
|
|
||||||
ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1)
|
|
||||||
ENABL = 0x1f, // Write: graphics (enable) ball (D1)
|
|
||||||
HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4)
|
|
||||||
HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4)
|
|
||||||
HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4)
|
|
||||||
HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4)
|
|
||||||
HMBL = 0x24, // Write: horizontal motion ball (D7-4)
|
|
||||||
VDELP0 = 0x25, // Write: vertical delay player 0 (D0)
|
|
||||||
VDELP1 = 0x26, // Write: vertical delay player 1 (D0)
|
|
||||||
VDELBL = 0x27, // Write: vertical delay ball (D0)
|
|
||||||
RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1)
|
|
||||||
RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1)
|
|
||||||
HMOVE = 0x2a, // Write: apply horizontal motion (strobe)
|
|
||||||
HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe)
|
|
||||||
CXCLR = 0x2c, // Write: clear collision latches (strobe)
|
|
||||||
|
|
||||||
CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0)
|
|
||||||
CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1)
|
|
||||||
CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL)
|
|
||||||
CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL)
|
|
||||||
CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL)
|
|
||||||
CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL)
|
|
||||||
CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused)
|
|
||||||
CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1)
|
|
||||||
INPT0 = 0x08, // Read pot port: D7
|
|
||||||
INPT1 = 0x09, // Read pot port: D7
|
|
||||||
INPT2 = 0x0a, // Read pot port: D7
|
|
||||||
INPT3 = 0x0b, // Read pot port: D7
|
|
||||||
INPT4 = 0x0c, // Read P1 joystick trigger: D7
|
|
||||||
INPT5 = 0x0d // Read P2 joystick trigger: D7
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
The TIA class uses some static tables that aren't dependent on the actual
|
|
||||||
TIA state. For code organization, it's better to place that functionality
|
|
||||||
here.
|
|
||||||
|
|
||||||
@author Stephen Anthony
|
|
||||||
@version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
*/
|
|
||||||
class TIATables
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Compute all static tables used by the TIA
|
|
||||||
*/
|
|
||||||
static void computeAllTables();
|
|
||||||
|
|
||||||
// Player mask table
|
|
||||||
// [suppress mode][nusiz][pixel]
|
|
||||||
static unsigned char PxMask[2][8][320];
|
|
||||||
|
|
||||||
// Missle mask table (entries are true or false)
|
|
||||||
// [number][size][pixel]
|
|
||||||
// There are actually only 4 possible size combinations on a real system
|
|
||||||
// The fifth size is used for simulating the starfield effect in
|
|
||||||
// Cosmic Ark and Stay Frosty
|
|
||||||
static unsigned char MxMask[8][5][320];
|
|
||||||
|
|
||||||
// Ball mask table (entries are true or false)
|
|
||||||
// [size][pixel]
|
|
||||||
static unsigned char BLMask[4][320];
|
|
||||||
|
|
||||||
// Playfield mask table for reflected and non-reflected playfields
|
|
||||||
// [reflect, pixel]
|
|
||||||
static unsigned int PFMask[2][160];
|
|
||||||
|
|
||||||
// A mask table which can be used when an object is disabled
|
|
||||||
static unsigned char DisabledMask[640];
|
|
||||||
|
|
||||||
// Used to set the collision register to the correct value
|
|
||||||
static unsigned short CollisionMask[64];
|
|
||||||
|
|
||||||
// Indicates the update delay associated with poking at a TIA address
|
|
||||||
static const short PokeDelay[64];
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Used to convert value written in a motion register into
|
|
||||||
// its internal representation
|
|
||||||
static const int CompleteMotion[76][16];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Indicates if HMOVE blanks should occur for the corresponding cycle
|
|
||||||
static const bool HMOVEBlankEnableCycles[76];
|
|
||||||
|
|
||||||
// Used to reflect a players graphics
|
|
||||||
static unsigned char GRPReflect[256];
|
|
||||||
|
|
||||||
// Indicates if player is being reset during delay, display or other times
|
|
||||||
// [nusiz][old pixel][new pixel]
|
|
||||||
static signed char PxPosResetWhen[8][160][160];
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Compute the collision decode table
|
|
||||||
static void buildCollisionMaskTable();
|
|
||||||
|
|
||||||
// Compute the player mask table
|
|
||||||
static void buildPxMaskTable();
|
|
||||||
|
|
||||||
// Compute the missle mask table
|
|
||||||
static void buildMxMaskTable();
|
|
||||||
|
|
||||||
// Compute the ball mask table
|
|
||||||
static void buildBLMaskTable();
|
|
||||||
|
|
||||||
// Compute playfield mask table
|
|
||||||
static void buildPFMaskTable();
|
|
||||||
|
|
||||||
// Compute the player reflect table
|
|
||||||
static void buildGRPReflectTable();
|
|
||||||
|
|
||||||
// Compute the player position reset when table
|
|
||||||
static void buildPxPosResetWhenTable();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Following constructors and assignment operators not supported
|
|
||||||
TIATables() = delete;
|
|
||||||
TIATables(const TIATables&) = delete;
|
|
||||||
TIATables(TIATables&&) = delete;
|
|
||||||
TIATables& operator=(const TIATables&) = delete;
|
|
||||||
TIATables& operator=(TIATables&&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3
|
||||||
|
|
||||||
// note from tildearrow:
|
// note from tildearrow:
|
||||||
// - are you kidding? I have to write to this "load preset" register before keying on?
|
// - are you kidding? I have to write to this "load preset" register before keying on?
|
||||||
|
// another note from tildearrow:
|
||||||
|
// - see https://github.com/110-kenichi/ymfm/blob/main/src/ymfm_opz.cpp
|
||||||
|
// - is 0x08 the actual key on register just like OPM?
|
||||||
|
// - if so then what's bit 5?
|
||||||
if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/)
|
if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/)
|
||||||
{
|
{
|
||||||
channel = bitfield(index, 0, 3);
|
channel = bitfield(index, 0, 3);
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
|
#define rWrite(a,v) if (!skipRegisterWrites) {tia.write(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||||
|
|
||||||
const char* regCheatSheetTIA[]={
|
const char* regCheatSheetTIA[]={
|
||||||
"AUDC0", "15",
|
"AUDC0", "15",
|
||||||
|
@ -39,7 +39,22 @@ const char** DivPlatformTIA::getRegisterSheet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
tia.process(bufL+start,len,oscBuf);
|
for (size_t h=start; h<start+len; h++) {
|
||||||
|
tia.tick();
|
||||||
|
if (mixingType==2) {
|
||||||
|
bufL[h]=tia.myCurrentSample[0];
|
||||||
|
bufR[h]=tia.myCurrentSample[1];
|
||||||
|
} else if (mixingType==1) {
|
||||||
|
bufL[h]=(tia.myCurrentSample[0]+tia.myCurrentSample[1])>>1;
|
||||||
|
} else {
|
||||||
|
bufL[h]=tia.myCurrentSample[0];
|
||||||
|
}
|
||||||
|
if (++chanOscCounter>=114) {
|
||||||
|
chanOscCounter=0;
|
||||||
|
oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0];
|
||||||
|
oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) {
|
unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) {
|
||||||
|
@ -300,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformTIA::reset() {
|
void DivPlatformTIA::reset() {
|
||||||
tia.reset();
|
tia.reset(mixingType);
|
||||||
memset(regPool,0,16);
|
memset(regPool,0,16);
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
chan[i]=DivPlatformTIA::Channel();
|
chan[i]=DivPlatformTIA::Channel();
|
||||||
|
@ -309,8 +324,12 @@ void DivPlatformTIA::reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float DivPlatformTIA::getPostAmp() {
|
||||||
|
return 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
bool DivPlatformTIA::isStereo() {
|
bool DivPlatformTIA::isStereo() {
|
||||||
return false;
|
return (mixingType==2);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
|
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
|
||||||
|
@ -333,25 +352,28 @@ void DivPlatformTIA::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
|
|
||||||
void DivPlatformTIA::setFlags(unsigned int flags) {
|
void DivPlatformTIA::setFlags(unsigned int flags) {
|
||||||
if (flags&1) {
|
if (flags&1) {
|
||||||
rate=31250;
|
rate=COLOR_PAL*4.0/5.0;
|
||||||
} else {
|
} else {
|
||||||
rate=31468;
|
rate=COLOR_NTSC;
|
||||||
}
|
}
|
||||||
chipClock=rate;
|
chipClock=rate;
|
||||||
|
mixingType=(flags>>1)&3;
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
oscBuf[i]->rate=rate;
|
oscBuf[i]->rate=rate/114;
|
||||||
}
|
}
|
||||||
|
tia.reset(mixingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
parent=p;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
|
mixingType=0;
|
||||||
|
chanOscCounter=0;
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
isMuted[i]=false;
|
isMuted[i]=false;
|
||||||
oscBuf[i]=new DivDispatchOscBuffer;
|
oscBuf[i]=new DivDispatchOscBuffer;
|
||||||
}
|
}
|
||||||
tia.channels(1,false);
|
|
||||||
setFlags(flags);
|
setFlags(flags);
|
||||||
reset();
|
reset();
|
||||||
return 2;
|
return 2;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
#include "../dispatch.h"
|
#include "../dispatch.h"
|
||||||
#include "../macroInt.h"
|
#include "../macroInt.h"
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include "sound/tia/TIASnd.h"
|
#include "sound/tia/Audio.h"
|
||||||
|
|
||||||
class DivPlatformTIA: public DivDispatch {
|
class DivPlatformTIA: public DivDispatch {
|
||||||
protected:
|
protected:
|
||||||
|
@ -42,7 +42,9 @@ class DivPlatformTIA: public DivDispatch {
|
||||||
Channel chan[2];
|
Channel chan[2];
|
||||||
DivDispatchOscBuffer* oscBuf[2];
|
DivDispatchOscBuffer* oscBuf[2];
|
||||||
bool isMuted[2];
|
bool isMuted[2];
|
||||||
TIASound tia;
|
unsigned char mixingType;
|
||||||
|
unsigned char chanOscCounter;
|
||||||
|
TIA::Audio tia;
|
||||||
unsigned char regPool[16];
|
unsigned char regPool[16];
|
||||||
friend void putDispatchChan(void*,int,int);
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
||||||
|
@ -61,6 +63,7 @@ class DivPlatformTIA: public DivDispatch {
|
||||||
void tick(bool sysTick=true);
|
void tick(bool sysTick=true);
|
||||||
void muteChannel(int ch, bool mute);
|
void muteChannel(int ch, bool mute);
|
||||||
void setFlags(unsigned int flags);
|
void setFlags(unsigned int flags);
|
||||||
|
float getPostAmp();
|
||||||
bool isStereo();
|
bool isStereo();
|
||||||
bool keyOffAffectsArp(int ch);
|
bool keyOffAffectsArp(int ch);
|
||||||
void notifyInsDeletion(void* ins);
|
void notifyInsDeletion(void* ins);
|
||||||
|
|
|
@ -906,6 +906,7 @@ void DivPlatformTX81Z::reset() {
|
||||||
pmDepth=0x7f;
|
pmDepth=0x7f;
|
||||||
|
|
||||||
//rWrite(0x18,0x10);
|
//rWrite(0x18,0x10);
|
||||||
|
immWrite(0x18,0x00); // LFO Freq Off
|
||||||
immWrite(0x19,amDepth);
|
immWrite(0x19,amDepth);
|
||||||
immWrite(0x19,0x80|pmDepth);
|
immWrite(0x19,0x80|pmDepth);
|
||||||
//rWrite(0x1b,0x00);
|
//rWrite(0x1b,0x00);
|
||||||
|
|
|
@ -273,6 +273,10 @@ void DivPlatformYM2203::tick(bool sysTick) {
|
||||||
chan[i].state.fb=chan[i].std.fb.val;
|
chan[i].state.fb=chan[i].std.fb.val;
|
||||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -385,8 +389,9 @@ void DivPlatformYM2203::tick(bool sysTick) {
|
||||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
immWrite(0x28,0xf0|konOffs[i]);
|
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,6 +414,11 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
|
|
|
@ -43,9 +43,9 @@ class DivPlatformYM2203: public DivPlatformOPN {
|
||||||
DivInstrumentFM state;
|
DivInstrumentFM state;
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
unsigned char psgMode, autoEnvNum, autoEnvDen, opMask;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
int sample;
|
int sample;
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
|
@ -66,6 +66,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
|
||||||
psgMode(1),
|
psgMode(1),
|
||||||
autoEnvNum(0),
|
autoEnvNum(0),
|
||||||
autoEnvDen(0),
|
autoEnvDen(0),
|
||||||
|
opMask(15),
|
||||||
active(false),
|
active(false),
|
||||||
insChanged(true),
|
insChanged(true),
|
||||||
freqChanged(false),
|
freqChanged(false),
|
||||||
|
@ -75,6 +76,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
furnacePCM(false),
|
furnacePCM(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(15),
|
outVol(15),
|
||||||
sample(-1) {}
|
sample(-1) {}
|
||||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+0x70,op.d2r&31);
|
rWrite(baseAddr+0x70,op.d2r&31);
|
||||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||||
|
opChan[ch].mask=op.enable;
|
||||||
}
|
}
|
||||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
|
||||||
bool writeSomething=false;
|
bool writeSomething=false;
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||||
writeSomething=true;
|
writeSomething=true;
|
||||||
writeMask&=~(1<<(4+i));
|
writeMask&=~(1<<(4+i));
|
||||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
writeNoteOn=true;
|
writeNoteOn=true;
|
||||||
writeMask|=1<<(4+i);
|
if (opChan[i].mask) {
|
||||||
|
writeMask|=1<<(4+i);
|
||||||
|
}
|
||||||
opChan[i].keyOn=false;
|
opChan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
// UGLY
|
// UGLY
|
||||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
||||||
inPorta(false), vol(0), pan(3) {}
|
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||||
};
|
};
|
||||||
OpChannel opChan[4];
|
OpChannel opChan[4];
|
||||||
bool isOpMuted[4];
|
bool isOpMuted[4];
|
||||||
|
|
|
@ -441,6 +441,10 @@ void DivPlatformYM2608::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -581,8 +585,9 @@ void DivPlatformYM2608::tick(bool sysTick) {
|
||||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
immWrite(0x28,0xf0|konOffs[i]);
|
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -683,6 +688,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
|
|
|
@ -48,9 +48,9 @@ class DivPlatformYM2608: public DivPlatformOPN {
|
||||||
DivInstrumentFM state;
|
DivInstrumentFM state;
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
unsigned char psgMode, autoEnvNum, autoEnvDen, opMask;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
int sample;
|
int sample;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
|
@ -72,6 +72,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
|
||||||
psgMode(1),
|
psgMode(1),
|
||||||
autoEnvNum(0),
|
autoEnvNum(0),
|
||||||
autoEnvDen(0),
|
autoEnvDen(0),
|
||||||
|
opMask(15),
|
||||||
active(false),
|
active(false),
|
||||||
insChanged(true),
|
insChanged(true),
|
||||||
freqChanged(false),
|
freqChanged(false),
|
||||||
|
@ -81,6 +82,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
furnacePCM(false),
|
furnacePCM(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(15),
|
outVol(15),
|
||||||
sample(-1),
|
sample(-1),
|
||||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+0x70,op.d2r&31);
|
rWrite(baseAddr+0x70,op.d2r&31);
|
||||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||||
|
opChan[ch].mask=op.enable;
|
||||||
}
|
}
|
||||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
|
||||||
bool writeSomething=false;
|
bool writeSomething=false;
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||||
writeSomething=true;
|
writeSomething=true;
|
||||||
writeMask&=~(1<<(4+i));
|
writeMask&=~(1<<(4+i));
|
||||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
writeNoteOn=true;
|
writeNoteOn=true;
|
||||||
writeMask|=1<<(4+i);
|
if (opChan[i].mask) {
|
||||||
|
writeMask|=1<<(4+i);
|
||||||
|
}
|
||||||
opChan[i].keyOn=false;
|
opChan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
// UGLY
|
// UGLY
|
||||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
||||||
inPorta(false), vol(0), pan(3) {}
|
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||||
};
|
};
|
||||||
OpChannel opChan[4];
|
OpChannel opChan[4];
|
||||||
bool isOpMuted[4];
|
bool isOpMuted[4];
|
||||||
|
|
|
@ -482,6 +482,10 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -618,8 +622,9 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
||||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
immWrite(0x28,0xf0|konOffs[i]);
|
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -727,6 +732,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
|
|
|
@ -73,10 +73,10 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
int sample;
|
int sample;
|
||||||
unsigned char pan;
|
unsigned char pan, opMask;
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
void macroInit(DivInstrument* which) {
|
void macroInit(DivInstrument* which) {
|
||||||
std.init(which);
|
std.init(which);
|
||||||
|
@ -104,10 +104,12 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
furnacePCM(false),
|
furnacePCM(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(15),
|
outVol(15),
|
||||||
sample(-1),
|
sample(-1),
|
||||||
pan(3) {}
|
pan(3),
|
||||||
|
opMask(15) {}
|
||||||
};
|
};
|
||||||
Channel chan[14];
|
Channel chan[14];
|
||||||
DivDispatchOscBuffer* oscBuf[14];
|
DivDispatchOscBuffer* oscBuf[14];
|
||||||
|
|
|
@ -465,6 +465,10 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -600,8 +604,9 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
||||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
immWrite(0x28,0xf0|konOffs[i]);
|
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -709,6 +714,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
|
|
|
@ -40,10 +40,10 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
int sample;
|
int sample;
|
||||||
unsigned char pan;
|
unsigned char pan, opMask;
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
void macroInit(DivInstrument* which) {
|
void macroInit(DivInstrument* which) {
|
||||||
std.init(which);
|
std.init(which);
|
||||||
|
@ -71,10 +71,12 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
furnacePCM(false),
|
furnacePCM(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(15),
|
outVol(15),
|
||||||
sample(-1),
|
sample(-1),
|
||||||
pan(3) {}
|
pan(3),
|
||||||
|
opMask(15) {}
|
||||||
};
|
};
|
||||||
Channel chan[16];
|
Channel chan[16];
|
||||||
DivDispatchOscBuffer* oscBuf[16];
|
DivDispatchOscBuffer* oscBuf[16];
|
||||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+0x70,op.d2r&31);
|
rWrite(baseAddr+0x70,op.d2r&31);
|
||||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||||
|
opChan[ch].mask=op.enable;
|
||||||
}
|
}
|
||||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
|
||||||
bool writeSomething=false;
|
bool writeSomething=false;
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||||
writeSomething=true;
|
writeSomething=true;
|
||||||
writeMask&=~(1<<(4+i));
|
writeMask&=~(1<<(4+i));
|
||||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
writeNoteOn=true;
|
writeNoteOn=true;
|
||||||
writeMask|=1<<(4+i);
|
if (opChan[i].mask) {
|
||||||
|
writeMask|=1<<(4+i);
|
||||||
|
}
|
||||||
opChan[i].keyOn=false;
|
opChan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
// UGLY
|
// UGLY
|
||||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
||||||
inPorta(false), vol(0), pan(3) {}
|
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||||
};
|
};
|
||||||
OpChannel opChan[4];
|
OpChannel opChan[4];
|
||||||
bool isOpMuted[4];
|
bool isOpMuted[4];
|
||||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+0x70,op.d2r&31);
|
rWrite(baseAddr+0x70,op.d2r&31);
|
||||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||||
|
opChan[ch].mask=op.enable;
|
||||||
}
|
}
|
||||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||||
rWrite(chanOffs[1]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
rWrite(chanOffs[1]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
|
||||||
bool writeSomething=false;
|
bool writeSomething=false;
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||||
writeSomething=true;
|
writeSomething=true;
|
||||||
writeMask&=~(1<<(4+i));
|
writeMask&=~(1<<(4+i));
|
||||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
writeNoteOn=true;
|
writeNoteOn=true;
|
||||||
writeMask|=1<<(4+i);
|
if (opChan[i].mask) {
|
||||||
|
writeMask|=1<<(4+i);
|
||||||
|
}
|
||||||
opChan[i].keyOn=false;
|
opChan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
// UGLY
|
// UGLY
|
||||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false),
|
||||||
inPorta(false), vol(0), pan(3) {}
|
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||||
};
|
};
|
||||||
OpChannel opChan[4];
|
OpChannel opChan[4];
|
||||||
bool isOpMuted[4];
|
bool isOpMuted[4];
|
||||||
|
|
|
@ -31,7 +31,9 @@ void DivEngine::nextOrder() {
|
||||||
curRow=0;
|
curRow=0;
|
||||||
if (repeatPattern) return;
|
if (repeatPattern) return;
|
||||||
if (++curOrder>=curSubSong->ordersLen) {
|
if (++curOrder>=curSubSong->ordersLen) {
|
||||||
|
logV("end of orders reached");
|
||||||
endOfSong=true;
|
endOfSong=true;
|
||||||
|
memset(walked,0,8192);
|
||||||
curOrder=0;
|
curOrder=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,15 +350,31 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
if (effectVal>0) speed2=effectVal;
|
if (effectVal>0) speed2=effectVal;
|
||||||
break;
|
break;
|
||||||
case 0x0b: // change order
|
case 0x0b: // change order
|
||||||
if (changeOrd==-1) {
|
if (changeOrd==-1 || song.jumpTreatment==0) {
|
||||||
changeOrd=effectVal;
|
changeOrd=effectVal;
|
||||||
changePos=0;
|
if (song.jumpTreatment==1 || song.jumpTreatment==2) {
|
||||||
|
changePos=0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x0d: // next order
|
case 0x0d: // next order
|
||||||
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
if (song.jumpTreatment==2) {
|
||||||
changeOrd=-2;
|
if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||||
changePos=effectVal;
|
changeOrd=-2;
|
||||||
|
changePos=effectVal;
|
||||||
|
}
|
||||||
|
} else if (song.jumpTreatment==1) {
|
||||||
|
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||||
|
changeOrd=-2;
|
||||||
|
changePos=effectVal;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd) {
|
||||||
|
if (changeOrd<0) {
|
||||||
|
changeOrd=-2;
|
||||||
|
}
|
||||||
|
changePos=effectVal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0xed: // delay
|
case 0xed: // delay
|
||||||
|
@ -911,18 +929,23 @@ void DivEngine::nextRow() {
|
||||||
processRow(i,false);
|
processRow(i,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walked[((curOrder<<5)+(curRow>>3))&8191]|=1<<(curRow&7);
|
||||||
|
|
||||||
if (changeOrd!=-1) {
|
if (changeOrd!=-1) {
|
||||||
if (repeatPattern) {
|
if (repeatPattern) {
|
||||||
curRow=0;
|
curRow=0;
|
||||||
changeOrd=-1;
|
changeOrd=-1;
|
||||||
} else {
|
} else {
|
||||||
curRow=changePos;
|
curRow=changePos;
|
||||||
|
changePos=0;
|
||||||
if (changeOrd==-2) changeOrd=curOrder+1;
|
if (changeOrd==-2) changeOrd=curOrder+1;
|
||||||
if (changeOrd<=curOrder) endOfSong=true;
|
// old loop detection routine
|
||||||
|
//if (changeOrd<=curOrder) endOfSong=true;
|
||||||
curOrder=changeOrd;
|
curOrder=changeOrd;
|
||||||
if (curOrder>=curSubSong->ordersLen) {
|
if (curOrder>=curSubSong->ordersLen) {
|
||||||
curOrder=0;
|
curOrder=0;
|
||||||
endOfSong=true;
|
endOfSong=true;
|
||||||
|
memset(walked,0,8192);
|
||||||
}
|
}
|
||||||
changeOrd=-1;
|
changeOrd=-1;
|
||||||
}
|
}
|
||||||
|
@ -932,6 +955,13 @@ void DivEngine::nextRow() {
|
||||||
if (haltOn==DIV_HALT_PATTERN) halted=true;
|
if (haltOn==DIV_HALT_PATTERN) halted=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new loop detection routine
|
||||||
|
if (!endOfSong && walked[((curOrder<<5)+(curRow>>3))&8191]&(1<<(curRow&7))) {
|
||||||
|
logV("loop reached");
|
||||||
|
endOfSong=true;
|
||||||
|
memset(walked,0,8192);
|
||||||
|
}
|
||||||
|
|
||||||
if (song.brokenSpeedSel) {
|
if (song.brokenSpeedSel) {
|
||||||
if ((curSubSong->patLen&1) && curOrder&1) {
|
if ((curSubSong->patLen&1) && curOrder&1) {
|
||||||
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
|
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
|
||||||
|
|
|
@ -468,6 +468,11 @@ struct DivSong {
|
||||||
// 1: broken (don't allow value higher than speed)
|
// 1: broken (don't allow value higher than speed)
|
||||||
// 2: lax (allow value higher than speed)
|
// 2: lax (allow value higher than speed)
|
||||||
unsigned char delayBehavior;
|
unsigned char delayBehavior;
|
||||||
|
// 0B/0D treatment
|
||||||
|
// 0: normal (0B/0D accepted)
|
||||||
|
// 1: old Furnace (first one accepted)
|
||||||
|
// 2: DefleMask (0D takes priority over 0B)
|
||||||
|
unsigned char jumpTreatment;
|
||||||
bool properNoiseLayout;
|
bool properNoiseLayout;
|
||||||
bool waveDutyIsVol;
|
bool waveDutyIsVol;
|
||||||
bool resetMacroOnPorta;
|
bool resetMacroOnPorta;
|
||||||
|
@ -571,6 +576,7 @@ struct DivSong {
|
||||||
pitchSlideSpeed(4),
|
pitchSlideSpeed(4),
|
||||||
loopModality(2),
|
loopModality(2),
|
||||||
delayBehavior(2),
|
delayBehavior(2),
|
||||||
|
jumpTreatment(0),
|
||||||
properNoiseLayout(true),
|
properNoiseLayout(true),
|
||||||
waveDutyIsVol(false),
|
waveDutyIsVol(false),
|
||||||
resetMacroOnPorta(false),
|
resetMacroOnPorta(false),
|
||||||
|
|
|
@ -82,13 +82,15 @@ const char* aboutLine[]={
|
||||||
"Laggy",
|
"Laggy",
|
||||||
"LovelyA72",
|
"LovelyA72",
|
||||||
"LunaMoth",
|
"LunaMoth",
|
||||||
|
"Lunathir",
|
||||||
"LVintageNerd",
|
"LVintageNerd",
|
||||||
"Mahbod Karamoozian",
|
"Mahbod Karamoozian",
|
||||||
"Miker",
|
"Miker",
|
||||||
"nicco1690",
|
"nicco1690",
|
||||||
"NikonTeen",
|
"NikonTeen",
|
||||||
"psdominator",
|
"psxdominator",
|
||||||
"Raijin",
|
"Raijin",
|
||||||
|
"SnugglyBun",
|
||||||
"SuperJet Spade",
|
"SuperJet Spade",
|
||||||
"TheDuccinator",
|
"TheDuccinator",
|
||||||
"theloredev",
|
"theloredev",
|
||||||
|
@ -102,7 +104,7 @@ const char* aboutLine[]={
|
||||||
"fd",
|
"fd",
|
||||||
"GENATARi",
|
"GENATARi",
|
||||||
"host12prog",
|
"host12prog",
|
||||||
"lunathir",
|
"Lunathir",
|
||||||
"plane",
|
"plane",
|
||||||
"TheEssem",
|
"TheEssem",
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -213,6 +213,26 @@ void FurnaceGUI::drawCompatFlags() {
|
||||||
ImGui::SetTooltip("no checks (like FamiTracker)");
|
ImGui::SetTooltip("no checks (like FamiTracker)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Simultaneous jump (0B+0D) treatment:");
|
||||||
|
if (ImGui::RadioButton("Normal",e->song.jumpTreatment==0)) {
|
||||||
|
e->song.jumpTreatment=0;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("accept 0B+0D to jump to a specific row of an order");
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Old Furnace",e->song.jumpTreatment==1)) {
|
||||||
|
e->song.jumpTreatment=1;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("only accept the first jump effect");
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("DefleMask",e->song.jumpTreatment==2)) {
|
||||||
|
e->song.jumpTreatment=2;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("only accept 0Dxx");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions.");
|
ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions.");
|
||||||
|
|
|
@ -29,14 +29,20 @@ const char* sampleNote[12]={
|
||||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
|
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
|
||||||
};
|
};
|
||||||
|
|
||||||
void FurnaceGUI::drawInsList() {
|
void FurnaceGUI::drawInsList(bool asChild) {
|
||||||
if (nextWindow==GUI_WINDOW_INS_LIST) {
|
if (nextWindow==GUI_WINDOW_INS_LIST) {
|
||||||
insListOpen=true;
|
insListOpen=true;
|
||||||
ImGui::SetNextWindowFocus();
|
ImGui::SetNextWindowFocus();
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!insListOpen) return;
|
if (!insListOpen && !asChild) return;
|
||||||
if (ImGui::Begin("Instruments",&insListOpen,globalWinFlags)) {
|
bool began=false;
|
||||||
|
if (asChild) {
|
||||||
|
began=ImGui::BeginChild("Instruments");
|
||||||
|
} else {
|
||||||
|
began=ImGui::Begin("Instruments",&insListOpen,globalWinFlags);
|
||||||
|
}
|
||||||
|
if (began) {
|
||||||
if (settings.unifiedDataView) settings.horizontalDataView=0;
|
if (settings.unifiedDataView) settings.horizontalDataView=0;
|
||||||
if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) {
|
if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) {
|
||||||
if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD);
|
if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD);
|
||||||
|
@ -409,11 +415,15 @@ void FurnaceGUI::drawInsList() {
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST;
|
if (asChild) {
|
||||||
ImGui::End();
|
ImGui::EndChild();
|
||||||
|
} else {
|
||||||
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST;
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::drawWaveList() {
|
void FurnaceGUI::drawWaveList(bool asChild) {
|
||||||
if (nextWindow==GUI_WINDOW_WAVE_LIST) {
|
if (nextWindow==GUI_WINDOW_WAVE_LIST) {
|
||||||
waveListOpen=true;
|
waveListOpen=true;
|
||||||
if (settings.unifiedDataView) {
|
if (settings.unifiedDataView) {
|
||||||
|
@ -424,8 +434,14 @@ void FurnaceGUI::drawWaveList() {
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (settings.unifiedDataView) return;
|
if (settings.unifiedDataView) return;
|
||||||
if (!waveListOpen) return;
|
if (!waveListOpen && !asChild) return;
|
||||||
if (ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags)) {
|
bool began=false;
|
||||||
|
if (asChild) {
|
||||||
|
began=ImGui::BeginChild("Wavetables");
|
||||||
|
} else {
|
||||||
|
began=ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags);
|
||||||
|
}
|
||||||
|
if (began) {
|
||||||
if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) {
|
if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) {
|
||||||
doAction(GUI_ACTION_WAVE_LIST_ADD);
|
doAction(GUI_ACTION_WAVE_LIST_ADD);
|
||||||
}
|
}
|
||||||
|
@ -476,11 +492,15 @@ void FurnaceGUI::drawWaveList() {
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST;
|
if (asChild) {
|
||||||
ImGui::End();
|
ImGui::EndChild();
|
||||||
|
} else {
|
||||||
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST;
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::drawSampleList() {
|
void FurnaceGUI::drawSampleList(bool asChild) {
|
||||||
if (nextWindow==GUI_WINDOW_SAMPLE_LIST) {
|
if (nextWindow==GUI_WINDOW_SAMPLE_LIST) {
|
||||||
sampleListOpen=true;
|
sampleListOpen=true;
|
||||||
if (settings.unifiedDataView) {
|
if (settings.unifiedDataView) {
|
||||||
|
@ -491,8 +511,14 @@ void FurnaceGUI::drawSampleList() {
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (settings.unifiedDataView) return;
|
if (settings.unifiedDataView) return;
|
||||||
if (!sampleListOpen) return;
|
if (!sampleListOpen && !asChild) return;
|
||||||
if (ImGui::Begin("Samples",&sampleListOpen,globalWinFlags)) {
|
bool began=false;
|
||||||
|
if (asChild) {
|
||||||
|
began=ImGui::BeginChild("Samples");
|
||||||
|
} else {
|
||||||
|
began=ImGui::Begin("Samples",&sampleListOpen,globalWinFlags);
|
||||||
|
}
|
||||||
|
if (began) {
|
||||||
if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) {
|
if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) {
|
||||||
doAction(GUI_ACTION_SAMPLE_LIST_ADD);
|
doAction(GUI_ACTION_SAMPLE_LIST_ADD);
|
||||||
}
|
}
|
||||||
|
@ -548,8 +574,12 @@ void FurnaceGUI::drawSampleList() {
|
||||||
}
|
}
|
||||||
ImGui::Unindent();
|
ImGui::Unindent();
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST;
|
if (asChild) {
|
||||||
ImGui::End();
|
ImGui::EndChild();
|
||||||
|
} else {
|
||||||
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST;
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::actualWaveList() {
|
void FurnaceGUI::actualWaveList() {
|
||||||
|
|
|
@ -59,12 +59,12 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
|
|
||||||
if (!portrait) ImGui::Separator();
|
if (!portrait) ImGui::Separator();
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
pushToggleColors(e->isPlaying());
|
||||||
if (portrait) ImGui::SameLine();
|
if (portrait) ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) {
|
if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
if (portrait) ImGui::SameLine();
|
if (portrait) ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) {
|
if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) {
|
||||||
stop();
|
stop();
|
||||||
|
@ -76,27 +76,27 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool repeatPattern=e->getRepeatPattern();
|
bool repeatPattern=e->getRepeatPattern();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
pushToggleColors(repeatPattern);
|
||||||
if (portrait) ImGui::SameLine();
|
if (portrait) ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) {
|
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) {
|
||||||
e->setRepeatPattern(!repeatPattern);
|
e->setRepeatPattern(!repeatPattern);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
pushToggleColors(edit);
|
||||||
if (portrait) ImGui::SameLine();
|
if (portrait) ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) {
|
if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) {
|
||||||
edit=!edit;
|
edit=!edit;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
bool metro=e->getMetronome();
|
bool metro=e->getMetronome();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
pushToggleColors(metro);
|
||||||
if (portrait) ImGui::SameLine();
|
if (portrait) ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) {
|
if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) {
|
||||||
e->setMetronome(!metro);
|
e->setMetronome(!metro);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -104,79 +104,157 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f));
|
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f));
|
||||||
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale));
|
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale));
|
||||||
if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
||||||
ImGui::Button("Pattern");
|
if (ImGui::BeginTable("SceneSel",5)) {
|
||||||
ImGui::SameLine();
|
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||||
ImGui::Button("Ins");
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||||
ImGui::SameLine();
|
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||||
ImGui::Button("Wave");
|
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||||
ImGui::SameLine();
|
ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||||
ImGui::Button("Sample");
|
|
||||||
|
|
||||||
ImGui::Text("Data list goes here...");
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImVec2 buttonSize=ImGui::GetContentRegionAvail();
|
||||||
|
buttonSize.y=30.0f*dpiScale;
|
||||||
|
|
||||||
if (ImGui::Button("New")) {
|
if (ImGui::Button("Pattern",buttonSize)) {
|
||||||
mobileMenuOpen=false;
|
mobScene=GUI_SCENE_PATTERN;
|
||||||
//doAction(GUI_ACTION_NEW);
|
|
||||||
if (modified) {
|
|
||||||
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
|
|
||||||
} else {
|
|
||||||
displayNew=true;
|
|
||||||
}
|
}
|
||||||
}
|
ImGui::TableNextColumn();
|
||||||
ImGui::SameLine();
|
if (ImGui::Button("Orders",buttonSize)) {
|
||||||
if (ImGui::Button("Open")) {
|
mobScene=GUI_SCENE_ORDERS;
|
||||||
mobileMenuOpen=false;
|
}
|
||||||
doAction(GUI_ACTION_OPEN);
|
ImGui::TableNextColumn();
|
||||||
}
|
if (ImGui::Button("Ins",buttonSize)) {
|
||||||
ImGui::SameLine();
|
mobScene=GUI_SCENE_INSTRUMENT;
|
||||||
if (ImGui::Button("Save")) {
|
}
|
||||||
mobileMenuOpen=false;
|
ImGui::TableNextColumn();
|
||||||
doAction(GUI_ACTION_SAVE);
|
if (ImGui::Button("Wave",buttonSize)) {
|
||||||
}
|
mobScene=GUI_SCENE_WAVETABLE;
|
||||||
ImGui::SameLine();
|
}
|
||||||
if (ImGui::Button("Save as...")) {
|
ImGui::TableNextColumn();
|
||||||
mobileMenuOpen=false;
|
if (ImGui::Button("Sample",buttonSize)) {
|
||||||
doAction(GUI_ACTION_SAVE_AS);
|
mobScene=GUI_SCENE_SAMPLE;
|
||||||
}
|
}
|
||||||
|
ImGui::TableNextRow();
|
||||||
ImGui::Button("1.1+ .dmf");
|
ImGui::TableNextColumn();
|
||||||
ImGui::SameLine();
|
if (ImGui::Button("Song",buttonSize)) {
|
||||||
ImGui::Button("Legacy .dmf");
|
mobScene=GUI_SCENE_SONG;
|
||||||
ImGui::SameLine();
|
}
|
||||||
ImGui::Button("Export Audio");
|
ImGui::TableNextColumn();
|
||||||
ImGui::SameLine();
|
if (ImGui::Button("Channels",buttonSize)) {
|
||||||
ImGui::Button("Export VGM");
|
mobScene=GUI_SCENE_CHANNELS;
|
||||||
|
}
|
||||||
ImGui::Button("CmdStream");
|
ImGui::TableNextColumn();
|
||||||
ImGui::SameLine();
|
if (ImGui::Button("Chips",buttonSize)) {
|
||||||
ImGui::Button("Panic");
|
mobScene=GUI_SCENE_CHIPS;
|
||||||
ImGui::SameLine();
|
}
|
||||||
if (ImGui::Button("Settings")) {
|
ImGui::TableNextColumn();
|
||||||
mobileMenuOpen=false;
|
if (ImGui::Button("Other",buttonSize)) {
|
||||||
}
|
mobScene=GUI_SCENE_OTHER;
|
||||||
ImGui::SameLine();
|
}
|
||||||
if (ImGui::Button("About")) {
|
ImGui::EndTable();
|
||||||
mobileMenuOpen=false;
|
|
||||||
mobileMenuPos=0.0f;
|
|
||||||
aboutOpen=true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::Button("Osc")) {
|
if (settings.unifiedDataView) {
|
||||||
oscOpen=!oscOpen;
|
drawInsList(true);
|
||||||
}
|
} else {
|
||||||
ImGui::SameLine();
|
switch (mobScene) {
|
||||||
if (ImGui::Button("ChanOsc")) {
|
case GUI_SCENE_PATTERN:
|
||||||
chanOscOpen=!chanOscOpen;
|
case GUI_SCENE_ORDERS:
|
||||||
}
|
case GUI_SCENE_INSTRUMENT:
|
||||||
ImGui::SameLine();
|
drawInsList(true);
|
||||||
if (ImGui::Button("RegView")) {
|
break;
|
||||||
regViewOpen=!regViewOpen;
|
case GUI_SCENE_WAVETABLE:
|
||||||
}
|
drawWaveList(true);
|
||||||
ImGui::SameLine();
|
break;
|
||||||
if (ImGui::Button("Stats")) {
|
case GUI_SCENE_SAMPLE:
|
||||||
statsOpen=!statsOpen;
|
drawSampleList(true);
|
||||||
|
break;
|
||||||
|
case GUI_SCENE_SONG: {
|
||||||
|
if (ImGui::Button("New")) {
|
||||||
|
mobileMenuOpen=false;
|
||||||
|
//doAction(GUI_ACTION_NEW);
|
||||||
|
if (modified) {
|
||||||
|
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
|
||||||
|
} else {
|
||||||
|
displayNew=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Open")) {
|
||||||
|
mobileMenuOpen=false;
|
||||||
|
doAction(GUI_ACTION_OPEN);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Save")) {
|
||||||
|
mobileMenuOpen=false;
|
||||||
|
doAction(GUI_ACTION_SAVE);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Save as...")) {
|
||||||
|
mobileMenuOpen=false;
|
||||||
|
doAction(GUI_ACTION_SAVE_AS);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Button("1.1+ .dmf");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Button("Legacy .dmf");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Button("Export Audio");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Button("Export VGM");
|
||||||
|
|
||||||
|
ImGui::Button("CmdStream");
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Text("Song info here...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GUI_SCENE_CHANNELS:
|
||||||
|
ImGui::Text("Channels here...");
|
||||||
|
break;
|
||||||
|
case GUI_SCENE_CHIPS:
|
||||||
|
ImGui::Text("Chips here...");
|
||||||
|
break;
|
||||||
|
case GUI_SCENE_OTHER: {
|
||||||
|
if (ImGui::Button("Osc")) {
|
||||||
|
oscOpen=!oscOpen;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("ChanOsc")) {
|
||||||
|
chanOscOpen=!chanOscOpen;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("RegView")) {
|
||||||
|
regViewOpen=!regViewOpen;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Stats")) {
|
||||||
|
statsOpen=!statsOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Button("Panic");
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Settings")) {
|
||||||
|
mobileMenuOpen=false;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("About")) {
|
||||||
|
mobileMenuOpen=false;
|
||||||
|
mobileMenuPos=0.0f;
|
||||||
|
aboutOpen=true;
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Switch to Desktop Mode")) {
|
||||||
|
toggleMobileUI(!mobileUI);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -228,11 +306,11 @@ void FurnaceGUI::drawEditControls() {
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
pushToggleColors(e->isPlaying());
|
||||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_STOP "##Stop")) {
|
if (ImGui::Button(ICON_FA_STOP "##Stop")) {
|
||||||
stop();
|
stop();
|
||||||
|
@ -262,12 +340,12 @@ void FurnaceGUI::drawEditControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
pushToggleColors(noteInputPoly);
|
||||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||||
noteInputPoly=!noteInputPoly;
|
noteInputPoly=!noteInputPoly;
|
||||||
e->setAutoNotePoly(noteInputPoly);
|
e->setAutoNotePoly(noteInputPoly);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -278,11 +356,11 @@ void FurnaceGUI::drawEditControls() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
pushToggleColors(e->isPlaying());
|
||||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) {
|
if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) {
|
||||||
e->stepOne(cursor.y);
|
e->stepOne(cursor.y);
|
||||||
|
@ -291,26 +369,26 @@ void FurnaceGUI::drawEditControls() {
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
bool repeatPattern=e->getRepeatPattern();
|
bool repeatPattern=e->getRepeatPattern();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
pushToggleColors(repeatPattern);
|
||||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) {
|
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) {
|
||||||
e->setRepeatPattern(!repeatPattern);
|
e->setRepeatPattern(!repeatPattern);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
pushToggleColors(edit);
|
||||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) {
|
if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) {
|
||||||
edit=!edit;
|
edit=!edit;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
bool metro=e->getMetronome();
|
bool metro=e->getMetronome();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
pushToggleColors(metro);
|
||||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) {
|
if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) {
|
||||||
e->setMetronome(!metro);
|
e->setMetronome(!metro);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Text("Octave");
|
ImGui::Text("Octave");
|
||||||
|
@ -347,12 +425,12 @@ void FurnaceGUI::drawEditControls() {
|
||||||
unimportant(ImGui::Checkbox("Pattern",&followPattern));
|
unimportant(ImGui::Checkbox("Pattern",&followPattern));
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
pushToggleColors(noteInputPoly);
|
||||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||||
noteInputPoly=!noteInputPoly;
|
noteInputPoly=!noteInputPoly;
|
||||||
e->setAutoNotePoly(noteInputPoly);
|
e->setAutoNotePoly(noteInputPoly);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -360,11 +438,11 @@ void FurnaceGUI::drawEditControls() {
|
||||||
case 2: // compact vertical
|
case 2: // compact vertical
|
||||||
if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
||||||
ImVec2 buttonSize=ImVec2(ImGui::GetContentRegionAvail().x,0.0f);
|
ImVec2 buttonSize=ImVec2(ImGui::GetContentRegionAvail().x,0.0f);
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
pushToggleColors(e->isPlaying());
|
||||||
if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) {
|
if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) {
|
if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
@ -374,24 +452,24 @@ void FurnaceGUI::drawEditControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool repeatPattern=e->getRepeatPattern();
|
bool repeatPattern=e->getRepeatPattern();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
pushToggleColors(repeatPattern);
|
||||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) {
|
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) {
|
||||||
e->setRepeatPattern(!repeatPattern);
|
e->setRepeatPattern(!repeatPattern);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
pushToggleColors(edit);
|
||||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) {
|
if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) {
|
||||||
edit=!edit;
|
edit=!edit;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
bool metro=e->getMetronome();
|
bool metro=e->getMetronome();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
pushToggleColors(metro);
|
||||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) {
|
if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) {
|
||||||
e->setMetronome(!metro);
|
e->setMetronome(!metro);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::Text("Oct.");
|
ImGui::Text("Oct.");
|
||||||
float avail=ImGui::GetContentRegionAvail().x;
|
float avail=ImGui::GetContentRegionAvail().x;
|
||||||
|
@ -418,23 +496,23 @@ void FurnaceGUI::drawEditControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Foll.");
|
ImGui::Text("Foll.");
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders));
|
pushToggleColors(followOrders);
|
||||||
if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant
|
if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant
|
||||||
followOrders=!followOrders;
|
followOrders=!followOrders;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern));
|
pushToggleColors(followPattern);
|
||||||
if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant
|
if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant
|
||||||
followPattern=!followPattern;
|
followPattern=!followPattern;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
pushToggleColors(noteInputPoly);
|
||||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||||
noteInputPoly=!noteInputPoly;
|
noteInputPoly=!noteInputPoly;
|
||||||
e->setAutoNotePoly(noteInputPoly);
|
e->setAutoNotePoly(noteInputPoly);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -442,11 +520,11 @@ void FurnaceGUI::drawEditControls() {
|
||||||
case 3: // split
|
case 3: // split
|
||||||
if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
||||||
if (e->isPlaying()) {
|
if (e->isPlaying()) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]);
|
pushToggleColors(true);
|
||||||
if (ImGui::Button(ICON_FA_STOP "##Stop")) {
|
if (ImGui::Button(ICON_FA_STOP "##Stop")) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
} else {
|
} else {
|
||||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||||
play(oldRow);
|
play(oldRow);
|
||||||
|
@ -469,35 +547,35 @@ void FurnaceGUI::drawEditControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
pushToggleColors(edit);
|
||||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) {
|
if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) {
|
||||||
edit=!edit;
|
edit=!edit;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
bool metro=e->getMetronome();
|
bool metro=e->getMetronome();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
pushToggleColors(metro);
|
||||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) {
|
if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) {
|
||||||
e->setMetronome(!metro);
|
e->setMetronome(!metro);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
bool repeatPattern=e->getRepeatPattern();
|
bool repeatPattern=e->getRepeatPattern();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
pushToggleColors(repeatPattern);
|
||||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) {
|
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) {
|
||||||
e->setRepeatPattern(!repeatPattern);
|
e->setRepeatPattern(!repeatPattern);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
pushToggleColors(noteInputPoly);
|
||||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||||
noteInputPoly=!noteInputPoly;
|
noteInputPoly=!noteInputPoly;
|
||||||
e->setAutoNotePoly(noteInputPoly);
|
e->setAutoNotePoly(noteInputPoly);
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
|
@ -431,7 +431,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
||||||
int startOff=-1;
|
int startOff=-1;
|
||||||
bool invalidData=false;
|
bool invalidData=false;
|
||||||
if (data.size()<2) return;
|
if (data.size()<2) return;
|
||||||
if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return;
|
if (data[0].find("org.tildearrow.furnace - Pattern Data")!=0) return;
|
||||||
if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return;
|
if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return;
|
||||||
if (startOff<0) return;
|
if (startOff<0) return;
|
||||||
|
|
||||||
|
|
330
src/gui/gui.cpp
330
src/gui/gui.cpp
|
@ -228,7 +228,7 @@ void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex) {
|
void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex) {
|
||||||
int buf=0;
|
int buf=0;
|
||||||
bool negaBuf=false;
|
bool negaBuf=false;
|
||||||
bool hasVal=false;
|
bool hasVal=false;
|
||||||
|
@ -264,9 +264,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma
|
||||||
case ' ':
|
case ' ':
|
||||||
if (hasVal) {
|
if (hasVal) {
|
||||||
hasVal=false;
|
hasVal=false;
|
||||||
negaBuf=false;
|
|
||||||
macro[macroLen]=negaBuf?-buf:buf;
|
macro[macroLen]=negaBuf?-buf:buf;
|
||||||
if (macro[macroLen]<0) macro[macroLen]=0;
|
negaBuf=false;
|
||||||
|
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
|
||||||
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
|
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
|
||||||
macroLen++;
|
macroLen++;
|
||||||
buf=0;
|
buf=0;
|
||||||
|
@ -277,9 +277,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma
|
||||||
}
|
}
|
||||||
if (hasVal && macroLen<256) {
|
if (hasVal && macroLen<256) {
|
||||||
hasVal=false;
|
hasVal=false;
|
||||||
negaBuf=false;
|
|
||||||
macro[macroLen]=negaBuf?-buf:buf;
|
macro[macroLen]=negaBuf?-buf:buf;
|
||||||
if (macro[macroLen]<0) macro[macroLen]=0;
|
negaBuf=false;
|
||||||
|
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
|
||||||
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
|
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
|
||||||
macroLen++;
|
macroLen++;
|
||||||
buf=0;
|
buf=0;
|
||||||
|
@ -536,6 +536,7 @@ void FurnaceGUI::setFileName(String name) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
|
pushRecentFile(curFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::updateWindowTitle() {
|
void FurnaceGUI::updateWindowTitle() {
|
||||||
|
@ -1724,6 +1725,7 @@ int FurnaceGUI::save(String path, int dmfVersion) {
|
||||||
if (!e->getWarnings().empty()) {
|
if (!e->getWarnings().empty()) {
|
||||||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||||
}
|
}
|
||||||
|
pushRecentFile(path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1801,9 +1803,26 @@ int FurnaceGUI::load(String path) {
|
||||||
if (!e->getWarnings().empty()) {
|
if (!e->getWarnings().empty()) {
|
||||||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||||
}
|
}
|
||||||
|
pushRecentFile(path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::pushRecentFile(String path) {
|
||||||
|
if (path.empty()) return;
|
||||||
|
if (path==backupPath) return;
|
||||||
|
for (int i=0; i<(int)recentFile.size(); i++) {
|
||||||
|
if (recentFile[i]==path) {
|
||||||
|
recentFile.erase(recentFile.begin()+i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recentFile.push_front(path);
|
||||||
|
|
||||||
|
while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) {
|
||||||
|
recentFile.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
||||||
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
||||||
displayExporting=true;
|
displayExporting=true;
|
||||||
|
@ -2389,6 +2408,35 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::pushToggleColors(bool status) {
|
||||||
|
ImVec4 toggleColor=status?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF];
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button,toggleColor);
|
||||||
|
if (settings.guiColorsBase) {
|
||||||
|
toggleColor.x*=0.8f;
|
||||||
|
toggleColor.y*=0.8f;
|
||||||
|
toggleColor.z*=0.8f;
|
||||||
|
} else {
|
||||||
|
toggleColor.x=CLAMP(toggleColor.x*1.3f,0.0f,1.0f);
|
||||||
|
toggleColor.y=CLAMP(toggleColor.y*1.3f,0.0f,1.0f);
|
||||||
|
toggleColor.z=CLAMP(toggleColor.z*1.3f,0.0f,1.0f);
|
||||||
|
}
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,toggleColor);
|
||||||
|
if (settings.guiColorsBase) {
|
||||||
|
toggleColor.x*=0.8f;
|
||||||
|
toggleColor.y*=0.8f;
|
||||||
|
toggleColor.z*=0.8f;
|
||||||
|
} else {
|
||||||
|
toggleColor.x=CLAMP(toggleColor.x*1.5f,0.0f,1.0f);
|
||||||
|
toggleColor.y=CLAMP(toggleColor.y*1.5f,0.0f,1.0f);
|
||||||
|
toggleColor.z=CLAMP(toggleColor.z*1.5f,0.0f,1.0f);
|
||||||
|
}
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive,toggleColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::popToggleColors() {
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
}
|
||||||
|
|
||||||
int _processEvent(void* instance, SDL_Event* event) {
|
int _processEvent(void* instance, SDL_Event* event) {
|
||||||
return ((FurnaceGUI*)instance)->processEvent(event);
|
return ((FurnaceGUI*)instance)->processEvent(event);
|
||||||
}
|
}
|
||||||
|
@ -2544,12 +2592,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
||||||
TouchPoint* point=NULL;
|
TouchPoint* point=NULL;
|
||||||
FIND_POINT(point,ev.tfinger.fingerId);
|
FIND_POINT(point,ev.tfinger.fingerId);
|
||||||
if (point!=NULL) {
|
if (point!=NULL) {
|
||||||
|
float prevX=point->x;
|
||||||
|
float prevY=point->y;
|
||||||
point->x=ev.tfinger.x*scrW*dpiScale;
|
point->x=ev.tfinger.x*scrW*dpiScale;
|
||||||
point->y=ev.tfinger.y*scrH*dpiScale;
|
point->y=ev.tfinger.y*scrH*dpiScale;
|
||||||
point->z=ev.tfinger.pressure;
|
point->z=ev.tfinger.pressure;
|
||||||
|
|
||||||
if (point->id==0) {
|
if (point->id==0) {
|
||||||
ImGui::GetIO().AddMousePosEvent(point->x,point->y);
|
ImGui::GetIO().AddMousePosEvent(point->x,point->y);
|
||||||
|
pointMotion(point->x,point->y,point->x-prevX,point->y-prevY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -2570,6 +2621,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
||||||
if (newPoint.id==0) {
|
if (newPoint.id==0) {
|
||||||
ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y);
|
ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y);
|
||||||
ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true);
|
ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true);
|
||||||
|
pointDown(newPoint.x,newPoint.y,0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2577,13 +2629,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
||||||
for (size_t i=0; i<activePoints.size(); i++) {
|
for (size_t i=0; i<activePoints.size(); i++) {
|
||||||
TouchPoint& point=activePoints[i];
|
TouchPoint& point=activePoints[i];
|
||||||
if (point.id==ev.tfinger.fingerId) {
|
if (point.id==ev.tfinger.fingerId) {
|
||||||
releasedPoints.push_back(point);
|
|
||||||
activePoints.erase(activePoints.begin()+i);
|
|
||||||
|
|
||||||
if (point.id==0) {
|
if (point.id==0) {
|
||||||
ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,false);
|
ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,false);
|
||||||
//ImGui::GetIO().AddMousePosEvent(-FLT_MAX,-FLT_MAX);
|
//ImGui::GetIO().AddMousePosEvent(-FLT_MAX,-FLT_MAX);
|
||||||
|
pointUp(point.x,point.y,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
releasedPoints.push_back(point);
|
||||||
|
activePoints.erase(activePoints.begin()+i);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2592,6 +2646,84 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::pointDown(int x, int y, int button) {
|
||||||
|
aboutOpen=false;
|
||||||
|
if (bindSetActive) {
|
||||||
|
bindSetActive=false;
|
||||||
|
bindSetPending=false;
|
||||||
|
actionKeys[bindSetTarget]=bindSetPrevValue;
|
||||||
|
bindSetTarget=0;
|
||||||
|
bindSetPrevValue=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::pointUp(int x, int y, int button) {
|
||||||
|
if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) {
|
||||||
|
MARK_MODIFIED;
|
||||||
|
}
|
||||||
|
if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) {
|
||||||
|
displayMacroMenu=true;
|
||||||
|
}
|
||||||
|
macroDragActive=false;
|
||||||
|
macroDragBitMode=false;
|
||||||
|
macroDragInitialValue=false;
|
||||||
|
macroDragInitialValueSet=false;
|
||||||
|
macroDragLastX=-1;
|
||||||
|
macroDragLastY=-1;
|
||||||
|
macroLoopDragActive=false;
|
||||||
|
waveDragActive=false;
|
||||||
|
if (sampleDragActive) {
|
||||||
|
logD("stopping sample drag");
|
||||||
|
if (sampleDragMode) {
|
||||||
|
e->renderSamplesP();
|
||||||
|
} else {
|
||||||
|
if (sampleSelStart>sampleSelEnd) {
|
||||||
|
sampleSelStart^=sampleSelEnd;
|
||||||
|
sampleSelEnd^=sampleSelStart;
|
||||||
|
sampleSelStart^=sampleSelEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sampleDragActive=false;
|
||||||
|
if (selecting) {
|
||||||
|
if (!selectingFull) cursor=selEnd;
|
||||||
|
finishSelection();
|
||||||
|
demandScrollX=true;
|
||||||
|
if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y &&
|
||||||
|
cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) {
|
||||||
|
if (!settings.cursorMoveNoScroll) {
|
||||||
|
updateScroll(cursor.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) {
|
||||||
|
if (selecting) {
|
||||||
|
// detect whether we have to scroll
|
||||||
|
if (y<patWindowPos.y+2.0f*dpiScale) {
|
||||||
|
addScroll(-1);
|
||||||
|
}
|
||||||
|
if (y>patWindowPos.y+patWindowSize.y-2.0f*dpiScale) {
|
||||||
|
addScroll(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) {
|
||||||
|
int distance=fabs((double)xrel);
|
||||||
|
if (distance<1) distance=1;
|
||||||
|
float start=x-xrel;
|
||||||
|
float end=x;
|
||||||
|
float startY=y-yrel;
|
||||||
|
float endY=y;
|
||||||
|
for (int i=0; i<=distance; i++) {
|
||||||
|
float fraction=(float)i/(float)distance;
|
||||||
|
float x=start+(end-start)*fraction;
|
||||||
|
float y=startY+(endY-startY)*fraction;
|
||||||
|
processDrags(x,y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// how many pixels should be visible at least at x/y dir
|
// how many pixels should be visible at least at x/y dir
|
||||||
#define OOB_PIXELS_SAFETY 25
|
#define OOB_PIXELS_SAFETY 25
|
||||||
|
|
||||||
|
@ -2640,7 +2772,7 @@ bool FurnaceGUI::loop() {
|
||||||
if (settings.powerSave) SDL_WaitEventTimeout(NULL,500);
|
if (settings.powerSave) SDL_WaitEventTimeout(NULL,500);
|
||||||
}
|
}
|
||||||
eventTimeBegin=SDL_GetPerformanceCounter();
|
eventTimeBegin=SDL_GetPerformanceCounter();
|
||||||
bool updateWindow = false;
|
bool updateWindow=false;
|
||||||
while (SDL_PollEvent(&ev)) {
|
while (SDL_PollEvent(&ev)) {
|
||||||
WAKE_UP;
|
WAKE_UP;
|
||||||
ImGui_ImplSDL2_ProcessEvent(&ev);
|
ImGui_ImplSDL2_ProcessEvent(&ev);
|
||||||
|
@ -2658,80 +2790,14 @@ bool FurnaceGUI::loop() {
|
||||||
motionXrel*=dpiScale;
|
motionXrel*=dpiScale;
|
||||||
motionYrel*=dpiScale;
|
motionYrel*=dpiScale;
|
||||||
#endif
|
#endif
|
||||||
if (selecting) {
|
pointMotion(motionX,motionY,motionXrel,motionYrel);
|
||||||
// detect whether we have to scroll
|
|
||||||
if (motionY<patWindowPos.y+2.0f*dpiScale) {
|
|
||||||
addScroll(-1);
|
|
||||||
}
|
|
||||||
if (motionY>patWindowPos.y+patWindowSize.y-2.0f*dpiScale) {
|
|
||||||
addScroll(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) {
|
|
||||||
int distance=fabs((double)motionXrel);
|
|
||||||
if (distance<1) distance=1;
|
|
||||||
float start=motionX-motionXrel;
|
|
||||||
float end=motionX;
|
|
||||||
float startY=motionY-motionYrel;
|
|
||||||
float endY=motionY;
|
|
||||||
for (int i=0; i<=distance; i++) {
|
|
||||||
float fraction=(float)i/(float)distance;
|
|
||||||
float x=start+(end-start)*fraction;
|
|
||||||
float y=startY+(endY-startY)*fraction;
|
|
||||||
processDrags(x,y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) {
|
pointUp(ev.button.x,ev.button.y,ev.button.button);
|
||||||
MARK_MODIFIED;
|
|
||||||
}
|
|
||||||
if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) {
|
|
||||||
displayMacroMenu=true;
|
|
||||||
}
|
|
||||||
macroDragActive=false;
|
|
||||||
macroDragBitMode=false;
|
|
||||||
macroDragInitialValue=false;
|
|
||||||
macroDragInitialValueSet=false;
|
|
||||||
macroDragLastX=-1;
|
|
||||||
macroDragLastY=-1;
|
|
||||||
macroLoopDragActive=false;
|
|
||||||
waveDragActive=false;
|
|
||||||
if (sampleDragActive) {
|
|
||||||
logD("stopping sample drag");
|
|
||||||
if (sampleDragMode) {
|
|
||||||
e->renderSamplesP();
|
|
||||||
} else {
|
|
||||||
if (sampleSelStart>sampleSelEnd) {
|
|
||||||
sampleSelStart^=sampleSelEnd;
|
|
||||||
sampleSelEnd^=sampleSelStart;
|
|
||||||
sampleSelStart^=sampleSelEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sampleDragActive=false;
|
|
||||||
if (selecting) {
|
|
||||||
if (!selectingFull) cursor=selEnd;
|
|
||||||
finishSelection();
|
|
||||||
demandScrollX=true;
|
|
||||||
if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y &&
|
|
||||||
cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) {
|
|
||||||
if (!settings.cursorMoveNoScroll) {
|
|
||||||
updateScroll(cursor.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
aboutOpen=false;
|
pointDown(ev.button.x,ev.button.y,ev.button.button);
|
||||||
if (bindSetActive) {
|
|
||||||
bindSetActive=false;
|
|
||||||
bindSetPending=false;
|
|
||||||
actionKeys[bindSetTarget]=bindSetPrevValue;
|
|
||||||
bindSetTarget=0;
|
|
||||||
bindSetPrevValue=0;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL:
|
||||||
wheelX+=ev.wheel.x;
|
wheelX+=ev.wheel.x;
|
||||||
|
@ -3027,6 +3093,27 @@ bool FurnaceGUI::loop() {
|
||||||
openFileDialog(GUI_FILE_OPEN);
|
openFileDialog(GUI_FILE_OPEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ImGui::BeginMenu("open recent")) {
|
||||||
|
for (int i=0; i<(int)recentFile.size(); i++) {
|
||||||
|
String item=recentFile[i];
|
||||||
|
if (ImGui::MenuItem(item.c_str())) {
|
||||||
|
if (modified) {
|
||||||
|
nextFile=item;
|
||||||
|
showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP);
|
||||||
|
} else {
|
||||||
|
recentFile.erase(recentFile.begin()+i);
|
||||||
|
i--;
|
||||||
|
if (load(item)>0) {
|
||||||
|
showError(fmt::sprintf("Error while loading file! (%s)",lastError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recentFile.empty()) {
|
||||||
|
ImGui::Text("nothing here yet");
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) {
|
if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) {
|
||||||
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) {
|
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) {
|
||||||
|
@ -3227,6 +3314,11 @@ bool FurnaceGUI::loop() {
|
||||||
if (ImGui::MenuItem("reset layout")) {
|
if (ImGui::MenuItem("reset layout")) {
|
||||||
showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT);
|
showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT);
|
||||||
}
|
}
|
||||||
|
#ifdef IS_MOBILE
|
||||||
|
if (ImGui::MenuItem("switch to mobile view")) {
|
||||||
|
toggleMobileUI(!mobileUI);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) {
|
if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) {
|
||||||
syncSettings();
|
syncSettings();
|
||||||
settingsOpen=true;
|
settingsOpen=true;
|
||||||
|
@ -3353,8 +3445,37 @@ bool FurnaceGUI::loop() {
|
||||||
// scene handling goes here!
|
// scene handling goes here!
|
||||||
pianoOpen=true;
|
pianoOpen=true;
|
||||||
drawMobileControls();
|
drawMobileControls();
|
||||||
drawPattern();
|
switch (mobScene) {
|
||||||
drawPiano();
|
case GUI_SCENE_ORDERS:
|
||||||
|
ordersOpen=true;
|
||||||
|
curWindow=GUI_WINDOW_ORDERS;
|
||||||
|
drawOrders();
|
||||||
|
break;
|
||||||
|
case GUI_SCENE_INSTRUMENT:
|
||||||
|
insEditOpen=true;
|
||||||
|
curWindow=GUI_WINDOW_INS_EDIT;
|
||||||
|
drawInsEdit();
|
||||||
|
drawPiano();
|
||||||
|
break;
|
||||||
|
case GUI_SCENE_WAVETABLE:
|
||||||
|
waveEditOpen=true;
|
||||||
|
curWindow=GUI_WINDOW_WAVE_EDIT;
|
||||||
|
drawWaveEdit();
|
||||||
|
drawPiano();
|
||||||
|
break;
|
||||||
|
case GUI_SCENE_SAMPLE:
|
||||||
|
sampleEditOpen=true;
|
||||||
|
curWindow=GUI_WINDOW_SAMPLE_EDIT;
|
||||||
|
drawSampleEdit();
|
||||||
|
drawPiano();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
patternOpen=true;
|
||||||
|
curWindow=GUI_WINDOW_PATTERN;
|
||||||
|
drawPattern();
|
||||||
|
drawPiano();
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
globalWinFlags=0;
|
globalWinFlags=0;
|
||||||
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
|
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
|
||||||
|
@ -3397,6 +3518,13 @@ bool FurnaceGUI::loop() {
|
||||||
|
|
||||||
if (firstFrame) {
|
if (firstFrame) {
|
||||||
firstFrame=false;
|
firstFrame=false;
|
||||||
|
#ifdef IS_MOBILE
|
||||||
|
SDL_GetWindowSize(sdlWin,&scrW,&scrH);
|
||||||
|
scrW/=dpiScale;
|
||||||
|
scrH/=dpiScale;
|
||||||
|
portrait=(scrW<scrH);
|
||||||
|
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
|
||||||
|
#endif
|
||||||
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
|
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
SDL_RaiseWindow(sdlWin);
|
SDL_RaiseWindow(sdlWin);
|
||||||
|
@ -4522,6 +4650,7 @@ bool FurnaceGUI::init() {
|
||||||
|
|
||||||
tempoView=e->getConfBool("tempoView",true);
|
tempoView=e->getConfBool("tempoView",true);
|
||||||
waveHex=e->getConfBool("waveHex",false);
|
waveHex=e->getConfBool("waveHex",false);
|
||||||
|
waveSigned=e->getConfBool("waveSigned",false);
|
||||||
waveGenVisible=e->getConfBool("waveGenVisible",false);
|
waveGenVisible=e->getConfBool("waveGenVisible",false);
|
||||||
waveEditStyle=e->getConfInt("waveEditStyle",0);
|
waveEditStyle=e->getConfInt("waveEditStyle",0);
|
||||||
lockLayout=e->getConfBool("lockLayout",false);
|
lockLayout=e->getConfBool("lockLayout",false);
|
||||||
|
@ -4569,6 +4698,13 @@ bool FurnaceGUI::init() {
|
||||||
|
|
||||||
syncSettings();
|
syncSettings();
|
||||||
|
|
||||||
|
for (int i=0; i<settings.maxRecentFile; i++) {
|
||||||
|
String r=e->getConfString(fmt::sprintf("recentFile%d",i),"");
|
||||||
|
if (!r.empty()) {
|
||||||
|
recentFile.push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.dpiScale>=0.5f) {
|
if (settings.dpiScale>=0.5f) {
|
||||||
dpiScale=settings.dpiScale;
|
dpiScale=settings.dpiScale;
|
||||||
}
|
}
|
||||||
|
@ -4582,15 +4718,22 @@ bool FurnaceGUI::init() {
|
||||||
SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000);
|
SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef IS_MOBILE
|
||||||
|
scrW=960;
|
||||||
|
scrH=540;
|
||||||
|
scrX=0;
|
||||||
|
scrY=0;
|
||||||
|
#else
|
||||||
scrW=scrConfW=e->getConfInt("lastWindowWidth",1280);
|
scrW=scrConfW=e->getConfInt("lastWindowWidth",1280);
|
||||||
scrH=scrConfH=e->getConfInt("lastWindowHeight",800);
|
scrH=scrConfH=e->getConfInt("lastWindowHeight",800);
|
||||||
scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED);
|
scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED);
|
||||||
scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED);
|
scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED);
|
||||||
scrMax=e->getConfBool("lastWindowMax",false);
|
scrMax=e->getConfBool("lastWindowMax",false);
|
||||||
|
#endif
|
||||||
portrait=(scrW<scrH);
|
portrait=(scrW<scrH);
|
||||||
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
|
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#if !defined(__APPLE__) && !defined(IS_MOBILE)
|
||||||
SDL_Rect displaySize;
|
SDL_Rect displaySize;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -4604,12 +4747,14 @@ bool FurnaceGUI::init() {
|
||||||
|
|
||||||
SDL_Init(SDL_INIT_VIDEO);
|
SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
|
||||||
|
#ifndef IS_MOBILE
|
||||||
// if window would spawn out of bounds, force it to be get default position
|
// if window would spawn out of bounds, force it to be get default position
|
||||||
if (!detectOutOfBoundsWindow()) {
|
if (!detectOutOfBoundsWindow()) {
|
||||||
scrMax=false;
|
scrMax=false;
|
||||||
scrX=scrConfX=SDL_WINDOWPOS_CENTERED;
|
scrX=scrConfX=SDL_WINDOWPOS_CENTERED;
|
||||||
scrY=scrConfY=SDL_WINDOWPOS_CENTERED;
|
scrY=scrConfY=SDL_WINDOWPOS_CENTERED;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
|
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
|
||||||
if (sdlWin==NULL) {
|
if (sdlWin==NULL) {
|
||||||
|
@ -4623,6 +4768,7 @@ bool FurnaceGUI::init() {
|
||||||
SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(sdlWin),&dpiScaleF,NULL,NULL);
|
SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(sdlWin),&dpiScaleF,NULL,NULL);
|
||||||
dpiScale=round(dpiScaleF/96.0f);
|
dpiScale=round(dpiScaleF/96.0f);
|
||||||
if (dpiScale<1) dpiScale=1;
|
if (dpiScale<1) dpiScale=1;
|
||||||
|
#ifndef IS_MOBILE
|
||||||
if (dpiScale!=1) {
|
if (dpiScale!=1) {
|
||||||
if (!fullScreen) {
|
if (!fullScreen) {
|
||||||
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
|
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
|
||||||
|
@ -4642,6 +4788,7 @@ bool FurnaceGUI::init() {
|
||||||
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
|
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -4789,6 +4936,7 @@ bool FurnaceGUI::finish() {
|
||||||
|
|
||||||
e->setConf("tempoView",tempoView);
|
e->setConf("tempoView",tempoView);
|
||||||
e->setConf("waveHex",waveHex);
|
e->setConf("waveHex",waveHex);
|
||||||
|
e->setConf("waveSigned",waveSigned);
|
||||||
e->setConf("waveGenVisible",waveGenVisible);
|
e->setConf("waveGenVisible",waveGenVisible);
|
||||||
e->setConf("waveEditStyle",waveEditStyle);
|
e->setConf("waveEditStyle",waveEditStyle);
|
||||||
e->setConf("lockLayout",lockLayout);
|
e->setConf("lockLayout",lockLayout);
|
||||||
|
@ -4830,6 +4978,16 @@ bool FurnaceGUI::finish() {
|
||||||
e->setConf("chanOscUseGrad",chanOscUseGrad);
|
e->setConf("chanOscUseGrad",chanOscUseGrad);
|
||||||
e->setConf("chanOscGrad",chanOscGrad.toString());
|
e->setConf("chanOscGrad",chanOscGrad.toString());
|
||||||
|
|
||||||
|
// commit recent files
|
||||||
|
for (int i=0; i<30; i++) {
|
||||||
|
String key=fmt::sprintf("recentFile%d",i);
|
||||||
|
if (i>=settings.maxRecentFile || i>=(int)recentFile.size()) {
|
||||||
|
e->setConf(key,"");
|
||||||
|
} else {
|
||||||
|
e->setConf(key,recentFile[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||||
delete oldPat[i];
|
delete oldPat[i];
|
||||||
}
|
}
|
||||||
|
@ -4985,6 +5143,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
firstFrame(true),
|
firstFrame(true),
|
||||||
tempoView(true),
|
tempoView(true),
|
||||||
waveHex(false),
|
waveHex(false),
|
||||||
|
waveSigned(false),
|
||||||
waveGenVisible(false),
|
waveGenVisible(false),
|
||||||
lockLayout(false),
|
lockLayout(false),
|
||||||
editOptsVisible(false),
|
editOptsVisible(false),
|
||||||
|
@ -5057,6 +5216,8 @@ FurnaceGUI::FurnaceGUI():
|
||||||
macroOffY(0),
|
macroOffY(0),
|
||||||
macroScaleX(100.0f),
|
macroScaleX(100.0f),
|
||||||
macroScaleY(100.0f),
|
macroScaleY(100.0f),
|
||||||
|
macroRandMin(0),
|
||||||
|
macroRandMax(0),
|
||||||
macroLoopDragStart(0,0),
|
macroLoopDragStart(0,0),
|
||||||
macroLoopDragAreaSize(0,0),
|
macroLoopDragAreaSize(0,0),
|
||||||
macroLoopDragTarget(NULL),
|
macroLoopDragTarget(NULL),
|
||||||
|
@ -5087,6 +5248,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
chanToMove(-1),
|
chanToMove(-1),
|
||||||
sysToMove(-1),
|
sysToMove(-1),
|
||||||
sysToDelete(-1),
|
sysToDelete(-1),
|
||||||
|
opToMove(-1),
|
||||||
transposeAmount(0),
|
transposeAmount(0),
|
||||||
randomizeMin(0),
|
randomizeMin(0),
|
||||||
randomizeMax(255),
|
randomizeMax(255),
|
||||||
|
@ -5171,6 +5333,12 @@ FurnaceGUI::FurnaceGUI():
|
||||||
waveGenDuty(0.5f),
|
waveGenDuty(0.5f),
|
||||||
waveGenPower(1),
|
waveGenPower(1),
|
||||||
waveGenInvertPoint(1.0f),
|
waveGenInvertPoint(1.0f),
|
||||||
|
waveGenScaleX(32),
|
||||||
|
waveGenScaleY(31),
|
||||||
|
waveGenOffsetX(0),
|
||||||
|
waveGenOffsetY(0),
|
||||||
|
waveGenSmooth(1),
|
||||||
|
waveGenAmplify(1.0f),
|
||||||
waveGenFM(false) {
|
waveGenFM(false) {
|
||||||
// value keys
|
// value keys
|
||||||
valueKeys[SDLK_0]=0;
|
valueKeys[SDLK_0]=0;
|
||||||
|
|
|
@ -47,8 +47,6 @@
|
||||||
#define MARK_MODIFIED modified=true;
|
#define MARK_MODIFIED modified=true;
|
||||||
#define WAKE_UP drawHalt=16;
|
#define WAKE_UP drawHalt=16;
|
||||||
|
|
||||||
#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF])
|
|
||||||
|
|
||||||
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
|
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
|
@ -265,7 +263,11 @@ enum FurnaceGUIMobileScenes {
|
||||||
GUI_SCENE_ORDERS,
|
GUI_SCENE_ORDERS,
|
||||||
GUI_SCENE_INSTRUMENT,
|
GUI_SCENE_INSTRUMENT,
|
||||||
GUI_SCENE_WAVETABLE,
|
GUI_SCENE_WAVETABLE,
|
||||||
GUI_SCENE_SAMPLE
|
GUI_SCENE_SAMPLE,
|
||||||
|
GUI_SCENE_SONG,
|
||||||
|
GUI_SCENE_CHANNELS,
|
||||||
|
GUI_SCENE_CHIPS,
|
||||||
|
GUI_SCENE_OTHER,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FurnaceGUIFileDialogs {
|
enum FurnaceGUIFileDialogs {
|
||||||
|
@ -887,9 +889,10 @@ struct FurnaceGUIMacroDesc {
|
||||||
ImVec4 color;
|
ImVec4 color;
|
||||||
unsigned int bitOffset;
|
unsigned int bitOffset;
|
||||||
bool isBitfield, blockMode, bit30;
|
bool isBitfield, blockMode, bit30;
|
||||||
String (*hoverFunc)(int,float);
|
String (*hoverFunc)(int,float,void*);
|
||||||
|
void* hoverFuncUser;
|
||||||
|
|
||||||
FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0, bool bit30Special=false):
|
FurnaceGUIMacroDesc(const char* name, DivInstrumentMacro* m, int macroMin, int macroMax, float macroHeight, ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f), bool block=false, const char* mName=NULL, String (*hf)(int,float,void*)=NULL, bool bitfield=false, const char** bfVal=NULL, unsigned int bitOff=0, bool bit30Special=false, void* hfu=NULL):
|
||||||
macro(m),
|
macro(m),
|
||||||
height(macroHeight),
|
height(macroHeight),
|
||||||
displayName(name),
|
displayName(name),
|
||||||
|
@ -900,7 +903,8 @@ struct FurnaceGUIMacroDesc {
|
||||||
isBitfield(bitfield),
|
isBitfield(bitfield),
|
||||||
blockMode(block),
|
blockMode(block),
|
||||||
bit30(bit30Special),
|
bit30(bit30Special),
|
||||||
hoverFunc(hf) {
|
hoverFunc(hf),
|
||||||
|
hoverFuncUser(hfu) {
|
||||||
// MSVC -> hell
|
// MSVC -> hell
|
||||||
this->min=macroMin;
|
this->min=macroMin;
|
||||||
this->max=macroMax;
|
this->max=macroMax;
|
||||||
|
@ -994,6 +998,7 @@ class FurnaceGUI {
|
||||||
|
|
||||||
std::vector<DivSystem> sysSearchResults;
|
std::vector<DivSystem> sysSearchResults;
|
||||||
std::vector<FurnaceGUISysDef> newSongSearchResults;
|
std::vector<FurnaceGUISysDef> newSongSearchResults;
|
||||||
|
std::deque<String> recentFile;
|
||||||
|
|
||||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
|
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
|
||||||
bool portrait, mobileMenuOpen;
|
bool portrait, mobileMenuOpen;
|
||||||
|
@ -1165,6 +1170,7 @@ class FurnaceGUI {
|
||||||
int channelStyle;
|
int channelStyle;
|
||||||
int channelVolStyle;
|
int channelVolStyle;
|
||||||
int channelFeedbackStyle;
|
int channelFeedbackStyle;
|
||||||
|
int maxRecentFile;
|
||||||
unsigned int maxUndoSteps;
|
unsigned int maxUndoSteps;
|
||||||
String mainFontPath;
|
String mainFontPath;
|
||||||
String patFontPath;
|
String patFontPath;
|
||||||
|
@ -1285,6 +1291,7 @@ class FurnaceGUI {
|
||||||
channelStyle(0),
|
channelStyle(0),
|
||||||
channelVolStyle(0),
|
channelVolStyle(0),
|
||||||
channelFeedbackStyle(1),
|
channelFeedbackStyle(1),
|
||||||
|
maxRecentFile(10),
|
||||||
maxUndoSteps(100),
|
maxUndoSteps(100),
|
||||||
mainFontPath(""),
|
mainFontPath(""),
|
||||||
patFontPath(""),
|
patFontPath(""),
|
||||||
|
@ -1318,7 +1325,7 @@ class FurnaceGUI {
|
||||||
|
|
||||||
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
|
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
|
||||||
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
|
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
|
||||||
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
|
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
|
||||||
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
|
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
|
||||||
float peak[2];
|
float peak[2];
|
||||||
float patChanX[DIV_MAX_CHANS+1];
|
float patChanX[DIV_MAX_CHANS+1];
|
||||||
|
@ -1439,6 +1446,7 @@ class FurnaceGUI {
|
||||||
FurnaceGUIMacroDesc lastMacroDesc;
|
FurnaceGUIMacroDesc lastMacroDesc;
|
||||||
int macroOffX, macroOffY;
|
int macroOffX, macroOffY;
|
||||||
float macroScaleX, macroScaleY;
|
float macroScaleX, macroScaleY;
|
||||||
|
int macroRandMin, macroRandMax;
|
||||||
|
|
||||||
ImVec2 macroLoopDragStart;
|
ImVec2 macroLoopDragStart;
|
||||||
ImVec2 macroLoopDragAreaSize;
|
ImVec2 macroLoopDragAreaSize;
|
||||||
|
@ -1462,7 +1470,7 @@ class FurnaceGUI {
|
||||||
int renderTimeBegin, renderTimeEnd, renderTimeDelta;
|
int renderTimeBegin, renderTimeEnd, renderTimeDelta;
|
||||||
int eventTimeBegin, eventTimeEnd, eventTimeDelta;
|
int eventTimeBegin, eventTimeEnd, eventTimeDelta;
|
||||||
|
|
||||||
int chanToMove, sysToMove, sysToDelete;
|
int chanToMove, sysToMove, sysToDelete, opToMove;
|
||||||
|
|
||||||
ImVec2 patWindowPos, patWindowSize;
|
ImVec2 patWindowPos, patWindowSize;
|
||||||
|
|
||||||
|
@ -1567,6 +1575,8 @@ class FurnaceGUI {
|
||||||
float waveGenTL[4];
|
float waveGenTL[4];
|
||||||
int waveGenMult[4];
|
int waveGenMult[4];
|
||||||
int waveGenFB[4];
|
int waveGenFB[4];
|
||||||
|
int waveGenScaleX, waveGenScaleY, waveGenOffsetX, waveGenOffsetY, waveGenSmooth;
|
||||||
|
float waveGenAmplify;
|
||||||
bool waveGenFMCon1[4];
|
bool waveGenFMCon1[4];
|
||||||
bool waveGenFMCon2[3];
|
bool waveGenFMCon2[3];
|
||||||
bool waveGenFMCon3[2];
|
bool waveGenFMCon3[2];
|
||||||
|
@ -1605,16 +1615,19 @@ class FurnaceGUI {
|
||||||
|
|
||||||
void toggleMobileUI(bool enable, bool force=false);
|
void toggleMobileUI(bool enable, bool force=false);
|
||||||
|
|
||||||
|
void pushToggleColors(bool status);
|
||||||
|
void popToggleColors();
|
||||||
|
|
||||||
void drawMobileControls();
|
void drawMobileControls();
|
||||||
void drawEditControls();
|
void drawEditControls();
|
||||||
void drawSongInfo();
|
void drawSongInfo();
|
||||||
void drawOrders();
|
void drawOrders();
|
||||||
void drawPattern();
|
void drawPattern();
|
||||||
void drawInsList();
|
void drawInsList(bool asChild=false);
|
||||||
void drawInsEdit();
|
void drawInsEdit();
|
||||||
void drawWaveList();
|
void drawWaveList(bool asChild=false);
|
||||||
void drawWaveEdit();
|
void drawWaveEdit();
|
||||||
void drawSampleList();
|
void drawSampleList(bool asChild=false);
|
||||||
void drawSampleEdit();
|
void drawSampleEdit();
|
||||||
void drawMixer();
|
void drawMixer();
|
||||||
void drawOsc();
|
void drawOsc();
|
||||||
|
@ -1713,9 +1726,14 @@ class FurnaceGUI {
|
||||||
void keyDown(SDL_Event& ev);
|
void keyDown(SDL_Event& ev);
|
||||||
void keyUp(SDL_Event& ev);
|
void keyUp(SDL_Event& ev);
|
||||||
|
|
||||||
|
void pointDown(int x, int y, int button);
|
||||||
|
void pointUp(int x, int y, int button);
|
||||||
|
void pointMotion(int x, int y, int xrel, int yrel);
|
||||||
|
|
||||||
void openFileDialog(FurnaceGUIFileDialogs type);
|
void openFileDialog(FurnaceGUIFileDialogs type);
|
||||||
int save(String path, int dmfVersion);
|
int save(String path, int dmfVersion);
|
||||||
int load(String path);
|
int load(String path);
|
||||||
|
void pushRecentFile(String path);
|
||||||
void exportAudio(String path, DivAudioExportModes mode);
|
void exportAudio(String path, DivAudioExportModes mode);
|
||||||
|
|
||||||
bool parseSysEx(unsigned char* data, size_t len);
|
bool parseSysEx(unsigned char* data, size_t len);
|
||||||
|
@ -1725,7 +1743,7 @@ class FurnaceGUI {
|
||||||
|
|
||||||
void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false, bool bit30=false);
|
void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false, bool bit30=false);
|
||||||
void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30=false);
|
void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30=false);
|
||||||
void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false);
|
void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMin, int macroMax, bool hex=false);
|
||||||
|
|
||||||
String encodeKeyMap(std::map<int,int>& map);
|
String encodeKeyMap(std::map<int,int>& map);
|
||||||
void decodeKeyMap(std::map<int,int>& map, String source);
|
void decodeKeyMap(std::map<int,int>& map, String source);
|
||||||
|
|
|
@ -316,27 +316,31 @@ const char* macroRelativeMode="Relative";
|
||||||
const char* macroQSoundMode="QSound";
|
const char* macroQSoundMode="QSound";
|
||||||
const char* macroDummyMode="Bug";
|
const char* macroDummyMode="Bug";
|
||||||
|
|
||||||
String macroHoverNote(int id, float val) {
|
String macroHoverNote(int id, float val, void* u) {
|
||||||
if (val<-60 || val>=120) return "???";
|
int* macroVal=(int*)u;
|
||||||
return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]);
|
if ((macroVal[id]&0xc0000000)==0x40000000 || (macroVal[id]&0xc0000000)==0x80000000) {
|
||||||
|
if (val<-60 || val>=120) return "???";
|
||||||
|
return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]);
|
||||||
|
}
|
||||||
|
return fmt::sprintf("%d: %d",id,(int)val);
|
||||||
}
|
}
|
||||||
|
|
||||||
String macroHover(int id, float val) {
|
String macroHover(int id, float val, void* u) {
|
||||||
return fmt::sprintf("%d: %d",id,val);
|
return fmt::sprintf("%d: %d",id,val);
|
||||||
}
|
}
|
||||||
|
|
||||||
String macroHoverLoop(int id, float val) {
|
String macroHoverLoop(int id, float val, void* u) {
|
||||||
if (val>1) return "Release";
|
if (val>1) return "Release";
|
||||||
if (val>0) return "Loop";
|
if (val>0) return "Loop";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String macroHoverBit30(int id, float val) {
|
String macroHoverBit30(int id, float val, void* u) {
|
||||||
if (val>0) return "Fixed";
|
if (val>0) return "Fixed";
|
||||||
return "Relative";
|
return "Relative";
|
||||||
}
|
}
|
||||||
|
|
||||||
String macroHoverES5506FilterMode(int id, float val) {
|
String macroHoverES5506FilterMode(int id, float val, void* u) {
|
||||||
String mode="???";
|
String mode="???";
|
||||||
switch (((int)val)&3) {
|
switch (((int)val)&3) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -357,7 +361,7 @@ String macroHoverES5506FilterMode(int id, float val) {
|
||||||
return fmt::sprintf("%d: %s",id,mode);
|
return fmt::sprintf("%d: %s",id,mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
String macroLFOWaves(int id, float val) {
|
String macroLFOWaves(int id, float val, void* u) {
|
||||||
switch (((int)val)&3) {
|
switch (((int)val)&3) {
|
||||||
case 0:
|
case 0:
|
||||||
return "Saw";
|
return "Saw";
|
||||||
|
@ -1355,7 +1359,7 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
||||||
if (i.isBitfield) {
|
if (i.isBitfield) {
|
||||||
PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight);
|
PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight);
|
||||||
} else {
|
} else {
|
||||||
PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight);
|
PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight);
|
||||||
}
|
}
|
||||||
if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) {
|
if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) {
|
||||||
macroDragStart=ImGui::GetItemRectMin();
|
macroDragStart=ImGui::GetItemRectMin();
|
||||||
|
@ -1553,6 +1557,49 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
||||||
#define CENTER_TEXT_20(text) \
|
#define CENTER_TEXT_20(text) \
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x));
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x));
|
||||||
|
|
||||||
|
#define OP_DRAG_POINT \
|
||||||
|
if (ImGui::Button(ICON_FA_ARROWS)) { \
|
||||||
|
} \
|
||||||
|
if (ImGui::BeginDragDropSource()) { \
|
||||||
|
opToMove=i; \
|
||||||
|
ImGui::SetDragDropPayload("FUR_OP",NULL,0,ImGuiCond_Once); \
|
||||||
|
ImGui::Button(ICON_FA_ARROWS "##SysDrag"); \
|
||||||
|
ImGui::SameLine(); \
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \
|
||||||
|
ImGui::Text("(copying)"); \
|
||||||
|
} else { \
|
||||||
|
ImGui::Text("(swapping)"); \
|
||||||
|
} \
|
||||||
|
ImGui::EndDragDropSource(); \
|
||||||
|
} else if (ImGui::IsItemHovered()) { \
|
||||||
|
ImGui::SetTooltip("- drag to swap operator\n- shift-drag to copy operator"); \
|
||||||
|
} \
|
||||||
|
if (ImGui::BeginDragDropTarget()) { \
|
||||||
|
const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_OP"); \
|
||||||
|
if (dragItem!=NULL) { \
|
||||||
|
if (dragItem->IsDataType("FUR_OP")) { \
|
||||||
|
if (opToMove!=i && opToMove>=0) { \
|
||||||
|
int destOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i; \
|
||||||
|
int sourceOp=(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[opToMove]:opToMove; \
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \
|
||||||
|
e->lockEngine([ins,destOp,sourceOp]() { \
|
||||||
|
ins->fm.op[destOp]=ins->fm.op[sourceOp]; \
|
||||||
|
}); \
|
||||||
|
} else { \
|
||||||
|
e->lockEngine([ins,destOp,sourceOp]() { \
|
||||||
|
DivInstrumentFM::Operator origOp=ins->fm.op[sourceOp]; \
|
||||||
|
ins->fm.op[sourceOp]=ins->fm.op[destOp]; \
|
||||||
|
ins->fm.op[destOp]=origOp; \
|
||||||
|
}); \
|
||||||
|
} \
|
||||||
|
PARAMETER; \
|
||||||
|
} \
|
||||||
|
opToMove=-1; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
ImGui::EndDragDropTarget(); \
|
||||||
|
}
|
||||||
|
|
||||||
void FurnaceGUI::drawInsEdit() {
|
void FurnaceGUI::drawInsEdit() {
|
||||||
if (nextWindow==GUI_WINDOW_INS_EDIT) {
|
if (nextWindow==GUI_WINDOW_INS_EDIT) {
|
||||||
insEditOpen=true;
|
insEditOpen=true;
|
||||||
|
@ -1560,7 +1607,14 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!insEditOpen) return;
|
if (!insEditOpen) return;
|
||||||
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
|
if (mobileUI) {
|
||||||
|
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
|
||||||
|
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
|
||||||
|
ImGui::SetNextWindowPos(patWindowPos);
|
||||||
|
ImGui::SetNextWindowSize(patWindowSize);
|
||||||
|
} else {
|
||||||
|
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
|
||||||
|
}
|
||||||
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
|
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
|
||||||
if (curIns<0 || curIns>=(int)e->song.ins.size()) {
|
if (curIns<0 || curIns>=(int)e->song.ins.size()) {
|
||||||
ImGui::Text("no instrument selected");
|
ImGui::Text("no instrument selected");
|
||||||
|
@ -1697,6 +1751,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
int opCount=4;
|
int opCount=4;
|
||||||
if (ins->type==DIV_INS_OPLL) opCount=2;
|
if (ins->type==DIV_INS_OPLL) opCount=2;
|
||||||
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
|
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
|
||||||
|
bool opsAreMutable=(ins->type==DIV_INS_FM);
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("FM")) {
|
if (ImGui::BeginTabItem("FM")) {
|
||||||
if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) {
|
if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) {
|
||||||
|
@ -2051,17 +2106,31 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y;
|
if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y;
|
||||||
|
|
||||||
ImGui::PushID(fmt::sprintf("op%d",i).c_str());
|
ImGui::PushID(fmt::sprintf("op%d",i).c_str());
|
||||||
|
String opNameLabel;
|
||||||
if (ins->type==DIV_INS_OPL_DRUMS) {
|
if (ins->type==DIV_INS_OPL_DRUMS) {
|
||||||
ImGui::Text("%s",oplDrumNames[i]);
|
opNameLabel=fmt::sprintf("%s",oplDrumNames[i]);
|
||||||
} else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) {
|
} else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) {
|
||||||
if (i==1) {
|
if (i==1) {
|
||||||
ImGui::Text("Kick");
|
opNameLabel="Kick";
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Env");
|
opNameLabel="Env";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("OP%d",i+1);
|
opNameLabel=fmt::sprintf("OP%d",i+1);
|
||||||
}
|
}
|
||||||
|
if (opsAreMutable) {
|
||||||
|
pushToggleColors(op.enable);
|
||||||
|
if (ImGui::Button(opNameLabel.c_str())) {
|
||||||
|
op.enable=!op.enable;
|
||||||
|
PARAMETER;
|
||||||
|
}
|
||||||
|
popToggleColors();
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted(opNameLabel.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// drag point
|
||||||
|
OP_DRAG_POINT;
|
||||||
|
|
||||||
int maxTl=127;
|
int maxTl=127;
|
||||||
if (ins->type==DIV_INS_OPLL) {
|
if (ins->type==DIV_INS_OPLL) {
|
||||||
|
@ -2345,8 +2414,20 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
} else {
|
} else {
|
||||||
snprintf(tempID,1024,"Operator %d",i+1);
|
snprintf(tempID,1024,"Operator %d",i+1);
|
||||||
}
|
}
|
||||||
CENTER_TEXT(tempID);
|
float nextCursorPosX=ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(tempID).x-(opsAreMutable?(ImGui::GetStyle().FramePadding.x*2.0f):0.0f));
|
||||||
ImGui::TextUnformatted(tempID);
|
OP_DRAG_POINT;
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosX(nextCursorPosX);
|
||||||
|
if (opsAreMutable) {
|
||||||
|
pushToggleColors(op.enable);
|
||||||
|
if (ImGui::Button(tempID)) {
|
||||||
|
op.enable=!op.enable;
|
||||||
|
PARAMETER;
|
||||||
|
}
|
||||||
|
popToggleColors();
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted(tempID);
|
||||||
|
}
|
||||||
|
|
||||||
float sliderHeight=200.0f*dpiScale;
|
float sliderHeight=200.0f*dpiScale;
|
||||||
float waveWidth=140.0*dpiScale;
|
float waveWidth=140.0*dpiScale;
|
||||||
|
@ -2778,16 +2859,29 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(dpiScale,dpiScale));
|
ImGui::Dummy(ImVec2(dpiScale,dpiScale));
|
||||||
|
String opNameLabel;
|
||||||
|
OP_DRAG_POINT;
|
||||||
|
ImGui::SameLine();
|
||||||
if (ins->type==DIV_INS_OPL_DRUMS) {
|
if (ins->type==DIV_INS_OPL_DRUMS) {
|
||||||
ImGui::Text("%s",oplDrumNames[i]);
|
opNameLabel=fmt::sprintf("%s",oplDrumNames[i]);
|
||||||
} else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) {
|
} else if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) {
|
||||||
if (i==1) {
|
if (i==1) {
|
||||||
ImGui::Text("Envelope 2 (kick only)");
|
opNameLabel="Envelope 2 (kick only)";
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Envelope");
|
opNameLabel="Envelope";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("OP%d",i+1);
|
opNameLabel=fmt::sprintf("OP%d",i+1);
|
||||||
|
}
|
||||||
|
if (opsAreMutable) {
|
||||||
|
pushToggleColors(op.enable);
|
||||||
|
if (ImGui::Button(opNameLabel.c_str())) {
|
||||||
|
op.enable=!op.enable;
|
||||||
|
PARAMETER;
|
||||||
|
}
|
||||||
|
popToggleColors();
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted(opNameLabel.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
@ -3021,7 +3115,14 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("PM Depth",&ins->std.ex2Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("PM Depth",&ins->std.ex2Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("LFO Speed",&ins->std.ex3Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("LFO Speed",&ins->std.ex3Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("LFO Shape",&ins->std.waveMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves));
|
macroList.push_back(FurnaceGUIMacroDesc("LFO Shape",&ins->std.waveMacro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves));
|
||||||
|
}
|
||||||
|
if (ins->type==DIV_INS_FM) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,fmOperatorBits));
|
macroList.push_back(FurnaceGUIMacroDesc("OpMask",&ins->std.ex4Macro,0,4,128,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,fmOperatorBits));
|
||||||
|
} else if (ins->type==DIV_INS_OPZ) {
|
||||||
|
macroList.push_back(FurnaceGUIMacroDesc("AM Depth 2",&ins->std.ex5Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
|
macroList.push_back(FurnaceGUIMacroDesc("PM Depth 2",&ins->std.ex6Macro,0,127,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
|
macroList.push_back(FurnaceGUIMacroDesc("LFO2 Speed",&ins->std.ex7Macro,0,255,128,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
|
macroList.push_back(FurnaceGUIMacroDesc("LFO2 Shape",&ins->std.ex8Macro,0,3,48,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroLFOWaves));
|
||||||
}
|
}
|
||||||
drawMacros(macroList);
|
drawMacros(macroList);
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
|
@ -3352,29 +3453,29 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) {
|
if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) {
|
||||||
ImGui::Text("Waveform");
|
ImGui::Text("Waveform");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn));
|
pushToggleColors(ins->c64.triOn);
|
||||||
if (ImGui::Button("tri")) { PARAMETER
|
if (ImGui::Button("tri")) { PARAMETER
|
||||||
ins->c64.triOn=!ins->c64.triOn;
|
ins->c64.triOn=!ins->c64.triOn;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn));
|
pushToggleColors(ins->c64.sawOn);
|
||||||
if (ImGui::Button("saw")) { PARAMETER
|
if (ImGui::Button("saw")) { PARAMETER
|
||||||
ins->c64.sawOn=!ins->c64.sawOn;
|
ins->c64.sawOn=!ins->c64.sawOn;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn));
|
pushToggleColors(ins->c64.pulseOn);
|
||||||
if (ImGui::Button("pulse")) { PARAMETER
|
if (ImGui::Button("pulse")) { PARAMETER
|
||||||
ins->c64.pulseOn=!ins->c64.pulseOn;
|
ins->c64.pulseOn=!ins->c64.pulseOn;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn));
|
pushToggleColors(ins->c64.noiseOn);
|
||||||
if (ImGui::Button("noise")) { PARAMETER
|
if (ImGui::Button("noise")) { PARAMETER
|
||||||
ins->c64.noiseOn=!ins->c64.noiseOn;
|
ins->c64.noiseOn=!ins->c64.noiseOn;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
|
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
|
||||||
|
|
||||||
|
@ -3436,29 +3537,29 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
|
|
||||||
ImGui::Text("Filter Mode");
|
ImGui::Text("Filter Mode");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp));
|
pushToggleColors(ins->c64.lp);
|
||||||
if (ImGui::Button("low")) { PARAMETER
|
if (ImGui::Button("low")) { PARAMETER
|
||||||
ins->c64.lp=!ins->c64.lp;
|
ins->c64.lp=!ins->c64.lp;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp));
|
pushToggleColors(ins->c64.bp);
|
||||||
if (ImGui::Button("band")) { PARAMETER
|
if (ImGui::Button("band")) { PARAMETER
|
||||||
ins->c64.bp=!ins->c64.bp;
|
ins->c64.bp=!ins->c64.bp;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp));
|
pushToggleColors(ins->c64.hp);
|
||||||
if (ImGui::Button("high")) { PARAMETER
|
if (ImGui::Button("high")) { PARAMETER
|
||||||
ins->c64.hp=!ins->c64.hp;
|
ins->c64.hp=!ins->c64.hp;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off));
|
pushToggleColors(ins->c64.ch3off);
|
||||||
if (ImGui::Button("ch3off")) { PARAMETER
|
if (ImGui::Button("ch3off")) { PARAMETER
|
||||||
ins->c64.ch3off=!ins->c64.ch3off;
|
ins->c64.ch3off=!ins->c64.ch3off;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
|
|
||||||
P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff));
|
P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff));
|
||||||
P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs));
|
P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs));
|
||||||
|
@ -3606,7 +3707,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
modTable[i]=ins->fds.modTable[i];
|
modTable[i]=ins->fds.modTable[i];
|
||||||
}
|
}
|
||||||
ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale);
|
ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale);
|
||||||
PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true);
|
PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true);
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
macroDragStart=ImGui::GetItemRectMin();
|
macroDragStart=ImGui::GetItemRectMin();
|
||||||
macroDragAreaSize=modTableSize;
|
macroDragAreaSize=modTableSize;
|
||||||
|
@ -4225,7 +4326,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (volMax>0) {
|
if (volMax>0) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
|
macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,&ins->std.volMacro,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
|
||||||
}
|
}
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,NULL,false,NULL,0,true));
|
macroList.push_back(FurnaceGUIMacroDesc("Arpeggio",&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,0,true,ins->std.arpMacro.val));
|
||||||
if (dutyMax>0) {
|
if (dutyMax>0) {
|
||||||
if (ins->type==DIV_INS_MIKEY) {
|
if (ins->type==DIV_INS_MIKEY) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits));
|
macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits));
|
||||||
|
@ -4455,6 +4556,28 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
if (ImGui::BeginMenu("randomize...")) {
|
||||||
|
if (macroRandMin<lastMacroDesc.min) macroRandMin=lastMacroDesc.min;
|
||||||
|
if (macroRandMin>lastMacroDesc.max) macroRandMin=lastMacroDesc.max;
|
||||||
|
if (macroRandMax<lastMacroDesc.min) macroRandMax=lastMacroDesc.min;
|
||||||
|
if (macroRandMax>lastMacroDesc.max) macroRandMax=lastMacroDesc.max;
|
||||||
|
ImGui::InputInt("Min",¯oRandMin,1,10);
|
||||||
|
ImGui::InputInt("Max",¯oRandMax,1,10);
|
||||||
|
if (ImGui::Button("randomize")) {
|
||||||
|
for (int i=0; i<lastMacroDesc.macro->len; i++) {
|
||||||
|
int val=0;
|
||||||
|
if (macroRandMax<=macroRandMin) {
|
||||||
|
val=macroRandMin;
|
||||||
|
} else {
|
||||||
|
val=macroRandMin+(rand()%(macroRandMax-macroRandMin+1));
|
||||||
|
}
|
||||||
|
lastMacroDesc.macro->val[i]=val;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,7 +374,7 @@ void FurnaceGUI::drawPattern() {
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f));
|
||||||
if (mobileUI) {
|
if (mobileUI) {
|
||||||
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
|
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
|
||||||
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.3*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
|
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
|
||||||
ImGui::SetNextWindowPos(patWindowPos);
|
ImGui::SetNextWindowPos(patWindowPos);
|
||||||
ImGui::SetNextWindowSize(patWindowSize);
|
ImGui::SetNextWindowSize(patWindowSize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,29 +54,26 @@ void FurnaceGUI::drawPiano() {
|
||||||
if (!pianoOpen) return;
|
if (!pianoOpen) return;
|
||||||
if (mobileUI) {
|
if (mobileUI) {
|
||||||
ImGui::SetNextWindowPos(ImVec2(patWindowPos.x,patWindowPos.y+patWindowSize.y));
|
ImGui::SetNextWindowPos(ImVec2(patWindowPos.x,patWindowPos.y+patWindowSize.y));
|
||||||
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.5*scrW*dpiScale):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),0.3*scrH*dpiScale));
|
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.4*scrW*dpiScale):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),0.3*scrH*dpiScale));
|
||||||
}
|
}
|
||||||
if (ImGui::Begin("Piano",&pianoOpen,((pianoOptions)?0:ImGuiWindowFlags_NoTitleBar)|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
if (ImGui::Begin("Piano",&pianoOpen,((pianoOptions)?0:ImGuiWindowFlags_NoTitleBar)|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
||||||
bool oldPianoKeyPressed[180];
|
bool oldPianoKeyPressed[180];
|
||||||
memcpy(oldPianoKeyPressed,pianoKeyPressed,180*sizeof(bool));
|
memcpy(oldPianoKeyPressed,pianoKeyPressed,180*sizeof(bool));
|
||||||
memset(pianoKeyPressed,0,180*sizeof(bool));
|
memset(pianoKeyPressed,0,180*sizeof(bool));
|
||||||
if (ImGui::BeginTable("PianoLayout",(pianoOptions?2:1)+((pianoInputPadMode==1 && cursor.xFine>0)?1:0),ImGuiTableFlags_BordersInnerV)) {
|
if (ImGui::BeginTable("PianoLayout",((pianoOptions && (!mobileUI || !portrait))?2:1),ImGuiTableFlags_BordersInnerV)) {
|
||||||
int& off=(e->isPlaying() || pianoSharePosition)?pianoOffset:pianoOffsetEdit;
|
int& off=(e->isPlaying() || pianoSharePosition)?pianoOffset:pianoOffsetEdit;
|
||||||
int& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit;
|
int& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit;
|
||||||
bool view=(pianoView==2)?(!e->isPlaying()):pianoView;
|
bool view=(pianoView==2)?(!e->isPlaying()):pianoView;
|
||||||
if (pianoOptions) {
|
if (pianoOptions && (!mobileUI || !portrait)) {
|
||||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||||
}
|
}
|
||||||
if (pianoInputPadMode==1 && cursor.xFine>0) {
|
|
||||||
ImGui::TableSetupColumn("c0s",ImGuiTableColumnFlags_WidthStretch,2.0f);
|
|
||||||
}
|
|
||||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
if (pianoOptions) {
|
if (pianoOptions) {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
float optionSizeY=ImGui::GetContentRegionAvail().y*0.5-ImGui::GetStyle().ItemSpacing.y;
|
float optionSizeY=ImGui::GetContentRegionAvail().y*((mobileUI && portrait)?0.3:0.5)-ImGui::GetStyle().ItemSpacing.y;
|
||||||
ImVec2 optionSize=ImVec2(1.2f*optionSizeY,optionSizeY);
|
ImVec2 optionSize=ImVec2((mobileUI && portrait)?((ImGui::GetContentRegionAvail().x-ImGui::GetStyle().ItemSpacing.x*5.0f)/6.0f):(1.2f*optionSizeY),optionSizeY);
|
||||||
if (pianoOptionsSet) {
|
if (pianoOptionsSet) {
|
||||||
if (ImGui::Button("OFF##PianoNOff",optionSize)) {
|
if (ImGui::Button("OFF##PianoNOff",optionSize)) {
|
||||||
if (edit) noteInput(0,100);
|
if (edit) noteInput(0,100);
|
||||||
|
@ -126,6 +123,10 @@ void FurnaceGUI::drawPiano() {
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mobileUI && portrait) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
if (pianoOptionsSet) {
|
if (pianoOptionsSet) {
|
||||||
if (ImGui::Button("REL##PianoNMRel",optionSize)) {
|
if (ImGui::Button("REL##PianoNMRel",optionSize)) {
|
||||||
if (edit) noteInput(0,102);
|
if (edit) noteInput(0,102);
|
||||||
|
@ -152,6 +153,10 @@ void FurnaceGUI::drawPiano() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mobileUI && portrait) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
if (pianoInputPadMode==1 && cursor.xFine>0) {
|
if (pianoInputPadMode==1 && cursor.xFine>0) {
|
||||||
ImVec2 buttonSize=ImGui::GetContentRegionAvail();
|
ImVec2 buttonSize=ImGui::GetContentRegionAvail();
|
||||||
|
@ -199,192 +204,192 @@ void FurnaceGUI::drawPiano() {
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
ImGui::TableNextColumn();
|
} else {
|
||||||
}
|
ImGuiWindow* window=ImGui::GetCurrentWindow();
|
||||||
ImGuiWindow* window=ImGui::GetCurrentWindow();
|
ImVec2 size=ImGui::GetContentRegionAvail();
|
||||||
ImVec2 size=ImGui::GetContentRegionAvail();
|
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
|
||||||
|
|
||||||
ImVec2 minArea=window->DC.CursorPos;
|
ImVec2 minArea=window->DC.CursorPos;
|
||||||
ImVec2 maxArea=ImVec2(
|
ImVec2 maxArea=ImVec2(
|
||||||
minArea.x+size.x,
|
minArea.x+size.x,
|
||||||
minArea.y+size.y
|
minArea.y+size.y
|
||||||
);
|
);
|
||||||
ImRect rect=ImRect(minArea,maxArea);
|
ImRect rect=ImRect(minArea,maxArea);
|
||||||
|
|
||||||
// render piano
|
// render piano
|
||||||
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
|
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
|
||||||
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
|
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
|
||||||
ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"));
|
ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"));
|
||||||
if (view) {
|
if (view) {
|
||||||
int notes=oct*12;
|
int notes=oct*12;
|
||||||
// evaluate input
|
// evaluate input
|
||||||
for (TouchPoint& i: activePoints) {
|
for (TouchPoint& i: activePoints) {
|
||||||
if (rect.Contains(ImVec2(i.x,i.y))) {
|
if (rect.Contains(ImVec2(i.x,i.y))) {
|
||||||
int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off;
|
int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off;
|
||||||
if (note<0) continue;
|
|
||||||
if (note>=180) continue;
|
|
||||||
pianoKeyPressed[note]=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<notes; i++) {
|
|
||||||
int note=i+12*off;
|
|
||||||
if (note<0) continue;
|
|
||||||
if (note>=180) continue;
|
|
||||||
float pkh=pianoKeyHit[note];
|
|
||||||
ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
|
|
||||||
if (pianoKeyPressed[note]) {
|
|
||||||
color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
|
|
||||||
} else {
|
|
||||||
ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
|
|
||||||
color.x+=(colorHit.x-color.x)*pkh;
|
|
||||||
color.y+=(colorHit.y-color.y)*pkh;
|
|
||||||
color.z+=(colorHit.z-color.z)*pkh;
|
|
||||||
color.w+=(colorHit.w-color.w)*pkh;
|
|
||||||
}
|
|
||||||
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f));
|
|
||||||
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f));
|
|
||||||
p1.x-=dpiScale;
|
|
||||||
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
|
|
||||||
if ((i%12)==0) {
|
|
||||||
String label=fmt::sprintf("%d",(note-60)/12);
|
|
||||||
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
|
|
||||||
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
|
|
||||||
pText.x-=labelSize.x*0.5f;
|
|
||||||
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
|
|
||||||
dl->AddText(pText,0xff404040,label.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int bottomNotes=7*oct;
|
|
||||||
// evaluate input
|
|
||||||
for (TouchPoint& i: activePoints) {
|
|
||||||
if (rect.Contains(ImVec2(i.x,i.y))) {
|
|
||||||
// top
|
|
||||||
int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct;
|
|
||||||
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f));
|
|
||||||
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f));
|
|
||||||
bool foundTopKey=false;
|
|
||||||
|
|
||||||
for (int j=0; j<5; j++) {
|
|
||||||
int note=topKeyNotes[j]+12*(o+off);
|
|
||||||
if (note<0) continue;
|
if (note<0) continue;
|
||||||
if (note>=180) continue;
|
if (note>=180) continue;
|
||||||
ImRect keyRect=ImRect(
|
pianoKeyPressed[note]=true;
|
||||||
ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)),
|
|
||||||
ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f))
|
|
||||||
);
|
|
||||||
if (keyRect.Contains(ImVec2(i.x,i.y))) {
|
|
||||||
pianoKeyPressed[note]=true;
|
|
||||||
foundTopKey=true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (foundTopKey) continue;
|
|
||||||
|
|
||||||
// bottom
|
|
||||||
int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes;
|
|
||||||
int note=bottomKeyNotes[n%7]+12*((n/7)+off);
|
|
||||||
if (note<0) continue;
|
|
||||||
if (note>=180) continue;
|
|
||||||
pianoKeyPressed[note]=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<bottomNotes; i++) {
|
|
||||||
int note=bottomKeyNotes[i%7]+12*((i/7)+off);
|
|
||||||
if (note<0) continue;
|
|
||||||
if (note>=180) continue;
|
|
||||||
|
|
||||||
float pkh=pianoKeyHit[note];
|
|
||||||
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
|
|
||||||
if (pianoKeyPressed[note]) {
|
|
||||||
color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
|
|
||||||
} else {
|
|
||||||
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
|
|
||||||
color.x+=(colorHit.x-color.x)*pkh;
|
|
||||||
color.y+=(colorHit.y-color.y)*pkh;
|
|
||||||
color.z+=(colorHit.z-color.z)*pkh;
|
|
||||||
color.w+=(colorHit.w-color.w)*pkh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f));
|
for (int i=0; i<notes; i++) {
|
||||||
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f));
|
int note=i+12*off;
|
||||||
p1.x-=dpiScale;
|
|
||||||
|
|
||||||
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
|
|
||||||
if ((i%7)==0) {
|
|
||||||
String label=fmt::sprintf("%d",(note-60)/12);
|
|
||||||
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
|
|
||||||
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
|
|
||||||
pText.x-=labelSize.x*0.5f;
|
|
||||||
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
|
|
||||||
dl->AddText(pText,0xff404040,label.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<oct; i++) {
|
|
||||||
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/oct,0.0f));
|
|
||||||
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/oct,1.0f));
|
|
||||||
|
|
||||||
for (int j=0; j<5; j++) {
|
|
||||||
int note=topKeyNotes[j]+12*(i+off);
|
|
||||||
if (note<0) continue;
|
if (note<0) continue;
|
||||||
if (note>=180) continue;
|
if (note>=180) continue;
|
||||||
float pkh=pianoKeyHit[note];
|
float pkh=pianoKeyHit[note];
|
||||||
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP];
|
ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
|
||||||
if (pianoKeyPressed[note]) {
|
if (pianoKeyPressed[note]) {
|
||||||
color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE];
|
color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
|
||||||
} else {
|
} else {
|
||||||
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT];
|
ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
|
||||||
color.x+=(colorHit.x-color.x)*pkh;
|
color.x+=(colorHit.x-color.x)*pkh;
|
||||||
color.y+=(colorHit.y-color.y)*pkh;
|
color.y+=(colorHit.y-color.y)*pkh;
|
||||||
color.z+=(colorHit.z-color.z)*pkh;
|
color.z+=(colorHit.z-color.z)*pkh;
|
||||||
color.w+=(colorHit.w-color.w)*pkh;
|
color.w+=(colorHit.w-color.w)*pkh;
|
||||||
}
|
}
|
||||||
ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f));
|
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f));
|
||||||
ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f));
|
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f));
|
||||||
dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND]));
|
|
||||||
p0.x+=dpiScale;
|
|
||||||
p1.x-=dpiScale;
|
p1.x-=dpiScale;
|
||||||
p1.y-=dpiScale;
|
|
||||||
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
|
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
|
||||||
|
if ((i%12)==0) {
|
||||||
|
String label=fmt::sprintf("%d",(note-60)/12);
|
||||||
|
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
|
||||||
|
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
|
||||||
|
pText.x-=labelSize.x*0.5f;
|
||||||
|
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
|
||||||
|
dl->AddText(pText,0xff404040,label.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int bottomNotes=7*oct;
|
||||||
|
// evaluate input
|
||||||
|
for (TouchPoint& i: activePoints) {
|
||||||
|
if (rect.Contains(ImVec2(i.x,i.y))) {
|
||||||
|
// top
|
||||||
|
int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct;
|
||||||
|
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f));
|
||||||
|
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f));
|
||||||
|
bool foundTopKey=false;
|
||||||
|
|
||||||
|
for (int j=0; j<5; j++) {
|
||||||
|
int note=topKeyNotes[j]+12*(o+off);
|
||||||
|
if (note<0) continue;
|
||||||
|
if (note>=180) continue;
|
||||||
|
ImRect keyRect=ImRect(
|
||||||
|
ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)),
|
||||||
|
ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f))
|
||||||
|
);
|
||||||
|
if (keyRect.Contains(ImVec2(i.x,i.y))) {
|
||||||
|
pianoKeyPressed[note]=true;
|
||||||
|
foundTopKey=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundTopKey) continue;
|
||||||
|
|
||||||
|
// bottom
|
||||||
|
int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes;
|
||||||
|
int note=bottomKeyNotes[n%7]+12*((n/7)+off);
|
||||||
|
if (note<0) continue;
|
||||||
|
if (note>=180) continue;
|
||||||
|
pianoKeyPressed[note]=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i<bottomNotes; i++) {
|
||||||
|
int note=bottomKeyNotes[i%7]+12*((i/7)+off);
|
||||||
|
if (note<0) continue;
|
||||||
|
if (note>=180) continue;
|
||||||
|
|
||||||
|
float pkh=pianoKeyHit[note];
|
||||||
|
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
|
||||||
|
if (pianoKeyPressed[note]) {
|
||||||
|
color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
|
||||||
|
} else {
|
||||||
|
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
|
||||||
|
color.x+=(colorHit.x-color.x)*pkh;
|
||||||
|
color.y+=(colorHit.y-color.y)*pkh;
|
||||||
|
color.z+=(colorHit.z-color.z)*pkh;
|
||||||
|
color.w+=(colorHit.w-color.w)*pkh;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f));
|
||||||
|
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f));
|
||||||
|
p1.x-=dpiScale;
|
||||||
|
|
||||||
|
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
|
||||||
|
if ((i%7)==0) {
|
||||||
|
String label=fmt::sprintf("%d",(note-60)/12);
|
||||||
|
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
|
||||||
|
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
|
||||||
|
pText.x-=labelSize.x*0.5f;
|
||||||
|
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
|
||||||
|
dl->AddText(pText,0xff404040,label.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i<oct; i++) {
|
||||||
|
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/oct,0.0f));
|
||||||
|
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/oct,1.0f));
|
||||||
|
|
||||||
|
for (int j=0; j<5; j++) {
|
||||||
|
int note=topKeyNotes[j]+12*(i+off);
|
||||||
|
if (note<0) continue;
|
||||||
|
if (note>=180) continue;
|
||||||
|
float pkh=pianoKeyHit[note];
|
||||||
|
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP];
|
||||||
|
if (pianoKeyPressed[note]) {
|
||||||
|
color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE];
|
||||||
|
} else {
|
||||||
|
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT];
|
||||||
|
color.x+=(colorHit.x-color.x)*pkh;
|
||||||
|
color.y+=(colorHit.y-color.y)*pkh;
|
||||||
|
color.z+=(colorHit.z-color.z)*pkh;
|
||||||
|
color.w+=(colorHit.w-color.w)*pkh;
|
||||||
|
}
|
||||||
|
ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f));
|
||||||
|
ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f));
|
||||||
|
dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND]));
|
||||||
|
p0.x+=dpiScale;
|
||||||
|
p1.x-=dpiScale;
|
||||||
|
p1.y-=dpiScale;
|
||||||
|
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12;
|
||||||
|
for (int i=0; i<180; i++) {
|
||||||
|
pianoKeyHit[i]-=reduction;
|
||||||
|
if (pianoKeyHit[i]<0) pianoKeyHit[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||||
|
pianoOptions=!pianoOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check released keys
|
||||||
|
for (int i=0; i<180; i++) {
|
||||||
|
int note=i-60;
|
||||||
|
if (!pianoKeyPressed[i]) {
|
||||||
|
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
|
||||||
|
e->synchronized([this,note]() {
|
||||||
|
e->autoNoteOff(-1,note);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// then pressed ones
|
||||||
const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12;
|
|
||||||
for (int i=0; i<180; i++) {
|
for (int i=0; i<180; i++) {
|
||||||
pianoKeyHit[i]-=reduction;
|
int note=i-60;
|
||||||
if (pianoKeyHit[i]<0) pianoKeyHit[i]=0;
|
if (pianoKeyPressed[i]) {
|
||||||
}
|
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
|
||||||
}
|
e->synchronized([this,note]() {
|
||||||
|
e->autoNoteOn(-1,curIns,note);
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
});
|
||||||
pianoOptions=!pianoOptions;
|
if (edit) noteInput(note,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// first check released keys
|
|
||||||
for (int i=0; i<180; i++) {
|
|
||||||
int note=i-60;
|
|
||||||
if (!pianoKeyPressed[i]) {
|
|
||||||
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
|
|
||||||
e->synchronized([this,note]() {
|
|
||||||
e->autoNoteOff(-1,note);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// then pressed ones
|
|
||||||
for (int i=0; i<180; i++) {
|
|
||||||
int note=i-60;
|
|
||||||
if (pianoKeyPressed[i]) {
|
|
||||||
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
|
|
||||||
e->synchronized([this,note]() {
|
|
||||||
e->autoNoteOn(-1,curIns,note);
|
|
||||||
});
|
|
||||||
if (edit) noteInput(note,0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,7 +293,7 @@ void PlotBitfield(const char* label, const int* values, int values_count, int va
|
||||||
PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor);
|
PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor)
|
int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor)
|
||||||
{
|
{
|
||||||
ImGuiContext& g = *GImGui;
|
ImGuiContext& g = *GImGui;
|
||||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||||
|
@ -359,7 +359,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
|
||||||
const float v0 = values_getter(data, (v_idx) % values_count);
|
const float v0 = values_getter(data, (v_idx) % values_count);
|
||||||
const float v1 = values_getter(data, (v_idx + 1) % values_count);
|
const float v1 = values_getter(data, (v_idx + 1) % values_count);
|
||||||
if (hoverFunc) {
|
if (hoverFunc) {
|
||||||
std::string hoverText=hoverFunc(v_idx+values_display_offset,v0);
|
std::string hoverText=hoverFunc(v_idx+values_display_offset,v0,hoverFuncUser);
|
||||||
if (!hoverText.empty()) {
|
if (!hoverText.empty()) {
|
||||||
ImGui::SetTooltip("%s",hoverText.c_str());
|
ImGui::SetTooltip("%s",hoverText.c_str());
|
||||||
}
|
}
|
||||||
|
@ -459,8 +459,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
|
||||||
return idx_hovered;
|
return idx_hovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor)
|
void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor)
|
||||||
{
|
{
|
||||||
FurnacePlotArrayGetterData data(values, stride);
|
FurnacePlotArrayGetterData data(values, stride);
|
||||||
PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, blockMode, guideFunc, values_highlight, highlightColor);
|
PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, hoverFuncUser, blockMode, guideFunc, values_highlight, highlightColor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,4 @@
|
||||||
|
|
||||||
void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
|
void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
|
||||||
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f));
|
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f));
|
||||||
void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float) = NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f));
|
void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float,void*) = NULL, void* hoverFuncUser=NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f));
|
||||||
|
|
|
@ -35,6 +35,12 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!sampleEditOpen) return;
|
if (!sampleEditOpen) return;
|
||||||
|
if (mobileUI) {
|
||||||
|
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
|
||||||
|
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
|
||||||
|
ImGui::SetNextWindowPos(patWindowPos);
|
||||||
|
ImGui::SetNextWindowSize(patWindowSize);
|
||||||
|
}
|
||||||
if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
|
if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
|
||||||
if (curSample<0 || curSample>=(int)e->song.sample.size()) {
|
if (curSample<0 || curSample>=(int)e->song.sample.size()) {
|
||||||
ImGui::Text("no sample selected");
|
ImGui::Text("no sample selected");
|
||||||
|
@ -146,20 +152,20 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
|
|
||||||
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
|
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode));
|
pushToggleColors(!sampleDragMode);
|
||||||
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
|
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
|
||||||
sampleDragMode=false;
|
sampleDragMode=false;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Edit mode: Select");
|
ImGui::SetTooltip("Edit mode: Select");
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode));
|
pushToggleColors(sampleDragMode);
|
||||||
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
|
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
|
||||||
sampleDragMode=true;
|
sampleDragMode=true;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Edit mode: Draw");
|
ImGui::SetTooltip("Edit mode: Draw");
|
||||||
}
|
}
|
||||||
|
@ -681,20 +687,20 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
|
|
||||||
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
|
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode));
|
pushToggleColors(!sampleDragMode);
|
||||||
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
|
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
|
||||||
sampleDragMode=false;
|
sampleDragMode=false;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Edit mode: Select");
|
ImGui::SetTooltip("Edit mode: Select");
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode));
|
pushToggleColors(sampleDragMode);
|
||||||
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
|
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
|
||||||
sampleDragMode=true;
|
sampleDragMode=true;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
popToggleColors();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Edit mode: Draw");
|
ImGui::SetTooltip("Edit mode: Draw");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1130,6 +1130,13 @@ void FurnaceGUI::drawSettings() {
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::InputInt("Number of recent files",&settings.maxRecentFile)) {
|
||||||
|
if (settings.maxRecentFile<0) settings.maxRecentFile=0;
|
||||||
|
if (settings.maxRecentFile>30) settings.maxRecentFile=30;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
ImGui::Text("Pattern view labels:");
|
ImGui::Text("Pattern view labels:");
|
||||||
ImGui::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel);
|
ImGui::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel);
|
||||||
ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel);
|
ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel);
|
||||||
|
@ -2273,6 +2280,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.channelStyle=e->getConfInt("channelStyle",0);
|
settings.channelStyle=e->getConfInt("channelStyle",0);
|
||||||
settings.channelVolStyle=e->getConfInt("channelVolStyle",0);
|
settings.channelVolStyle=e->getConfInt("channelVolStyle",0);
|
||||||
settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1);
|
settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1);
|
||||||
|
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
|
||||||
|
|
||||||
clampSetting(settings.mainFontSize,2,96);
|
clampSetting(settings.mainFontSize,2,96);
|
||||||
clampSetting(settings.patFontSize,2,96);
|
clampSetting(settings.patFontSize,2,96);
|
||||||
|
@ -2371,6 +2379,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.channelStyle,0,5);
|
clampSetting(settings.channelStyle,0,5);
|
||||||
clampSetting(settings.channelVolStyle,0,3);
|
clampSetting(settings.channelVolStyle,0,3);
|
||||||
clampSetting(settings.channelFeedbackStyle,0,3);
|
clampSetting(settings.channelFeedbackStyle,0,3);
|
||||||
|
clampSetting(settings.maxRecentFile,0,30);
|
||||||
|
|
||||||
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
|
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
|
||||||
if (settings.initialSys.size()<4) {
|
if (settings.initialSys.size()<4) {
|
||||||
|
@ -2403,7 +2412,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::commitSettings() {
|
void FurnaceGUI::commitSettings() {
|
||||||
bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") ||
|
bool sampleROMsChanged=settings.yrw801Path!=e->getConfString("yrw801Path","") ||
|
||||||
settings.tg100Path!=e->getConfString("tg100Path","") ||
|
settings.tg100Path!=e->getConfString("tg100Path","") ||
|
||||||
settings.mu5Path!=e->getConfString("mu5Path","");
|
settings.mu5Path!=e->getConfString("mu5Path","");
|
||||||
|
|
||||||
|
@ -2525,6 +2534,7 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("channelStyle",settings.channelStyle);
|
e->setConf("channelStyle",settings.channelStyle);
|
||||||
e->setConf("channelVolStyle",settings.channelVolStyle);
|
e->setConf("channelVolStyle",settings.channelVolStyle);
|
||||||
e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle);
|
e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle);
|
||||||
|
e->setConf("maxRecentFile",settings.maxRecentFile);
|
||||||
|
|
||||||
// colors
|
// colors
|
||||||
for (int i=0; i<GUI_COLOR_MAX; i++) {
|
for (int i=0; i<GUI_COLOR_MAX; i++) {
|
||||||
|
@ -2546,6 +2556,10 @@ void FurnaceGUI::commitSettings() {
|
||||||
|
|
||||||
e->saveConf();
|
e->saveConf();
|
||||||
|
|
||||||
|
while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) {
|
||||||
|
recentFile.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
if (sampleROMsChanged) {
|
if (sampleROMsChanged) {
|
||||||
if (e->loadSampleROMs()) {
|
if (e->loadSampleROMs()) {
|
||||||
showError(e->getLastError());
|
showError(e->getLastError());
|
||||||
|
|
|
@ -402,6 +402,24 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case DIV_SYSTEM_TIA: {
|
||||||
|
ImGui::Text("Mixing mode:");
|
||||||
|
if (ImGui::RadioButton("Mono",(flags&6)==0)) {
|
||||||
|
copyOfFlags=(flags&(~6));
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Mono (no distortion)",(flags&6)==2)) {
|
||||||
|
copyOfFlags=(flags&(~6))|2;
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Stereo",(flags&6)==4)) {
|
||||||
|
copyOfFlags=(flags&(~6))|4;
|
||||||
|
}
|
||||||
|
|
||||||
|
sysPal=flags&1;
|
||||||
|
if (ImGui::Checkbox("PAL",&sysPal)) {
|
||||||
|
copyOfFlags=(flags&(~1))|(unsigned int)sysPal;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case DIV_SYSTEM_PCSPKR: {
|
case DIV_SYSTEM_PCSPKR: {
|
||||||
ImGui::Text("Speaker type:");
|
ImGui::Text("Speaker type:");
|
||||||
if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) {
|
if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) {
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
#include "../ta-log.h"
|
#include "../ta-log.h"
|
||||||
|
|
||||||
|
// table taken from https://nornand.hatenablog.com/entry/2020/11/21/201911
|
||||||
|
// Yamaha why didn't you just use 0-127 as it should be?
|
||||||
|
const unsigned char tlTable[100]={
|
||||||
|
127, 122, 118, 114, 110, 107, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 85, 84, 82, 81,
|
||||||
|
// desde aquí la tabla consiste de valores que bajan de 1 en 1
|
||||||
|
79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58,
|
||||||
|
57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36,
|
||||||
|
35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14,
|
||||||
|
13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||||
|
};
|
||||||
|
|
||||||
bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) {
|
bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) {
|
||||||
SafeReader reader(data,len);
|
SafeReader reader(data,len);
|
||||||
|
|
||||||
|
@ -137,7 +148,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) {
|
||||||
op.sl=15-reader.readC();
|
op.sl=15-reader.readC();
|
||||||
reader.readC(); // LS - ignore
|
reader.readC(); // LS - ignore
|
||||||
op.am=(reader.readC()&0x40)?1:0;
|
op.am=(reader.readC()&0x40)?1:0;
|
||||||
op.tl=3+((99-reader.readC())*124)/99;
|
op.tl=tlTable[reader.readC()%100];
|
||||||
unsigned char freq=reader.readC();
|
unsigned char freq=reader.readC();
|
||||||
logV("OP%d freq: %d",i,freq);
|
logV("OP%d freq: %d",i,freq);
|
||||||
op.mult=freq>>2;
|
op.mult=freq>>2;
|
||||||
|
|
|
@ -32,7 +32,7 @@ const char* waveGenBaseShapes[4]={
|
||||||
"Pulse"
|
"Pulse"
|
||||||
};
|
};
|
||||||
|
|
||||||
const float multFactors[16]={
|
const float multFactors[17]={
|
||||||
M_PI,
|
M_PI,
|
||||||
2*M_PI,
|
2*M_PI,
|
||||||
4*M_PI,
|
4*M_PI,
|
||||||
|
@ -49,6 +49,7 @@ const float multFactors[16]={
|
||||||
26*M_PI,
|
26*M_PI,
|
||||||
28*M_PI,
|
28*M_PI,
|
||||||
30*M_PI,
|
30*M_PI,
|
||||||
|
32*M_PI,
|
||||||
};
|
};
|
||||||
|
|
||||||
void FurnaceGUI::doGenerateWave() {
|
void FurnaceGUI::doGenerateWave() {
|
||||||
|
@ -164,7 +165,14 @@ void FurnaceGUI::drawWaveEdit() {
|
||||||
}
|
}
|
||||||
if (!waveEditOpen) return;
|
if (!waveEditOpen) return;
|
||||||
float wavePreview[257];
|
float wavePreview[257];
|
||||||
ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
|
if (mobileUI) {
|
||||||
|
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
|
||||||
|
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
|
||||||
|
ImGui::SetNextWindowPos(patWindowPos);
|
||||||
|
ImGui::SetNextWindowSize(patWindowSize);
|
||||||
|
} else {
|
||||||
|
ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
|
||||||
|
}
|
||||||
if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
|
if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
|
||||||
if (curWave<0 || curWave>=(int)e->song.wave.size()) {
|
if (curWave<0 || curWave>=(int)e->song.wave.size()) {
|
||||||
ImGui::Text("no wavetable selected");
|
ImGui::Text("no wavetable selected");
|
||||||
|
@ -248,6 +256,9 @@ void FurnaceGUI::drawWaveEdit() {
|
||||||
for (int i=0; i<wave->len; i++) {
|
for (int i=0; i<wave->len; i++) {
|
||||||
if (wave->data[i]>wave->max) wave->data[i]=wave->max;
|
if (wave->data[i]>wave->max) wave->data[i]=wave->max;
|
||||||
wavePreview[i]=wave->data[i];
|
wavePreview[i]=wave->data[i];
|
||||||
|
if (waveSigned && !waveHex) {
|
||||||
|
wavePreview[i]-=(int)((wave->max+1)/2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1];
|
if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1];
|
||||||
|
|
||||||
|
@ -262,9 +273,9 @@ void FurnaceGUI::drawWaveEdit() {
|
||||||
ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here
|
ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here
|
||||||
contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y;
|
contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y;
|
||||||
if (waveEditStyle) {
|
if (waveEditStyle) {
|
||||||
PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion);
|
PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion);
|
||||||
} else {
|
} else {
|
||||||
PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,0,wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true);
|
PlotCustom("##Waveform",wavePreview,wave->len,0,NULL,(waveSigned && !waveHex)?(-(int)((wave->max+1)/2)):0,(waveSigned && !waveHex)?((int)(wave->max/2)):wave->max,contentRegion,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,NULL,true);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
waveDragStart=ImGui::GetItemRectMin();
|
waveDragStart=ImGui::GetItemRectMin();
|
||||||
|
@ -405,7 +416,7 @@ void FurnaceGUI::drawWaveEdit() {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
ImGui::PushID(i);
|
ImGui::PushID(i);
|
||||||
if (CWSliderInt("##WGMULT",&waveGenMult[i],0,15)) {
|
if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) {
|
||||||
doGenerateWave();
|
doGenerateWave();
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
@ -496,7 +507,224 @@ void FurnaceGUI::drawWaveEdit() {
|
||||||
}
|
}
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginTabItem("Mangle")) {
|
if (ImGui::BeginTabItem("WaveTools")) {
|
||||||
|
if (ImGui::BeginTable("WGParamItems",2)) {
|
||||||
|
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##WGScaleX",&waveGenScaleX,1,16)) {
|
||||||
|
if (waveGenScaleX<2) waveGenScaleX=2;
|
||||||
|
if (waveGenScaleX>256) waveGenScaleX=256;
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button("Scale X")) {
|
||||||
|
if (waveGenScaleX>0 && wave->len!=waveGenScaleX) e->lockEngine([this,wave]() {
|
||||||
|
int origData[256];
|
||||||
|
memcpy(origData,wave->data,wave->len*sizeof(int));
|
||||||
|
for (int i=0; i<waveGenScaleX; i++) {
|
||||||
|
wave->data[i]=origData[i*wave->len/waveGenScaleX];
|
||||||
|
}
|
||||||
|
wave->len=waveGenScaleX;
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##WGScaleY",&waveGenScaleY,1,16)) {
|
||||||
|
if (waveGenScaleY<2) waveGenScaleY=2;
|
||||||
|
if (waveGenScaleY>256) waveGenScaleY=256;
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button("Scale Y")) {
|
||||||
|
if (waveGenScaleY>0 && wave->max!=waveGenScaleY) e->lockEngine([this,wave]() {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=(wave->data[i]*(waveGenScaleY+1))/(wave->max+1);
|
||||||
|
}
|
||||||
|
wave->max=waveGenScaleY;
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##WGOffsetX",&waveGenOffsetX,1,16)) {
|
||||||
|
if (waveGenOffsetX<-wave->len+1) waveGenOffsetX=-wave->len+1;
|
||||||
|
if (waveGenOffsetX>wave->len-1) waveGenOffsetX=wave->len-1;
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button("Offset X")) {
|
||||||
|
if (waveGenOffsetX!=0 && wave->len>0) e->lockEngine([this,wave]() {
|
||||||
|
int origData[256];
|
||||||
|
memcpy(origData,wave->data,wave->len*sizeof(int));
|
||||||
|
int realOff=-waveGenOffsetX;
|
||||||
|
while (realOff<0) realOff+=wave->len;
|
||||||
|
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=origData[(i+realOff)%wave->len];
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##WGOffsetY",&waveGenOffsetY,1,16)) {
|
||||||
|
if (waveGenOffsetY<-wave->max) waveGenOffsetY=-wave->max;
|
||||||
|
if (waveGenOffsetY>wave->max) waveGenOffsetY=wave->max;
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button("Offset Y")) {
|
||||||
|
if (waveGenOffsetY!=0) e->lockEngine([this,wave]() {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=CLAMP(wave->data[i]+waveGenOffsetY,0,wave->max);
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##WGSmooth",&waveGenSmooth,1,4)) {
|
||||||
|
if (waveGenSmooth>wave->len) waveGenSmooth=wave->len;
|
||||||
|
if (waveGenSmooth<1) waveGenSmooth=1;
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button("Smooth")) {
|
||||||
|
if (waveGenSmooth>0) e->lockEngine([this,wave]() {
|
||||||
|
int origData[256];
|
||||||
|
memcpy(origData,wave->data,wave->len*sizeof(int));
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
int dataSum=0;
|
||||||
|
for (int j=i; j<i+waveGenSmooth+1; j++) {
|
||||||
|
int pos=(j-((waveGenSmooth+1)/2));
|
||||||
|
while (pos<0) pos+=wave->len;
|
||||||
|
dataSum+=origData[pos%wave->len];
|
||||||
|
}
|
||||||
|
dataSum/=waveGenSmooth+1;
|
||||||
|
wave->data[i]=dataSum;
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
float amp=waveGenAmplify*100.0f;
|
||||||
|
if (ImGui::InputFloat("##WGAmplify",&,1.0f,10.0f)) {
|
||||||
|
waveGenAmplify=amp/100.0f;
|
||||||
|
if (waveGenAmplify<0.0f) waveGenAmplify=0.0f;
|
||||||
|
if (waveGenAmplify>100.0f) waveGenAmplify=100.0f;
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button("Amplify")) {
|
||||||
|
if (waveGenAmplify!=1.0f) e->lockEngine([this,wave]() {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=CLAMP(round((float)(wave->data[i]-(int)( /* Clang can you stop complaining */ (int)(wave->max+1)/(int)2))*waveGenAmplify),(int)(-((wave->max+1)/2)),(int)(wave->max/2))+(int)((wave->max+1)/2);
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 buttonSize=ImGui::GetContentRegionAvail();
|
||||||
|
buttonSize.y=0.0f;
|
||||||
|
ImVec2 buttonSizeHalf=buttonSize;
|
||||||
|
buttonSizeHalf.x-=ImGui::GetStyle().ItemSpacing.x;
|
||||||
|
buttonSizeHalf.x*=0.5;
|
||||||
|
|
||||||
|
if (ImGui::Button("Normalize",buttonSize)) {
|
||||||
|
e->lockEngine([this,wave]() {
|
||||||
|
// find lowest point
|
||||||
|
int lowest=wave->max;
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
if (wave->data[i]<lowest) lowest=wave->data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// find highest point
|
||||||
|
int highest=0;
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
if (wave->data[i]>highest) highest=wave->data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// abort if lowest and highest points are equal
|
||||||
|
if (lowest==highest) return;
|
||||||
|
|
||||||
|
// abort if lowest and highest points already span the entire height
|
||||||
|
if (lowest==wave->max && highest==0) return;
|
||||||
|
|
||||||
|
// apply offset
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]-=lowest;
|
||||||
|
}
|
||||||
|
highest-=lowest;
|
||||||
|
|
||||||
|
// scale
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=(wave->data[i]*wave->max)/highest;
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Invert",buttonSize)) {
|
||||||
|
e->lockEngine([this,wave]() {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=wave->max-wave->data[i];
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Half",buttonSizeHalf)) {
|
||||||
|
int origData[256];
|
||||||
|
memcpy(origData,wave->data,wave->len*sizeof(int));
|
||||||
|
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=origData[i>>1];
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Double",buttonSizeHalf)) {
|
||||||
|
int origData[256];
|
||||||
|
memcpy(origData,wave->data,wave->len*sizeof(int));
|
||||||
|
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=origData[(i*2)%wave->len];
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Convert Signed/Unsigned",buttonSize)) {
|
||||||
|
if (wave->max>0) e->lockEngine([this,wave]() {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
if (wave->data[i]>(wave->max/2)) {
|
||||||
|
wave->data[i]-=(wave->max+1)/2;
|
||||||
|
} else {
|
||||||
|
wave->data[i]+=(wave->max+1)/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Randomize",buttonSize)) {
|
||||||
|
if (wave->max>0) e->lockEngine([this,wave]() {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
wave->data[i]=rand()%wave->max;
|
||||||
|
}
|
||||||
|
MARK_MODIFIED;
|
||||||
|
});
|
||||||
|
}
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
ImGui::EndTabBar();
|
ImGui::EndTabBar();
|
||||||
|
@ -513,12 +741,33 @@ void FurnaceGUI::drawWaveEdit() {
|
||||||
waveHex=true;
|
waveHex=true;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
if (!waveHex) if (ImGui::Button(waveSigned?"±##WaveSign":"+##WaveSign",ImVec2(ImGui::GetFrameHeight(),ImGui::GetFrameHeight()))) {
|
||||||
|
waveSigned=!waveSigned;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Signed/Unsigned");
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here
|
||||||
if (ImGui::InputText("##MMLWave",&mmlStringW)) {
|
if (ImGui::InputText("##MMLWave",&mmlStringW)) {
|
||||||
decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex);
|
int actualData[256];
|
||||||
|
decodeMMLStrW(mmlStringW,actualData,wave->len,(waveSigned && !waveHex)?(-((wave->max+1)/2)):0,(waveSigned && !waveHex)?(wave->max/2):wave->max,waveHex);
|
||||||
|
if (waveSigned && !waveHex) {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
actualData[i]+=(wave->max+1)/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(wave->data,actualData,wave->len*sizeof(int));
|
||||||
}
|
}
|
||||||
if (!ImGui::IsItemActive()) {
|
if (!ImGui::IsItemActive()) {
|
||||||
encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex);
|
int actualData[256];
|
||||||
|
memcpy(actualData,wave->data,256*sizeof(int));
|
||||||
|
if (waveSigned && !waveHex) {
|
||||||
|
for (int i=0; i<wave->len; i++) {
|
||||||
|
actualData[i]-=(wave->max+1)/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encodeMMLStr(mmlStringW,actualData,wave->len,-1,-1,waveHex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sndfile.h>
|
||||||
|
|
||||||
|
#define BUF_SIZE 8192
|
||||||
|
|
||||||
|
// usage: assert_delta file
|
||||||
|
// return values:
|
||||||
|
// - 0: pass (file is silence)
|
||||||
|
// - 1: fail (noise found)
|
||||||
|
// - 2: command line error
|
||||||
|
// - 3: file open error
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc<2) return 2;
|
||||||
|
|
||||||
|
SF_INFO si;
|
||||||
|
memset(&si,0,sizeof(SF_INFO));
|
||||||
|
SNDFILE* sf=sf_open(argv[1],SFM_READ,&si);
|
||||||
|
if (sf==NULL) {
|
||||||
|
fprintf(stderr,"open: %s\n",sf_strerror(NULL));
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (si.channels<1) {
|
||||||
|
fprintf(stderr,"invalid channel count\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
float* buf=malloc(BUF_SIZE*si.channels*sizeof(float));
|
||||||
|
|
||||||
|
sf_count_t totalRead=0;
|
||||||
|
size_t seekPos=0;
|
||||||
|
while ((totalRead=sf_readf_float(sf,buf,BUF_SIZE))!=0) {
|
||||||
|
for (int i=0; i<totalRead*si.channels; i++) {
|
||||||
|
if (buf[i]!=0.0f) {
|
||||||
|
printf("%ld\n",seekPos+(i/si.channels));
|
||||||
|
sf_close(sf);
|
||||||
|
free(buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seekPos+=BUF_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf_close(sf);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -12,6 +12,14 @@ fi
|
||||||
|
|
||||||
echo "lastTest is $lastTest"
|
echo "lastTest is $lastTest"
|
||||||
|
|
||||||
|
if [ -e "test/assert_delta" ]; then
|
||||||
|
echo "assert_delta present."
|
||||||
|
else
|
||||||
|
echo "compiling assert_delta..."
|
||||||
|
gcc -Wall -Wextra -Werror -o "test/assert_delta" "test/assert_delta.c" -lsndfile || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
echo "furnace test suite begin..."
|
echo "furnace test suite begin..."
|
||||||
echo "--- STEP 1: render test files"
|
echo "--- STEP 1: render test files"
|
||||||
mkdir -p "test/result/$testDir" || exit 1
|
mkdir -p "test/result/$testDir" || exit 1
|
||||||
|
@ -21,11 +29,19 @@ if [ -z $lastTest ]; then
|
||||||
echo "skipping since this apparently is your first run."
|
echo "skipping since this apparently is your first run."
|
||||||
else
|
else
|
||||||
mkdir -p "test/delta/$testDir" || exit 1
|
mkdir -p "test/delta/$testDir" || exit 1
|
||||||
ls "test/result/$testDir/" | parallel --verbose -j4 ffmpeg -i "test/result/$lastTest/{0}" -i "test/result/$testDir/{0}" -filter_complex stereotools=phasel=1:phaser=1,amix=inputs=2:duration=longest -c:a pcm_s16le -y "test/delta/$testDir/{0}"
|
ls "test/result/$testDir/" | parallel --verbose -j4 ffmpeg -loglevel fatal -i "test/result/$lastTest/{0}" -i "test/result/$testDir/{0}" -filter_complex stereotools=phasel=1:phaser=1,amix=inputs=2:duration=longest -c:a pcm_s16le -y "test/delta/$testDir/{0}"
|
||||||
fi
|
fi
|
||||||
echo "--- STEP 3: make delta images"
|
echo "--- STEP 3: check deltas"
|
||||||
if [ -z $lastTest ]; then
|
if [ -z $lastTest ]; then
|
||||||
echo "skipping since this apparently is your first run."
|
echo "skipping since this apparently is your first run."
|
||||||
else
|
else
|
||||||
ls "test/result/$testDir/" | parallel --verbose -j4 ffmpeg -i "test/delta/$testDir/{0}" -lavfi showspectrumpic "test/delta/$testDir/{0}.png"
|
for i in `ls "test/result/$testDir"`; do
|
||||||
|
echo -n "$i... "
|
||||||
|
if ./test/assert_delta "test/delta/$testDir/$i"; then
|
||||||
|
echo "[1;32mOK[m"
|
||||||
|
else
|
||||||
|
echo "[1;31mFAIL FAIL FAIL[m"
|
||||||
|
ffmpeg -loglevel quiet -i "test/delta/$testDir/$i" -lavfi showspectrumpic "test/delta/$testDir/$i.png"
|
||||||
|
fi
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in New Issue