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/delta/
|
||||
test/result/
|
||||
test/assert_delta
|
||||
android/.gradle/
|
||||
android/app/build/
|
||||
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/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_opm.cpp
|
||||
|
|
|
@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted.
|
|||
|
||||
the coding style is described here:
|
||||
|
||||
- indentation: two spaces
|
||||
- indentation: two spaces. **strictly** spaces. do NOT use tabs.
|
||||
- modified 1TBS style:
|
||||
- no spaces in function calls
|
||||
- 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
|
||||
|
||||
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)
|
||||
- saves .dmf modules - both modern and legacy
|
||||
- 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)
|
||||
- bug/quirk implementation for increased playback accuracy through compatibility flags
|
||||
- VGM export
|
||||
|
@ -103,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
|||
- built-in visualizer in pattern view
|
||||
- open-source under GPLv2 or later.
|
||||
|
||||
***
|
||||
---
|
||||
# quick references
|
||||
|
||||
- **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.
|
||||
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
|
||||
|
||||
***
|
||||
---
|
||||
# developer info
|
||||
|
||||
[![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.**
|
||||
|
||||
***
|
||||
---
|
||||
# frequently asked questions
|
||||
|
||||
> 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.
|
||||
|
||||
***
|
||||
---
|
||||
# footnotes
|
||||
|
||||
copyright (C) 2021-2022 tildearrow and contributors.
|
||||
|
|
6
TODO.md
6
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
|
||||
- "paste with instrument"
|
||||
- FM operator muting
|
||||
- FM operator swap
|
||||
- bug fixes
|
||||
|
|
|
@ -15,8 +15,8 @@ android {
|
|||
}
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 112
|
||||
versionName "dev112"
|
||||
versionCode 113
|
||||
versionName "dev113"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.tildearrow.furnace"
|
||||
android:versionCode="93"
|
||||
android:versionName="0.6pre1"
|
||||
android:versionCode="113"
|
||||
android:versionName="dev113"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
|
@ -83,13 +83,17 @@
|
|||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
<!-- Drop file event -->
|
||||
<!--
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<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>
|
||||
-->
|
||||
</activity>
|
||||
</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
|
||||
* Copyright (C) 2020 Nuke.YKT
|
||||
* Copyright (C) 2022 Nuke.YKT
|
||||
*
|
||||
* This file is part of Nuked OPM.
|
||||
*
|
||||
|
@ -21,7 +21,7 @@
|
|||
* siliconpr0n.org(digshadow, John McMaster):
|
||||
* YM2151 and other FM chip decaps and die shots.
|
||||
*
|
||||
* version: 0.9.2 beta
|
||||
* version: 0.9.3 beta
|
||||
*/
|
||||
#include <string.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]);
|
||||
|
||||
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;
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
- 114: Furnace dev114
|
||||
- 113: Furnace dev113
|
||||
- 112: Furnace dev112
|
||||
- 111: Furnace dev111
|
||||
- 110: Furnace dev110
|
||||
|
@ -254,6 +256,7 @@ size | description
|
|||
| - 0xc3: OPN CSM - 10 channels
|
||||
| - 0xc4: PC-98 CSM - 20 channels
|
||||
| - 0xc5: YM2610B CSM - 20 channels
|
||||
| - 0xc6: MSM5232 - 8 channels
|
||||
| - 0xde: YM2610B extended - 19 channels
|
||||
| - 0xe0: QSound - 19 channels
|
||||
| - 0xfd: Dummy System - 8 channels
|
||||
|
@ -338,7 +341,8 @@ size | description
|
|||
1 | broken initial position of porta after arp (>=101) or reserved
|
||||
1 | SN periods under 8 are treated as 1 (>=108) 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**
|
||||
2 | virtual tempo numerator 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 | ws
|
||||
1 | ksr
|
||||
12 | reserved
|
||||
1 | operator enabled (>=114) or reserved
|
||||
11 | reserved
|
||||
--- | **Game Boy instrument data**
|
||||
1 | volume
|
||||
1 | direction
|
||||
|
|
|
@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double 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) {
|
||||
conf[key]=value;
|
||||
}
|
||||
|
|
|
@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
int nextOrder=-1;
|
||||
int nextRow=0;
|
||||
int effectVal=0;
|
||||
int lastSuspectedLoopEnd=-1;
|
||||
DivPattern* pat[DIV_MAX_CHANS];
|
||||
unsigned char wsWalked[8192];
|
||||
memset(wsWalked,0,8192);
|
||||
for (int i=0; i<curSubSong->ordersLen; i++) {
|
||||
for (int j=0; j<chans; j++) {
|
||||
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
|
||||
}
|
||||
if (i>lastSuspectedLoopEnd) {
|
||||
lastSuspectedLoopEnd=i;
|
||||
}
|
||||
for (int j=nextRow; j<curSubSong->patLen; j++) {
|
||||
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 l=0; l<curPat[k].effectCols; l++) {
|
||||
effectVal=pat[k]->data[j][5+(l<<1)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||
if (song.jumpTreatment==2) {
|
||||
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||
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) {
|
||||
if (nextOrder==-1) {
|
||||
if (nextOrder==-1 || song.jumpTreatment==0) {
|
||||
nextOrder=effectVal;
|
||||
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<=i) {
|
||||
loopOrder=nextOrder;
|
||||
loopRow=nextRow;
|
||||
loopEnd=i;
|
||||
return;
|
||||
}
|
||||
i=nextOrder-1;
|
||||
nextOrder=-1;
|
||||
break;
|
||||
|
@ -1658,6 +1689,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
speedAB=false;
|
||||
playing=true;
|
||||
skipping=true;
|
||||
memset(walked,0,8192);
|
||||
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
|
||||
while (playing && curOrder<goal) {
|
||||
if (nextTick(preserveDrift)) {
|
||||
|
@ -3421,9 +3453,8 @@ void DivEngine::setConsoleMode(bool enable) {
|
|||
}
|
||||
|
||||
bool DivEngine::switchMaster() {
|
||||
deinitAudioBackend();
|
||||
quitDispatch();
|
||||
initDispatch();
|
||||
logI("switching output...");
|
||||
deinitAudioBackend(true);
|
||||
if (initAudioBackend()) {
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
disCont[i].setRates(got.rate);
|
||||
|
@ -3567,6 +3598,7 @@ void DivEngine::quitDispatch() {
|
|||
|
||||
bool DivEngine::initAudioBackend() {
|
||||
// load values
|
||||
logI("initializing audio.");
|
||||
if (audioEngine==DIV_AUDIO_NULL) {
|
||||
if (getConfString("audioEngine","SDL")=="JACK") {
|
||||
audioEngine=DIV_AUDIO_JACK;
|
||||
|
@ -3672,8 +3704,9 @@ bool DivEngine::initAudioBackend() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivEngine::deinitAudioBackend() {
|
||||
bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
|
||||
if (output!=NULL) {
|
||||
logI("closing audio output.");
|
||||
output->quit();
|
||||
if (output->midiIn) {
|
||||
if (output->midiIn->isDeviceOpen()) {
|
||||
|
@ -3690,7 +3723,9 @@ bool DivEngine::deinitAudioBackend() {
|
|||
output->quitMidi();
|
||||
delete output;
|
||||
output=NULL;
|
||||
//audioEngine=DIV_AUDIO_NULL;
|
||||
if (dueToSwitchMaster) {
|
||||
audioEngine=DIV_AUDIO_NULL;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -46,9 +46,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev112"
|
||||
#define DIV_ENGINE_VERSION 112
|
||||
|
||||
#define DIV_VERSION "dev114"
|
||||
#define DIV_ENGINE_VERSION 114
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
#define DIV_VERSION_FC 0xff02
|
||||
|
@ -358,6 +357,8 @@ class DivEngine {
|
|||
double exportFadeOut;
|
||||
std::map<String,String> conf;
|
||||
std::deque<DivNoteEvent> pendingNotes;
|
||||
// bitfield
|
||||
unsigned char walked[8192];
|
||||
bool isMuted[DIV_MAX_CHANS];
|
||||
std::mutex isBusy, saveLock;
|
||||
String configPath;
|
||||
|
@ -451,7 +452,7 @@ class DivEngine {
|
|||
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
||||
|
||||
bool initAudioBackend();
|
||||
bool deinitAudioBackend();
|
||||
bool deinitAudioBackend(bool dueToSwitchMaster=false);
|
||||
|
||||
void registerSystems();
|
||||
void initSongWithDesc(const int* description);
|
||||
|
@ -543,6 +544,7 @@ class DivEngine {
|
|||
void setConf(String key, int value);
|
||||
void setConf(String key, float value);
|
||||
void setConf(String key, double value);
|
||||
void setConf(String key, const char* value);
|
||||
void setConf(String key, String value);
|
||||
|
||||
// calculate base frequency/period
|
||||
|
@ -1072,6 +1074,7 @@ class DivEngine {
|
|||
memset(reversePitchTable,0,4096*sizeof(int));
|
||||
memset(pitchTable,0,4096*sizeof(int));
|
||||
memset(sysDefs,0,256*sizeof(void*));
|
||||
memset(walked,0,8192);
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||
|
|
|
@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ds.brokenPortaArp=false;
|
||||
ds.snNoLowPeriods=true;
|
||||
ds.delayBehavior=0;
|
||||
ds.jumpTreatment=2;
|
||||
|
||||
// 1.1 compat flags
|
||||
if (ds.version>24) {
|
||||
|
@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
if (ds.version<110) {
|
||||
ds.delayBehavior=1;
|
||||
}
|
||||
if (ds.version<113) {
|
||||
ds.jumpTreatment=1;
|
||||
}
|
||||
ds.isDMF=false;
|
||||
|
||||
reader.readS(); // reserved
|
||||
|
@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
} else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -3747,7 +3756,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeC(song.brokenPortaArp);
|
||||
w->writeC(song.snNoLowPeriods);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -150,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
ins->type=DIV_INS_FM;
|
||||
logD("instrument type is Arcade");
|
||||
break;
|
||||
case 9: // Neo Geo
|
||||
ins->type=DIV_INS_FM;
|
||||
logD("instrument type is Neo Geo");
|
||||
break;
|
||||
default:
|
||||
logD("instrument type is unknown");
|
||||
lastError="unknown instrument type!";
|
||||
lastError=fmt::sprintf("unknown instrument type %d!",sys);
|
||||
delete ins;
|
||||
return;
|
||||
break;
|
||||
|
@ -171,12 +175,22 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
mode=reader.readC();
|
||||
logD("instrument mode is %d",mode);
|
||||
if (mode==0) {
|
||||
if (version<11) {
|
||||
if (ins->type==DIV_INS_FM) {
|
||||
if (sys==9) {
|
||||
ins->type=DIV_INS_AY;
|
||||
} else {
|
||||
ins->type=DIV_INS_STD;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
ins->type=DIV_INS_FM;
|
||||
}
|
||||
|
@ -231,6 +245,16 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
ins->fm.op[j].ksr=reader.readC();
|
||||
ins->fm.op[j].dvb=reader.readC();
|
||||
ins->fm.op[j].dam=reader.readC();
|
||||
} else {
|
||||
if (sys==3 || sys==6) { // OPLL/VRC7
|
||||
ins->fm.op[j].ksr=reader.readC()?1:0;
|
||||
ins->fm.op[j].vib=reader.readC();
|
||||
if (j==0) {
|
||||
ins->fm.opllPreset=ins->fm.op[j].vib>>4;
|
||||
}
|
||||
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();
|
||||
|
@ -240,6 +264,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
ins->fm.op[j].ssgEnv=reader.readC();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // STD
|
||||
logD("reading STD data...");
|
||||
if (ins->type!=DIV_INS_GB) {
|
||||
|
@ -247,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
if (version>5) {
|
||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||
ins->std.volMacro.val[i]=reader.readI();
|
||||
if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS
|
||||
ins->type=DIV_INS_FDS;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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.ksr);
|
||||
|
||||
w->writeC(op.enable);
|
||||
|
||||
// reserved
|
||||
for (int k=0; k<12; k++) {
|
||||
for (int k=0; k<11; k++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
}
|
||||
|
@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
op.ws=reader.readC();
|
||||
op.ksr=reader.readC();
|
||||
|
||||
if (version>=114) {
|
||||
op.enable=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
|
||||
// reserved
|
||||
for (int k=0; k<12; k++) reader.readC();
|
||||
for (int k=0; k<11; k++) reader.readC();
|
||||
}
|
||||
|
||||
// GB
|
||||
|
|
|
@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
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));
|
||||
}
|
||||
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++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[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);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x08,0x78|i);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x08,(chan[i].opMask<<3)|i);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -370,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
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);
|
||||
|
@ -502,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
if(c.value==0) {
|
||||
rWrite(0x01,0x02);
|
||||
}
|
||||
else {
|
||||
rWrite(0x01,0x00);
|
||||
}
|
||||
rWrite(0x18,c.value);
|
||||
break;
|
||||
}
|
||||
|
@ -825,6 +841,8 @@ void DivPlatformArcade::reset() {
|
|||
pmDepth=0x7f;
|
||||
|
||||
//rWrite(0x18,0x10);
|
||||
immWrite(0x01,0x02); // LFO Off
|
||||
immWrite(0x18,0x00); // LFO Freq Off
|
||||
immWrite(0x19,amDepth);
|
||||
immWrite(0x19,0x80|pmDepth);
|
||||
//rWrite(0x1b,0x00);
|
||||
|
|
|
@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM {
|
|||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins;
|
||||
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;
|
||||
unsigned char chVolL, chVolR;
|
||||
unsigned char chVolL, chVolR, opMask;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
|
@ -68,10 +68,12 @@ class DivPlatformArcade: public DivPlatformOPM {
|
|||
portaPause(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(0),
|
||||
chVolL(127),
|
||||
chVolR(127) {}
|
||||
chVolR(127),
|
||||
opMask(15) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
|
|
|
@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
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));
|
||||
}
|
||||
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++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
|
@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -591,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
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);
|
||||
|
|
|
@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
||||
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;
|
||||
unsigned char pan;
|
||||
unsigned char pan, opMask;
|
||||
|
||||
bool dacMode;
|
||||
int dacPeriod;
|
||||
|
@ -82,9 +82,11 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
furnaceDac(false),
|
||||
inPorta(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(0),
|
||||
pan(3),
|
||||
opMask(15),
|
||||
dacMode(false),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#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) {
|
||||
if (c.chan<2) {
|
||||
|
@ -69,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
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]+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;
|
||||
|
||||
|
@ -123,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
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;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
|
@ -397,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
|||
rWrite(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]={
|
||||
|
@ -412,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
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) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
@ -459,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
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) {
|
||||
writeNoteOn=true;
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -544,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
if (i==2) {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
} 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) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
|
|
|
@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel():
|
||||
|
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
keyOff(false),
|
||||
portaPause(false),
|
||||
inPorta(false),
|
||||
mask(true),
|
||||
vol(0),
|
||||
pan(3) {}
|
||||
};
|
||||
|
|
|
@ -249,9 +249,11 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
if (os[1]>32767) os[1]=32767;
|
||||
|
||||
bufL[h]=os[0];
|
||||
if (oplType==3 || oplType==759) {
|
||||
bufR[h]=os[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
//if (useYMFM) {
|
||||
|
@ -1520,7 +1522,7 @@ void DivPlatformOPL::reset() {
|
|||
}
|
||||
|
||||
bool DivPlatformOPL::isStereo() {
|
||||
return true;
|
||||
return (oplType==3 || oplType==759);
|
||||
}
|
||||
|
||||
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:
|
||||
// - 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)*/)
|
||||
{
|
||||
channel = bitfield(index, 0, 3);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <string.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[]={
|
||||
"AUDC0", "15",
|
||||
|
@ -39,7 +39,22 @@ const char** DivPlatformTIA::getRegisterSheet() {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -300,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() {
|
|||
}
|
||||
|
||||
void DivPlatformTIA::reset() {
|
||||
tia.reset();
|
||||
tia.reset(mixingType);
|
||||
memset(regPool,0,16);
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i]=DivPlatformTIA::Channel();
|
||||
|
@ -309,8 +324,12 @@ void DivPlatformTIA::reset() {
|
|||
}
|
||||
}
|
||||
|
||||
float DivPlatformTIA::getPostAmp() {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
bool DivPlatformTIA::isStereo() {
|
||||
return false;
|
||||
return (mixingType==2);
|
||||
}
|
||||
|
||||
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
|
||||
|
@ -333,25 +352,28 @@ void DivPlatformTIA::poke(std::vector<DivRegWrite>& wlist) {
|
|||
|
||||
void DivPlatformTIA::setFlags(unsigned int flags) {
|
||||
if (flags&1) {
|
||||
rate=31250;
|
||||
rate=COLOR_PAL*4.0/5.0;
|
||||
} else {
|
||||
rate=31468;
|
||||
rate=COLOR_NTSC;
|
||||
}
|
||||
chipClock=rate;
|
||||
mixingType=(flags>>1)&3;
|
||||
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) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
mixingType=0;
|
||||
chanOscCounter=0;
|
||||
for (int i=0; i<2; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
tia.channels(1,false);
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 2;
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
#include <queue>
|
||||
#include "sound/tia/TIASnd.h"
|
||||
#include "sound/tia/Audio.h"
|
||||
|
||||
class DivPlatformTIA: public DivDispatch {
|
||||
protected:
|
||||
|
@ -42,7 +42,9 @@ class DivPlatformTIA: public DivDispatch {
|
|||
Channel chan[2];
|
||||
DivDispatchOscBuffer* oscBuf[2];
|
||||
bool isMuted[2];
|
||||
TIASound tia;
|
||||
unsigned char mixingType;
|
||||
unsigned char chanOscCounter;
|
||||
TIA::Audio tia;
|
||||
unsigned char regPool[16];
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
|
@ -61,6 +63,7 @@ class DivPlatformTIA: public DivDispatch {
|
|||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(unsigned int flags);
|
||||
float getPostAmp();
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
|
|
|
@ -906,6 +906,7 @@ void DivPlatformTX81Z::reset() {
|
|||
pmDepth=0x7f;
|
||||
|
||||
//rWrite(0x18,0x10);
|
||||
immWrite(0x18,0x00); // LFO Freq Off
|
||||
immWrite(0x19,amDepth);
|
||||
immWrite(0x19,0x80|pmDepth);
|
||||
//rWrite(0x1b,0x00);
|
||||
|
|
|
@ -273,6 +273,10 @@ void DivPlatformYM2203::tick(bool sysTick) {
|
|||
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));
|
||||
}
|
||||
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++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[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);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -409,6 +414,11 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
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++) {
|
||||
|
|
|
@ -43,9 +43,9 @@ class DivPlatformYM2203: public DivPlatformOPN {
|
|||
DivInstrumentFM state;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen, opMask;
|
||||
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 sample;
|
||||
DivMacroInt std;
|
||||
|
@ -66,6 +66,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
|
|||
psgMode(1),
|
||||
autoEnvNum(0),
|
||||
autoEnvDen(0),
|
||||
opMask(15),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
|
@ -75,6 +76,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
|
|||
inPorta(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1) {}
|
||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
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) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
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) {
|
||||
writeNoteOn=true;
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
// 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),
|
||||
inPorta(false), vol(0), pan(3) {}
|
||||
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
|
|
@ -441,6 +441,10 @@ void DivPlatformYM2608::tick(bool sysTick) {
|
|||
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));
|
||||
}
|
||||
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++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[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);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -683,6 +688,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
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++) {
|
||||
|
|
|
@ -48,9 +48,9 @@ class DivPlatformYM2608: public DivPlatformOPN {
|
|||
DivInstrumentFM state;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen, opMask;
|
||||
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 sample;
|
||||
unsigned char pan;
|
||||
|
@ -72,6 +72,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
|
|||
psgMode(1),
|
||||
autoEnvNum(0),
|
||||
autoEnvDen(0),
|
||||
opMask(15),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
|
@ -81,6 +82,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
|
|||
inPorta(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
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) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
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) {
|
||||
writeNoteOn=true;
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
// 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),
|
||||
inPorta(false), vol(0), pan(3) {}
|
||||
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
|
|
@ -482,6 +482,10 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
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));
|
||||
}
|
||||
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++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[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);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -727,6 +732,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
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++) {
|
||||
|
|
|
@ -73,10 +73,10 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
|||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
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 sample;
|
||||
unsigned char pan;
|
||||
unsigned char pan, opMask;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
|
@ -104,10 +104,12 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
|||
inPorta(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
pan(3) {}
|
||||
pan(3),
|
||||
opMask(15) {}
|
||||
};
|
||||
Channel chan[14];
|
||||
DivDispatchOscBuffer* oscBuf[14];
|
||||
|
|
|
@ -465,6 +465,10 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
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));
|
||||
}
|
||||
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++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[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);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
@ -709,6 +714,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
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++) {
|
||||
|
|
|
@ -40,10 +40,10 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
|||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
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 sample;
|
||||
unsigned char pan;
|
||||
unsigned char pan, opMask;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
|
@ -71,10 +71,12 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
|||
inPorta(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
pan(3) {}
|
||||
pan(3),
|
||||
opMask(15) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
DivDispatchOscBuffer* oscBuf[16];
|
||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
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) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
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) {
|
||||
writeNoteOn=true;
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
// 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),
|
||||
inPorta(false), vol(0), pan(3) {}
|
||||
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
|
|
@ -59,6 +59,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[1]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||
|
@ -358,7 +359,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
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) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
@ -395,10 +396,12 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
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) {
|
||||
writeNoteOn=true;
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
// 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),
|
||||
inPorta(false), vol(0), pan(3) {}
|
||||
inPorta(false), mask(true), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
|
|
@ -31,7 +31,9 @@ void DivEngine::nextOrder() {
|
|||
curRow=0;
|
||||
if (repeatPattern) return;
|
||||
if (++curOrder>=curSubSong->ordersLen) {
|
||||
logV("end of orders reached");
|
||||
endOfSong=true;
|
||||
memset(walked,0,8192);
|
||||
curOrder=0;
|
||||
}
|
||||
}
|
||||
|
@ -348,16 +350,32 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal>0) speed2=effectVal;
|
||||
break;
|
||||
case 0x0b: // change order
|
||||
if (changeOrd==-1) {
|
||||
if (changeOrd==-1 || song.jumpTreatment==0) {
|
||||
changeOrd=effectVal;
|
||||
if (song.jumpTreatment==1 || song.jumpTreatment==2) {
|
||||
changePos=0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x0d: // next order
|
||||
if (song.jumpTreatment==2) {
|
||||
if ((curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||
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;
|
||||
case 0xed: // delay
|
||||
if (effectVal!=0) {
|
||||
|
@ -911,18 +929,23 @@ void DivEngine::nextRow() {
|
|||
processRow(i,false);
|
||||
}
|
||||
|
||||
walked[((curOrder<<5)+(curRow>>3))&8191]|=1<<(curRow&7);
|
||||
|
||||
if (changeOrd!=-1) {
|
||||
if (repeatPattern) {
|
||||
curRow=0;
|
||||
changeOrd=-1;
|
||||
} else {
|
||||
curRow=changePos;
|
||||
changePos=0;
|
||||
if (changeOrd==-2) changeOrd=curOrder+1;
|
||||
if (changeOrd<=curOrder) endOfSong=true;
|
||||
// old loop detection routine
|
||||
//if (changeOrd<=curOrder) endOfSong=true;
|
||||
curOrder=changeOrd;
|
||||
if (curOrder>=curSubSong->ordersLen) {
|
||||
curOrder=0;
|
||||
endOfSong=true;
|
||||
memset(walked,0,8192);
|
||||
}
|
||||
changeOrd=-1;
|
||||
}
|
||||
|
@ -932,6 +955,13 @@ void DivEngine::nextRow() {
|
|||
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 ((curSubSong->patLen&1) && curOrder&1) {
|
||||
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
|
||||
|
|
|
@ -468,6 +468,11 @@ struct DivSong {
|
|||
// 1: broken (don't allow value higher than speed)
|
||||
// 2: lax (allow value higher than speed)
|
||||
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 waveDutyIsVol;
|
||||
bool resetMacroOnPorta;
|
||||
|
@ -571,6 +576,7 @@ struct DivSong {
|
|||
pitchSlideSpeed(4),
|
||||
loopModality(2),
|
||||
delayBehavior(2),
|
||||
jumpTreatment(0),
|
||||
properNoiseLayout(true),
|
||||
waveDutyIsVol(false),
|
||||
resetMacroOnPorta(false),
|
||||
|
|
|
@ -82,13 +82,15 @@ const char* aboutLine[]={
|
|||
"Laggy",
|
||||
"LovelyA72",
|
||||
"LunaMoth",
|
||||
"Lunathir",
|
||||
"LVintageNerd",
|
||||
"Mahbod Karamoozian",
|
||||
"Miker",
|
||||
"nicco1690",
|
||||
"NikonTeen",
|
||||
"psdominator",
|
||||
"psxdominator",
|
||||
"Raijin",
|
||||
"SnugglyBun",
|
||||
"SuperJet Spade",
|
||||
"TheDuccinator",
|
||||
"theloredev",
|
||||
|
@ -102,7 +104,7 @@ const char* aboutLine[]={
|
|||
"fd",
|
||||
"GENATARi",
|
||||
"host12prog",
|
||||
"lunathir",
|
||||
"Lunathir",
|
||||
"plane",
|
||||
"TheEssem",
|
||||
"",
|
||||
|
|
|
@ -213,6 +213,26 @@ void FurnaceGUI::drawCompatFlags() {
|
|||
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::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"
|
||||
};
|
||||
|
||||
void FurnaceGUI::drawInsList() {
|
||||
void FurnaceGUI::drawInsList(bool asChild) {
|
||||
if (nextWindow==GUI_WINDOW_INS_LIST) {
|
||||
insListOpen=true;
|
||||
ImGui::SetNextWindowFocus();
|
||||
nextWindow=GUI_WINDOW_NOTHING;
|
||||
}
|
||||
if (!insListOpen) return;
|
||||
if (ImGui::Begin("Instruments",&insListOpen,globalWinFlags)) {
|
||||
if (!insListOpen && !asChild) return;
|
||||
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 (ImGui::Button(ICON_FA_PLUS "##InsAdd")) {
|
||||
if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD);
|
||||
|
@ -409,11 +415,15 @@ void FurnaceGUI::drawInsList() {
|
|||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
if (asChild) {
|
||||
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) {
|
||||
waveListOpen=true;
|
||||
if (settings.unifiedDataView) {
|
||||
|
@ -424,8 +434,14 @@ void FurnaceGUI::drawWaveList() {
|
|||
nextWindow=GUI_WINDOW_NOTHING;
|
||||
}
|
||||
if (settings.unifiedDataView) return;
|
||||
if (!waveListOpen) return;
|
||||
if (ImGui::Begin("Wavetables",&waveListOpen,globalWinFlags)) {
|
||||
if (!waveListOpen && !asChild) return;
|
||||
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")) {
|
||||
doAction(GUI_ACTION_WAVE_LIST_ADD);
|
||||
}
|
||||
|
@ -476,11 +492,15 @@ void FurnaceGUI::drawWaveList() {
|
|||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
if (asChild) {
|
||||
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) {
|
||||
sampleListOpen=true;
|
||||
if (settings.unifiedDataView) {
|
||||
|
@ -491,8 +511,14 @@ void FurnaceGUI::drawSampleList() {
|
|||
nextWindow=GUI_WINDOW_NOTHING;
|
||||
}
|
||||
if (settings.unifiedDataView) return;
|
||||
if (!sampleListOpen) return;
|
||||
if (ImGui::Begin("Samples",&sampleListOpen,globalWinFlags)) {
|
||||
if (!sampleListOpen && !asChild) return;
|
||||
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")) {
|
||||
doAction(GUI_ACTION_SAMPLE_LIST_ADD);
|
||||
}
|
||||
|
@ -548,9 +574,13 @@ void FurnaceGUI::drawSampleList() {
|
|||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
if (asChild) {
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST;
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::actualWaveList() {
|
||||
float wavePreview[257];
|
||||
|
|
|
@ -59,12 +59,12 @@ void FurnaceGUI::drawMobileControls() {
|
|||
|
||||
if (!portrait) ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
||||
pushToggleColors(e->isPlaying());
|
||||
if (portrait) ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) {
|
||||
play();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
if (portrait) ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) {
|
||||
stop();
|
||||
|
@ -76,27 +76,27 @@ void FurnaceGUI::drawMobileControls() {
|
|||
}
|
||||
|
||||
bool repeatPattern=e->getRepeatPattern();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
||||
pushToggleColors(repeatPattern);
|
||||
if (portrait) ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) {
|
||||
e->setRepeatPattern(!repeatPattern);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
||||
pushToggleColors(edit);
|
||||
if (portrait) ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) {
|
||||
edit=!edit;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
bool metro=e->getMetronome();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
||||
pushToggleColors(metro);
|
||||
if (portrait) ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) {
|
||||
e->setMetronome(!metro);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||
ImGui::End();
|
||||
|
@ -104,16 +104,75 @@ 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::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)) {
|
||||
ImGui::Button("Pattern");
|
||||
ImGui::SameLine();
|
||||
ImGui::Button("Ins");
|
||||
ImGui::SameLine();
|
||||
ImGui::Button("Wave");
|
||||
ImGui::SameLine();
|
||||
ImGui::Button("Sample");
|
||||
if (ImGui::BeginTable("SceneSel",5)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||
ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,1.0f);
|
||||
|
||||
ImGui::Text("Data list goes here...");
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImVec2 buttonSize=ImGui::GetContentRegionAvail();
|
||||
buttonSize.y=30.0f*dpiScale;
|
||||
|
||||
if (ImGui::Button("Pattern",buttonSize)) {
|
||||
mobScene=GUI_SCENE_PATTERN;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Orders",buttonSize)) {
|
||||
mobScene=GUI_SCENE_ORDERS;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Ins",buttonSize)) {
|
||||
mobScene=GUI_SCENE_INSTRUMENT;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Wave",buttonSize)) {
|
||||
mobScene=GUI_SCENE_WAVETABLE;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Sample",buttonSize)) {
|
||||
mobScene=GUI_SCENE_SAMPLE;
|
||||
}
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Song",buttonSize)) {
|
||||
mobScene=GUI_SCENE_SONG;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Channels",buttonSize)) {
|
||||
mobScene=GUI_SCENE_CHANNELS;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Chips",buttonSize)) {
|
||||
mobScene=GUI_SCENE_CHIPS;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Other",buttonSize)) {
|
||||
mobScene=GUI_SCENE_OTHER;
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (settings.unifiedDataView) {
|
||||
drawInsList(true);
|
||||
} else {
|
||||
switch (mobScene) {
|
||||
case GUI_SCENE_PATTERN:
|
||||
case GUI_SCENE_ORDERS:
|
||||
case GUI_SCENE_INSTRUMENT:
|
||||
drawInsList(true);
|
||||
break;
|
||||
case GUI_SCENE_WAVETABLE:
|
||||
drawWaveList(true);
|
||||
break;
|
||||
case GUI_SCENE_SAMPLE:
|
||||
drawSampleList(true);
|
||||
break;
|
||||
case GUI_SCENE_SONG: {
|
||||
if (ImGui::Button("New")) {
|
||||
mobileMenuOpen=false;
|
||||
//doAction(GUI_ACTION_NEW);
|
||||
|
@ -148,21 +207,19 @@ void FurnaceGUI::drawMobileControls() {
|
|||
ImGui::Button("Export VGM");
|
||||
|
||||
ImGui::Button("CmdStream");
|
||||
ImGui::SameLine();
|
||||
ImGui::Button("Panic");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Settings")) {
|
||||
mobileMenuOpen=false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("About")) {
|
||||
mobileMenuOpen=false;
|
||||
mobileMenuPos=0.0f;
|
||||
aboutOpen=true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -178,6 +235,27 @@ void FurnaceGUI::drawMobileControls() {
|
|||
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();
|
||||
}
|
||||
|
@ -228,11 +306,11 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
||||
pushToggleColors(e->isPlaying());
|
||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||
play();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_STOP "##Stop")) {
|
||||
stop();
|
||||
|
@ -262,12 +340,12 @@ void FurnaceGUI::drawEditControls() {
|
|||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
||||
pushToggleColors(noteInputPoly);
|
||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||
noteInputPoly=!noteInputPoly;
|
||||
e->setAutoNotePoly(noteInputPoly);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||
ImGui::End();
|
||||
|
@ -278,11 +356,11 @@ void FurnaceGUI::drawEditControls() {
|
|||
stop();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying()));
|
||||
pushToggleColors(e->isPlaying());
|
||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||
play();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) {
|
||||
e->stepOne(cursor.y);
|
||||
|
@ -291,26 +369,26 @@ void FurnaceGUI::drawEditControls() {
|
|||
|
||||
ImGui::SameLine();
|
||||
bool repeatPattern=e->getRepeatPattern();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
||||
pushToggleColors(repeatPattern);
|
||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) {
|
||||
e->setRepeatPattern(!repeatPattern);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
||||
pushToggleColors(edit);
|
||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) {
|
||||
edit=!edit;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::SameLine();
|
||||
bool metro=e->getMetronome();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
||||
pushToggleColors(metro);
|
||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) {
|
||||
e->setMetronome(!metro);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Octave");
|
||||
|
@ -347,12 +425,12 @@ void FurnaceGUI::drawEditControls() {
|
|||
unimportant(ImGui::Checkbox("Pattern",&followPattern));
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
||||
pushToggleColors(noteInputPoly);
|
||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||
noteInputPoly=!noteInputPoly;
|
||||
e->setAutoNotePoly(noteInputPoly);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||
ImGui::End();
|
||||
|
@ -360,11 +438,11 @@ void FurnaceGUI::drawEditControls() {
|
|||
case 2: // compact vertical
|
||||
if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
||||
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)) {
|
||||
play();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) {
|
||||
stop();
|
||||
}
|
||||
|
@ -374,24 +452,24 @@ void FurnaceGUI::drawEditControls() {
|
|||
}
|
||||
|
||||
bool repeatPattern=e->getRepeatPattern();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
||||
pushToggleColors(repeatPattern);
|
||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) {
|
||||
e->setRepeatPattern(!repeatPattern);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
||||
pushToggleColors(edit);
|
||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) {
|
||||
edit=!edit;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
bool metro=e->getMetronome();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
||||
pushToggleColors(metro);
|
||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) {
|
||||
e->setMetronome(!metro);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::Text("Oct.");
|
||||
float avail=ImGui::GetContentRegionAvail().x;
|
||||
|
@ -418,23 +496,23 @@ void FurnaceGUI::drawEditControls() {
|
|||
}
|
||||
|
||||
ImGui::Text("Foll.");
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders));
|
||||
pushToggleColors(followOrders);
|
||||
if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant
|
||||
followOrders=!followOrders;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern));
|
||||
popToggleColors();
|
||||
pushToggleColors(followPattern);
|
||||
if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant
|
||||
followPattern=!followPattern;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
||||
pushToggleColors(noteInputPoly);
|
||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||
noteInputPoly=!noteInputPoly;
|
||||
e->setAutoNotePoly(noteInputPoly);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||
ImGui::End();
|
||||
|
@ -442,11 +520,11 @@ void FurnaceGUI::drawEditControls() {
|
|||
case 3: // split
|
||||
if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
|
||||
if (e->isPlaying()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]);
|
||||
pushToggleColors(true);
|
||||
if (ImGui::Button(ICON_FA_STOP "##Stop")) {
|
||||
stop();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
} else {
|
||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||
play(oldRow);
|
||||
|
@ -469,35 +547,35 @@ void FurnaceGUI::drawEditControls() {
|
|||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit));
|
||||
pushToggleColors(edit);
|
||||
if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) {
|
||||
edit=!edit;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
bool metro=e->getMetronome();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro));
|
||||
pushToggleColors(metro);
|
||||
if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) {
|
||||
e->setMetronome(!metro);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::SameLine();
|
||||
bool repeatPattern=e->getRepeatPattern();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern));
|
||||
pushToggleColors(repeatPattern);
|
||||
if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) {
|
||||
e->setRepeatPattern(!repeatPattern);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(noteInputPoly));
|
||||
pushToggleColors(noteInputPoly);
|
||||
if (ImGui::Button(noteInputPoly?("Poly##PolyInput"):("Mono##PolyInput"))) {
|
||||
noteInputPoly=!noteInputPoly;
|
||||
e->setAutoNotePoly(noteInputPoly);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||
ImGui::End();
|
||||
|
|
|
@ -431,7 +431,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
|||
int startOff=-1;
|
||||
bool invalidData=false;
|
||||
if (data.size()<2) return;
|
||||
if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return;
|
||||
if (data[0].find("org.tildearrow.furnace - Pattern Data")!=0) return;
|
||||
if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return;
|
||||
if (startOff<0) return;
|
||||
|
||||
|
|
324
src/gui/gui.cpp
324
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;
|
||||
bool negaBuf=false;
|
||||
bool hasVal=false;
|
||||
|
@ -264,9 +264,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma
|
|||
case ' ':
|
||||
if (hasVal) {
|
||||
hasVal=false;
|
||||
negaBuf=false;
|
||||
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;
|
||||
macroLen++;
|
||||
buf=0;
|
||||
|
@ -277,9 +277,9 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma
|
|||
}
|
||||
if (hasVal && macroLen<256) {
|
||||
hasVal=false;
|
||||
negaBuf=false;
|
||||
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;
|
||||
macroLen++;
|
||||
buf=0;
|
||||
|
@ -536,6 +536,7 @@ void FurnaceGUI::setFileName(String name) {
|
|||
}
|
||||
#endif
|
||||
updateWindowTitle();
|
||||
pushRecentFile(curFileName);
|
||||
}
|
||||
|
||||
void FurnaceGUI::updateWindowTitle() {
|
||||
|
@ -1724,6 +1725,7 @@ int FurnaceGUI::save(String path, int dmfVersion) {
|
|||
if (!e->getWarnings().empty()) {
|
||||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||
}
|
||||
pushRecentFile(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1801,9 +1803,26 @@ int FurnaceGUI::load(String path) {
|
|||
if (!e->getWarnings().empty()) {
|
||||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||
}
|
||||
pushRecentFile(path);
|
||||
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) {
|
||||
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
||||
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) {
|
||||
return ((FurnaceGUI*)instance)->processEvent(event);
|
||||
}
|
||||
|
@ -2544,12 +2592,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
|||
TouchPoint* point=NULL;
|
||||
FIND_POINT(point,ev.tfinger.fingerId);
|
||||
if (point!=NULL) {
|
||||
float prevX=point->x;
|
||||
float prevY=point->y;
|
||||
point->x=ev.tfinger.x*scrW*dpiScale;
|
||||
point->y=ev.tfinger.y*scrH*dpiScale;
|
||||
point->z=ev.tfinger.pressure;
|
||||
|
||||
if (point->id==0) {
|
||||
ImGui::GetIO().AddMousePosEvent(point->x,point->y);
|
||||
pointMotion(point->x,point->y,point->x-prevX,point->y-prevY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -2570,6 +2621,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
|||
if (newPoint.id==0) {
|
||||
ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y);
|
||||
ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true);
|
||||
pointDown(newPoint.x,newPoint.y,0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -2577,13 +2629,15 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
|
|||
for (size_t i=0; i<activePoints.size(); i++) {
|
||||
TouchPoint& point=activePoints[i];
|
||||
if (point.id==ev.tfinger.fingerId) {
|
||||
releasedPoints.push_back(point);
|
||||
activePoints.erase(activePoints.begin()+i);
|
||||
|
||||
if (point.id==0) {
|
||||
ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,false);
|
||||
//ImGui::GetIO().AddMousePosEvent(-FLT_MAX,-FLT_MAX);
|
||||
pointUp(point.x,point.y,0);
|
||||
}
|
||||
|
||||
releasedPoints.push_back(point);
|
||||
activePoints.erase(activePoints.begin()+i);
|
||||
|
||||
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
|
||||
#define OOB_PIXELS_SAFETY 25
|
||||
|
||||
|
@ -2658,80 +2790,14 @@ bool FurnaceGUI::loop() {
|
|||
motionXrel*=dpiScale;
|
||||
motionYrel*=dpiScale;
|
||||
#endif
|
||||
if (selecting) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
pointMotion(motionX,motionY,motionXrel,motionYrel);
|
||||
break;
|
||||
}
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
pointUp(ev.button.x,ev.button.y,ev.button.button);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
aboutOpen=false;
|
||||
if (bindSetActive) {
|
||||
bindSetActive=false;
|
||||
bindSetPending=false;
|
||||
actionKeys[bindSetTarget]=bindSetPrevValue;
|
||||
bindSetTarget=0;
|
||||
bindSetPrevValue=0;
|
||||
}
|
||||
pointDown(ev.button.x,ev.button.y,ev.button.button);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
wheelX+=ev.wheel.x;
|
||||
|
@ -3027,6 +3093,27 @@ bool FurnaceGUI::loop() {
|
|||
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();
|
||||
if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) {
|
||||
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) {
|
||||
|
@ -3227,6 +3314,11 @@ bool FurnaceGUI::loop() {
|
|||
if (ImGui::MenuItem("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))) {
|
||||
syncSettings();
|
||||
settingsOpen=true;
|
||||
|
@ -3353,8 +3445,37 @@ bool FurnaceGUI::loop() {
|
|||
// scene handling goes here!
|
||||
pianoOpen=true;
|
||||
drawMobileControls();
|
||||
switch (mobScene) {
|
||||
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 {
|
||||
globalWinFlags=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) {
|
||||
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;
|
||||
#ifdef __APPLE__
|
||||
SDL_RaiseWindow(sdlWin);
|
||||
|
@ -4522,6 +4650,7 @@ bool FurnaceGUI::init() {
|
|||
|
||||
tempoView=e->getConfBool("tempoView",true);
|
||||
waveHex=e->getConfBool("waveHex",false);
|
||||
waveSigned=e->getConfBool("waveSigned",false);
|
||||
waveGenVisible=e->getConfBool("waveGenVisible",false);
|
||||
waveEditStyle=e->getConfInt("waveEditStyle",0);
|
||||
lockLayout=e->getConfBool("lockLayout",false);
|
||||
|
@ -4569,6 +4698,13 @@ bool FurnaceGUI::init() {
|
|||
|
||||
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) {
|
||||
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);
|
||||
#endif
|
||||
|
||||
#ifdef IS_MOBILE
|
||||
scrW=960;
|
||||
scrH=540;
|
||||
scrX=0;
|
||||
scrY=0;
|
||||
#else
|
||||
scrW=scrConfW=e->getConfInt("lastWindowWidth",1280);
|
||||
scrH=scrConfH=e->getConfInt("lastWindowHeight",800);
|
||||
scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED);
|
||||
scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED);
|
||||
scrMax=e->getConfBool("lastWindowMax",false);
|
||||
#endif
|
||||
portrait=(scrW<scrH);
|
||||
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
|
||||
|
||||
#ifndef __APPLE__
|
||||
#if !defined(__APPLE__) && !defined(IS_MOBILE)
|
||||
SDL_Rect displaySize;
|
||||
#endif
|
||||
|
||||
|
@ -4604,12 +4747,14 @@ bool FurnaceGUI::init() {
|
|||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
#ifndef IS_MOBILE
|
||||
// if window would spawn out of bounds, force it to be get default position
|
||||
if (!detectOutOfBoundsWindow()) {
|
||||
scrMax=false;
|
||||
scrX=scrConfX=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));
|
||||
if (sdlWin==NULL) {
|
||||
|
@ -4623,6 +4768,7 @@ bool FurnaceGUI::init() {
|
|||
SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(sdlWin),&dpiScaleF,NULL,NULL);
|
||||
dpiScale=round(dpiScaleF/96.0f);
|
||||
if (dpiScale<1) dpiScale=1;
|
||||
#ifndef IS_MOBILE
|
||||
if (dpiScale!=1) {
|
||||
if (!fullScreen) {
|
||||
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
|
||||
|
@ -4642,6 +4788,7 @@ bool FurnaceGUI::init() {
|
|||
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -4789,6 +4936,7 @@ bool FurnaceGUI::finish() {
|
|||
|
||||
e->setConf("tempoView",tempoView);
|
||||
e->setConf("waveHex",waveHex);
|
||||
e->setConf("waveSigned",waveSigned);
|
||||
e->setConf("waveGenVisible",waveGenVisible);
|
||||
e->setConf("waveEditStyle",waveEditStyle);
|
||||
e->setConf("lockLayout",lockLayout);
|
||||
|
@ -4830,6 +4978,16 @@ bool FurnaceGUI::finish() {
|
|||
e->setConf("chanOscUseGrad",chanOscUseGrad);
|
||||
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++) {
|
||||
delete oldPat[i];
|
||||
}
|
||||
|
@ -4985,6 +5143,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
firstFrame(true),
|
||||
tempoView(true),
|
||||
waveHex(false),
|
||||
waveSigned(false),
|
||||
waveGenVisible(false),
|
||||
lockLayout(false),
|
||||
editOptsVisible(false),
|
||||
|
@ -5057,6 +5216,8 @@ FurnaceGUI::FurnaceGUI():
|
|||
macroOffY(0),
|
||||
macroScaleX(100.0f),
|
||||
macroScaleY(100.0f),
|
||||
macroRandMin(0),
|
||||
macroRandMax(0),
|
||||
macroLoopDragStart(0,0),
|
||||
macroLoopDragAreaSize(0,0),
|
||||
macroLoopDragTarget(NULL),
|
||||
|
@ -5087,6 +5248,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
chanToMove(-1),
|
||||
sysToMove(-1),
|
||||
sysToDelete(-1),
|
||||
opToMove(-1),
|
||||
transposeAmount(0),
|
||||
randomizeMin(0),
|
||||
randomizeMax(255),
|
||||
|
@ -5171,6 +5333,12 @@ FurnaceGUI::FurnaceGUI():
|
|||
waveGenDuty(0.5f),
|
||||
waveGenPower(1),
|
||||
waveGenInvertPoint(1.0f),
|
||||
waveGenScaleX(32),
|
||||
waveGenScaleY(31),
|
||||
waveGenOffsetX(0),
|
||||
waveGenOffsetY(0),
|
||||
waveGenSmooth(1),
|
||||
waveGenAmplify(1.0f),
|
||||
waveGenFM(false) {
|
||||
// value keys
|
||||
valueKeys[SDLK_0]=0;
|
||||
|
|
|
@ -47,8 +47,6 @@
|
|||
#define MARK_MODIFIED modified=true;
|
||||
#define WAKE_UP drawHalt=16;
|
||||
|
||||
#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF])
|
||||
|
||||
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
|
||||
|
||||
// TODO:
|
||||
|
@ -265,7 +263,11 @@ enum FurnaceGUIMobileScenes {
|
|||
GUI_SCENE_ORDERS,
|
||||
GUI_SCENE_INSTRUMENT,
|
||||
GUI_SCENE_WAVETABLE,
|
||||
GUI_SCENE_SAMPLE
|
||||
GUI_SCENE_SAMPLE,
|
||||
GUI_SCENE_SONG,
|
||||
GUI_SCENE_CHANNELS,
|
||||
GUI_SCENE_CHIPS,
|
||||
GUI_SCENE_OTHER,
|
||||
};
|
||||
|
||||
enum FurnaceGUIFileDialogs {
|
||||
|
@ -887,9 +889,10 @@ struct FurnaceGUIMacroDesc {
|
|||
ImVec4 color;
|
||||
unsigned int bitOffset;
|
||||
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),
|
||||
height(macroHeight),
|
||||
displayName(name),
|
||||
|
@ -900,7 +903,8 @@ struct FurnaceGUIMacroDesc {
|
|||
isBitfield(bitfield),
|
||||
blockMode(block),
|
||||
bit30(bit30Special),
|
||||
hoverFunc(hf) {
|
||||
hoverFunc(hf),
|
||||
hoverFuncUser(hfu) {
|
||||
// MSVC -> hell
|
||||
this->min=macroMin;
|
||||
this->max=macroMax;
|
||||
|
@ -994,6 +998,7 @@ class FurnaceGUI {
|
|||
|
||||
std::vector<DivSystem> sysSearchResults;
|
||||
std::vector<FurnaceGUISysDef> newSongSearchResults;
|
||||
std::deque<String> recentFile;
|
||||
|
||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
|
||||
bool portrait, mobileMenuOpen;
|
||||
|
@ -1165,6 +1170,7 @@ class FurnaceGUI {
|
|||
int channelStyle;
|
||||
int channelVolStyle;
|
||||
int channelFeedbackStyle;
|
||||
int maxRecentFile;
|
||||
unsigned int maxUndoSteps;
|
||||
String mainFontPath;
|
||||
String patFontPath;
|
||||
|
@ -1285,6 +1291,7 @@ class FurnaceGUI {
|
|||
channelStyle(0),
|
||||
channelVolStyle(0),
|
||||
channelFeedbackStyle(1),
|
||||
maxRecentFile(10),
|
||||
maxUndoSteps(100),
|
||||
mainFontPath(""),
|
||||
patFontPath(""),
|
||||
|
@ -1318,7 +1325,7 @@ class FurnaceGUI {
|
|||
|
||||
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
|
||||
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;
|
||||
float peak[2];
|
||||
float patChanX[DIV_MAX_CHANS+1];
|
||||
|
@ -1439,6 +1446,7 @@ class FurnaceGUI {
|
|||
FurnaceGUIMacroDesc lastMacroDesc;
|
||||
int macroOffX, macroOffY;
|
||||
float macroScaleX, macroScaleY;
|
||||
int macroRandMin, macroRandMax;
|
||||
|
||||
ImVec2 macroLoopDragStart;
|
||||
ImVec2 macroLoopDragAreaSize;
|
||||
|
@ -1462,7 +1470,7 @@ class FurnaceGUI {
|
|||
int renderTimeBegin, renderTimeEnd, renderTimeDelta;
|
||||
int eventTimeBegin, eventTimeEnd, eventTimeDelta;
|
||||
|
||||
int chanToMove, sysToMove, sysToDelete;
|
||||
int chanToMove, sysToMove, sysToDelete, opToMove;
|
||||
|
||||
ImVec2 patWindowPos, patWindowSize;
|
||||
|
||||
|
@ -1567,6 +1575,8 @@ class FurnaceGUI {
|
|||
float waveGenTL[4];
|
||||
int waveGenMult[4];
|
||||
int waveGenFB[4];
|
||||
int waveGenScaleX, waveGenScaleY, waveGenOffsetX, waveGenOffsetY, waveGenSmooth;
|
||||
float waveGenAmplify;
|
||||
bool waveGenFMCon1[4];
|
||||
bool waveGenFMCon2[3];
|
||||
bool waveGenFMCon3[2];
|
||||
|
@ -1605,16 +1615,19 @@ class FurnaceGUI {
|
|||
|
||||
void toggleMobileUI(bool enable, bool force=false);
|
||||
|
||||
void pushToggleColors(bool status);
|
||||
void popToggleColors();
|
||||
|
||||
void drawMobileControls();
|
||||
void drawEditControls();
|
||||
void drawSongInfo();
|
||||
void drawOrders();
|
||||
void drawPattern();
|
||||
void drawInsList();
|
||||
void drawInsList(bool asChild=false);
|
||||
void drawInsEdit();
|
||||
void drawWaveList();
|
||||
void drawWaveList(bool asChild=false);
|
||||
void drawWaveEdit();
|
||||
void drawSampleList();
|
||||
void drawSampleList(bool asChild=false);
|
||||
void drawSampleEdit();
|
||||
void drawMixer();
|
||||
void drawOsc();
|
||||
|
@ -1713,9 +1726,14 @@ class FurnaceGUI {
|
|||
void keyDown(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);
|
||||
int save(String path, int dmfVersion);
|
||||
int load(String path);
|
||||
void pushRecentFile(String path);
|
||||
void exportAudio(String path, DivAudioExportModes mode);
|
||||
|
||||
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 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);
|
||||
void decodeKeyMap(std::map<int,int>& map, String source);
|
||||
|
|
|
@ -316,27 +316,31 @@ const char* macroRelativeMode="Relative";
|
|||
const char* macroQSoundMode="QSound";
|
||||
const char* macroDummyMode="Bug";
|
||||
|
||||
String macroHoverNote(int id, float val) {
|
||||
String macroHoverNote(int id, float val, void* u) {
|
||||
int* macroVal=(int*)u;
|
||||
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);
|
||||
}
|
||||
|
||||
String macroHoverLoop(int id, float val) {
|
||||
String macroHoverLoop(int id, float val, void* u) {
|
||||
if (val>1) return "Release";
|
||||
if (val>0) return "Loop";
|
||||
return "";
|
||||
}
|
||||
|
||||
String macroHoverBit30(int id, float val) {
|
||||
String macroHoverBit30(int id, float val, void* u) {
|
||||
if (val>0) return "Fixed";
|
||||
return "Relative";
|
||||
}
|
||||
|
||||
String macroHoverES5506FilterMode(int id, float val) {
|
||||
String macroHoverES5506FilterMode(int id, float val, void* u) {
|
||||
String mode="???";
|
||||
switch (((int)val)&3) {
|
||||
case 0:
|
||||
|
@ -357,7 +361,7 @@ String macroHoverES5506FilterMode(int id, float val) {
|
|||
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) {
|
||||
case 0:
|
||||
return "Saw";
|
||||
|
@ -1355,7 +1359,7 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
|||
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);
|
||||
} 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))) {
|
||||
macroDragStart=ImGui::GetItemRectMin();
|
||||
|
@ -1553,6 +1557,49 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
|||
#define CENTER_TEXT_20(text) \
|
||||
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() {
|
||||
if (nextWindow==GUI_WINDOW_INS_EDIT) {
|
||||
insEditOpen=true;
|
||||
|
@ -1560,7 +1607,14 @@ void FurnaceGUI::drawInsEdit() {
|
|||
nextWindow=GUI_WINDOW_NOTHING;
|
||||
}
|
||||
if (!insEditOpen) 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);
|
||||
} 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 (curIns<0 || curIns>=(int)e->song.ins.size()) {
|
||||
ImGui::Text("no instrument selected");
|
||||
|
@ -1697,6 +1751,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
int opCount=4;
|
||||
if (ins->type==DIV_INS_OPLL) opCount=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::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) {
|
||||
|
@ -2051,17 +2106,31 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y;
|
||||
|
||||
ImGui::PushID(fmt::sprintf("op%d",i).c_str());
|
||||
String opNameLabel;
|
||||
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) {
|
||||
if (i==1) {
|
||||
ImGui::Text("Kick");
|
||||
opNameLabel="Kick";
|
||||
} else {
|
||||
ImGui::Text("Env");
|
||||
opNameLabel="Env";
|
||||
}
|
||||
} 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;
|
||||
if (ins->type==DIV_INS_OPLL) {
|
||||
|
@ -2345,8 +2414,20 @@ void FurnaceGUI::drawInsEdit() {
|
|||
} else {
|
||||
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));
|
||||
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 waveWidth=140.0*dpiScale;
|
||||
|
@ -2778,16 +2859,29 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(dpiScale,dpiScale));
|
||||
String opNameLabel;
|
||||
OP_DRAG_POINT;
|
||||
ImGui::SameLine();
|
||||
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) {
|
||||
if (i==1) {
|
||||
ImGui::Text("Envelope 2 (kick only)");
|
||||
opNameLabel="Envelope 2 (kick only)";
|
||||
} else {
|
||||
ImGui::Text("Envelope");
|
||||
opNameLabel="Envelope";
|
||||
}
|
||||
} 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();
|
||||
|
@ -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("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));
|
||||
}
|
||||
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));
|
||||
} 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);
|
||||
ImGui::EndTabItem();
|
||||
|
@ -3352,29 +3453,29 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) {
|
||||
ImGui::Text("Waveform");
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn));
|
||||
pushToggleColors(ins->c64.triOn);
|
||||
if (ImGui::Button("tri")) { PARAMETER
|
||||
ins->c64.triOn=!ins->c64.triOn;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn));
|
||||
pushToggleColors(ins->c64.sawOn);
|
||||
if (ImGui::Button("saw")) { PARAMETER
|
||||
ins->c64.sawOn=!ins->c64.sawOn;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn));
|
||||
pushToggleColors(ins->c64.pulseOn);
|
||||
if (ImGui::Button("pulse")) { PARAMETER
|
||||
ins->c64.pulseOn=!ins->c64.pulseOn;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn));
|
||||
pushToggleColors(ins->c64.noiseOn);
|
||||
if (ImGui::Button("noise")) { PARAMETER
|
||||
ins->c64.noiseOn=!ins->c64.noiseOn;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
|
||||
|
||||
|
@ -3436,29 +3537,29 @@ void FurnaceGUI::drawInsEdit() {
|
|||
|
||||
ImGui::Text("Filter Mode");
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp));
|
||||
pushToggleColors(ins->c64.lp);
|
||||
if (ImGui::Button("low")) { PARAMETER
|
||||
ins->c64.lp=!ins->c64.lp;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp));
|
||||
pushToggleColors(ins->c64.bp);
|
||||
if (ImGui::Button("band")) { PARAMETER
|
||||
ins->c64.bp=!ins->c64.bp;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp));
|
||||
pushToggleColors(ins->c64.hp);
|
||||
if (ImGui::Button("high")) { PARAMETER
|
||||
ins->c64.hp=!ins->c64.hp;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off));
|
||||
pushToggleColors(ins->c64.ch3off);
|
||||
if (ImGui::Button("ch3off")) { PARAMETER
|
||||
ins->c64.ch3off=!ins->c64.ch3off;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
|
||||
P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff));
|
||||
P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs));
|
||||
|
@ -3606,7 +3707,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
modTable[i]=ins->fds.modTable[i];
|
||||
}
|
||||
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)) {
|
||||
macroDragStart=ImGui::GetItemRectMin();
|
||||
macroDragAreaSize=modTableSize;
|
||||
|
@ -4225,7 +4326,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (volMax>0) {
|
||||
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 (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));
|
||||
|
@ -4455,6 +4556,28 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -374,7 +374,7 @@ void FurnaceGUI::drawPattern() {
|
|||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f));
|
||||
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.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::SetNextWindowSize(patWindowSize);
|
||||
}
|
||||
|
|
|
@ -54,29 +54,26 @@ void FurnaceGUI::drawPiano() {
|
|||
if (!pianoOpen) return;
|
||||
if (mobileUI) {
|
||||
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)) {
|
||||
bool oldPianoKeyPressed[180];
|
||||
memcpy(oldPianoKeyPressed,pianoKeyPressed,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& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit;
|
||||
bool view=(pianoView==2)?(!e->isPlaying()):pianoView;
|
||||
if (pianoOptions) {
|
||||
if (pianoOptions && (!mobileUI || !portrait)) {
|
||||
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::TableNextRow();
|
||||
if (pianoOptions) {
|
||||
ImGui::TableNextColumn();
|
||||
float optionSizeY=ImGui::GetContentRegionAvail().y*0.5-ImGui::GetStyle().ItemSpacing.y;
|
||||
ImVec2 optionSize=ImVec2(1.2f*optionSizeY,optionSizeY);
|
||||
float optionSizeY=ImGui::GetContentRegionAvail().y*((mobileUI && portrait)?0.3:0.5)-ImGui::GetStyle().ItemSpacing.y;
|
||||
ImVec2 optionSize=ImVec2((mobileUI && portrait)?((ImGui::GetContentRegionAvail().x-ImGui::GetStyle().ItemSpacing.x*5.0f)/6.0f):(1.2f*optionSizeY),optionSizeY);
|
||||
if (pianoOptionsSet) {
|
||||
if (ImGui::Button("OFF##PianoNOff",optionSize)) {
|
||||
if (edit) noteInput(0,100);
|
||||
|
@ -126,6 +123,10 @@ void FurnaceGUI::drawPiano() {
|
|||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (mobileUI && portrait) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (pianoOptionsSet) {
|
||||
if (ImGui::Button("REL##PianoNMRel",optionSize)) {
|
||||
if (edit) noteInput(0,102);
|
||||
|
@ -152,6 +153,10 @@ void FurnaceGUI::drawPiano() {
|
|||
}
|
||||
}
|
||||
|
||||
if (mobileUI && portrait) {
|
||||
ImGui::TableNextRow();
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (pianoInputPadMode==1 && cursor.xFine>0) {
|
||||
ImVec2 buttonSize=ImGui::GetContentRegionAvail();
|
||||
|
@ -199,8 +204,7 @@ void FurnaceGUI::drawPiano() {
|
|||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
}
|
||||
} else {
|
||||
ImGuiWindow* window=ImGui::GetCurrentWindow();
|
||||
ImVec2 size=ImGui::GetContentRegionAvail();
|
||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||
|
@ -388,6 +392,7 @@ void FurnaceGUI::drawPiano() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
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 v1 = values_getter(data, (v_idx + 1) % values_count);
|
||||
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()) {
|
||||
ImGui::SetTooltip("%s",hoverText.c_str());
|
||||
}
|
||||
|
@ -459,8 +459,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
|
|||
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);
|
||||
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 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;
|
||||
}
|
||||
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 (curSample<0 || curSample>=(int)e->song.sample.size()) {
|
||||
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::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode));
|
||||
pushToggleColors(!sampleDragMode);
|
||||
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
|
||||
sampleDragMode=false;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Edit mode: Select");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode));
|
||||
pushToggleColors(sampleDragMode);
|
||||
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
|
||||
sampleDragMode=true;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
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::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode));
|
||||
pushToggleColors(!sampleDragMode);
|
||||
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
|
||||
sampleDragMode=false;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Edit mode: Select");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode));
|
||||
pushToggleColors(sampleDragMode);
|
||||
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
|
||||
sampleDragMode=true;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
popToggleColors();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Edit mode: Draw");
|
||||
}
|
||||
|
|
|
@ -1130,6 +1130,13 @@ void FurnaceGUI::drawSettings() {
|
|||
|
||||
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::InputTextWithHint("Note off (3-char)","OFF",&settings.noteOffLabel);
|
||||
ImGui::InputTextWithHint("Note release (3-char)","===",&settings.noteRelLabel);
|
||||
|
@ -2273,6 +2280,7 @@ void FurnaceGUI::syncSettings() {
|
|||
settings.channelStyle=e->getConfInt("channelStyle",0);
|
||||
settings.channelVolStyle=e->getConfInt("channelVolStyle",0);
|
||||
settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1);
|
||||
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
|
||||
|
||||
clampSetting(settings.mainFontSize,2,96);
|
||||
clampSetting(settings.patFontSize,2,96);
|
||||
|
@ -2371,6 +2379,7 @@ void FurnaceGUI::syncSettings() {
|
|||
clampSetting(settings.channelStyle,0,5);
|
||||
clampSetting(settings.channelVolStyle,0,3);
|
||||
clampSetting(settings.channelFeedbackStyle,0,3);
|
||||
clampSetting(settings.maxRecentFile,0,30);
|
||||
|
||||
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
|
||||
if (settings.initialSys.size()<4) {
|
||||
|
@ -2525,6 +2534,7 @@ void FurnaceGUI::commitSettings() {
|
|||
e->setConf("channelStyle",settings.channelStyle);
|
||||
e->setConf("channelVolStyle",settings.channelVolStyle);
|
||||
e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle);
|
||||
e->setConf("maxRecentFile",settings.maxRecentFile);
|
||||
|
||||
// colors
|
||||
for (int i=0; i<GUI_COLOR_MAX; i++) {
|
||||
|
@ -2546,6 +2556,10 @@ void FurnaceGUI::commitSettings() {
|
|||
|
||||
e->saveConf();
|
||||
|
||||
while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) {
|
||||
recentFile.pop_back();
|
||||
}
|
||||
|
||||
if (sampleROMsChanged) {
|
||||
if (e->loadSampleROMs()) {
|
||||
showError(e->getLastError());
|
||||
|
|
|
@ -402,6 +402,24 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
|||
}
|
||||
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: {
|
||||
ImGui::Text("Speaker type:");
|
||||
if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) {
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
#include "gui.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) {
|
||||
SafeReader reader(data,len);
|
||||
|
||||
|
@ -137,7 +148,7 @@ bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) {
|
|||
op.sl=15-reader.readC();
|
||||
reader.readC(); // LS - ignore
|
||||
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();
|
||||
logV("OP%d freq: %d",i,freq);
|
||||
op.mult=freq>>2;
|
||||
|
|
|
@ -32,7 +32,7 @@ const char* waveGenBaseShapes[4]={
|
|||
"Pulse"
|
||||
};
|
||||
|
||||
const float multFactors[16]={
|
||||
const float multFactors[17]={
|
||||
M_PI,
|
||||
2*M_PI,
|
||||
4*M_PI,
|
||||
|
@ -49,6 +49,7 @@ const float multFactors[16]={
|
|||
26*M_PI,
|
||||
28*M_PI,
|
||||
30*M_PI,
|
||||
32*M_PI,
|
||||
};
|
||||
|
||||
void FurnaceGUI::doGenerateWave() {
|
||||
|
@ -164,7 +165,14 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
}
|
||||
if (!waveEditOpen) return;
|
||||
float wavePreview[257];
|
||||
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 (curWave<0 || curWave>=(int)e->song.wave.size()) {
|
||||
ImGui::Text("no wavetable selected");
|
||||
|
@ -248,6 +256,9 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
for (int i=0; i<wave->len; i++) {
|
||||
if (wave->data[i]>wave->max) wave->data[i]=wave->max;
|
||||
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];
|
||||
|
||||
|
@ -262,9 +273,9 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here
|
||||
contentRegion.y-=ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().WindowPadding.y;
|
||||
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 {
|
||||
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)) {
|
||||
waveDragStart=ImGui::GetItemRectMin();
|
||||
|
@ -405,7 +416,7 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::PushID(i);
|
||||
if (CWSliderInt("##WGMULT",&waveGenMult[i],0,15)) {
|
||||
if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) {
|
||||
doGenerateWave();
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
@ -496,7 +507,224 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
}
|
||||
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::EndTabBar();
|
||||
|
@ -513,12 +741,33 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
waveHex=true;
|
||||
}
|
||||
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
|
||||
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()) {
|
||||
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"
|
||||
|
||||
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 "--- STEP 1: render test files"
|
||||
mkdir -p "test/result/$testDir" || exit 1
|
||||
|
@ -21,11 +29,19 @@ if [ -z $lastTest ]; then
|
|||
echo "skipping since this apparently is your first run."
|
||||
else
|
||||
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
|
||||
echo "--- STEP 3: make delta images"
|
||||
echo "--- STEP 3: check deltas"
|
||||
if [ -z $lastTest ]; then
|
||||
echo "skipping since this apparently is your first run."
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue