Merge branch 'master' of https://github.com/tildearrow/furnace into vgsound_emu_update

This commit is contained in:
cam900 2022-09-16 23:49:11 +09:00
commit 52476ec1a6
81 changed files with 2090 additions and 1381 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ linuxbuild/
test/songs/
test/delta/
test/result/
test/assert_delta
android/.gradle/
android/app/build/
android/app/.cxx/

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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"
- "paste with instrument"
- FM operator muting
- FM operator swap
- bug fixes

View File

@ -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"

View File

@ -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" />
<data android:mimeType="*/*" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:host="*"/>
<data android:mimeType="*/*"/>
<data android:pathPattern=".*\\.fur"/>
</intent-filter>
-->
</activity>
</application>

Binary file not shown.

Binary file not shown.

BIN
demos/ChaosTune.fur Normal file

Binary file not shown.

BIN
demos/FEDMS.fur Normal file

Binary file not shown.

BIN
demos/GEN Equinox Intro.fur Normal file

Binary file not shown.

BIN
demos/her11_veraedit.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/iji_tor.fur Normal file

Binary file not shown.

BIN
demos/lunacommdemo.fur Normal file

Binary file not shown.

2
extern/Nuked-OPN2 vendored

@ -1 +1 @@
Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7
Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250

6
extern/opm/opm.c vendored
View File

@ -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;

View File

@ -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,

View 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

View File

@ -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;
}

View File

@ -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 (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
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;
nextRow=0;
if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) {
nextRow=0;
}
changingOrder=true;
}
}
}
}
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
if (nextOrder!=-1) {
if (nextOrder<=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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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,11 +175,21 @@ 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) {
ins->type=DIV_INS_STD;
if (ins->type==DIV_INS_FM) {
if (sys==9) {
ins->type=DIV_INS_AY;
} else {
ins->type=DIV_INS_STD;
}
}
} else {
ins->type=DIV_INS_FM;
if (sys==3 || sys==6) {
ins->type=DIV_INS_OPLL;
} else if (sys==1) {
ins->type=DIV_INS_OPL;
} else {
ins->type=DIV_INS_FM;
}
}
} else {
ins->type=DIV_INS_FM;
@ -232,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->fm.op[j].dvb=reader.readC();
ins->fm.op[j].dam=reader.readC();
} else {
ins->fm.op[j].rs=reader.readC();
ins->fm.op[j].dt=reader.readC();
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
ins->fm.op[j].dt&=15;
ins->fm.op[j].d2r=reader.readC();
ins->fm.op[j].ssgEnv=reader.readC();
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();
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
ins->fm.op[j].dt&=15;
ins->fm.op[j].d2r=reader.readC();
ins->fm.op[j].ssgEnv=reader.readC();
}
}
}
} else { // STD
@ -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++) {

View File

@ -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

View File

@ -67,7 +67,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
w.addrOrVal=true;
}
}
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
@ -77,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
}
if (o[0]<-32768) o[0]=-32768;
if (o[0]>32767) o[0]=32767;
if (o[1]<-32768) o[1]=-32768;
if (o[1]>32767) o[1]=32767;
bufL[h]=o[0];
bufR[h]=o[1];
}
@ -106,7 +106,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
delay=1;
}
}
fm_ymfm->generate(&out_ymfm);
for (int i=0; i<8; i++) {
@ -120,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
os[1]=out_ymfm.data[1];
if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767;
bufL[h]=os[0];
bufR[h]=os[1];
}
@ -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);

View File

@ -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];

View File

@ -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);

View File

@ -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),

View File

@ -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;
writeMask|=1<<(4+i);
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));
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (i==2) {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
} else {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;

View File

@ -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) {}
};

View File

@ -249,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
if (os[1]>32767) os[1]=32767;
bufL[h]=os[0];
bufR[h]=os[1];
if (oplType==3 || oplType==759) {
bufR[h]=os[1];
}
}
}
@ -1520,7 +1522,7 @@ void DivPlatformOPL::reset() {
}
bool DivPlatformOPL::isStereo() {
return true;
return (oplType==3 || oplType==759);
}
bool DivPlatformOPL::keyOffAffectsArp(int ch) {

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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
};

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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++) {

View File

@ -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) {}

View File

@ -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;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View File

@ -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];

View File

@ -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++) {

View File

@ -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),

View File

@ -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;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View File

@ -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];

View File

@ -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++) {

View File

@ -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];

View File

@ -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++) {

View File

@ -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];

View File

@ -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;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View File

@ -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];

View File

@ -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;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}

View File

@ -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];

View File

@ -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,15 +350,31 @@ 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;
changePos=0;
if (song.jumpTreatment==1 || song.jumpTreatment==2) {
changePos=0;
}
}
break;
case 0x0d: // next order
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
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
@ -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);

View File

@ -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),

View File

@ -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",
"",

View File

@ -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.");

View File

@ -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 (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST;
ImGui::End();
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 (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST;
ImGui::End();
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,8 +574,12 @@ void FurnaceGUI::drawSampleList() {
}
ImGui::Unindent();
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST;
ImGui::End();
if (asChild) {
ImGui::EndChild();
} else {
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST;
ImGui::End();
}
}
void FurnaceGUI::actualWaveList() {

View File

@ -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,79 +104,157 @@ void FurnaceGUI::drawMobileControls() {
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f));
ImGui::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("New")) {
mobileMenuOpen=false;
//doAction(GUI_ACTION_NEW);
if (modified) {
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
} else {
displayNew=true;
if (ImGui::Button("Pattern",buttonSize)) {
mobScene=GUI_SCENE_PATTERN;
}
}
ImGui::SameLine();
if (ImGui::Button("Open")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE);
}
ImGui::SameLine();
if (ImGui::Button("Save as...")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::Button("1.1+ .dmf");
ImGui::SameLine();
ImGui::Button("Legacy .dmf");
ImGui::SameLine();
ImGui::Button("Export Audio");
ImGui::SameLine();
ImGui::Button("Export VGM");
ImGui::Button("CmdStream");
ImGui::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::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 (ImGui::Button("Osc")) {
oscOpen=!oscOpen;
}
ImGui::SameLine();
if (ImGui::Button("ChanOsc")) {
chanOscOpen=!chanOscOpen;
}
ImGui::SameLine();
if (ImGui::Button("RegView")) {
regViewOpen=!regViewOpen;
}
ImGui::SameLine();
if (ImGui::Button("Stats")) {
statsOpen=!statsOpen;
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);
if (modified) {
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
} else {
displayNew=true;
}
}
ImGui::SameLine();
if (ImGui::Button("Open")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE);
}
ImGui::SameLine();
if (ImGui::Button("Save as...")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::Button("1.1+ .dmf");
ImGui::SameLine();
ImGui::Button("Legacy .dmf");
ImGui::SameLine();
ImGui::Button("Export Audio");
ImGui::SameLine();
ImGui::Button("Export VGM");
ImGui::Button("CmdStream");
ImGui::Separator();
ImGui::Text("Song info here...");
break;
}
case GUI_SCENE_CHANNELS:
ImGui::Text("Channels here...");
break;
case GUI_SCENE_CHIPS:
ImGui::Text("Chips here...");
break;
case GUI_SCENE_OTHER: {
if (ImGui::Button("Osc")) {
oscOpen=!oscOpen;
}
ImGui::SameLine();
if (ImGui::Button("ChanOsc")) {
chanOscOpen=!chanOscOpen;
}
ImGui::SameLine();
if (ImGui::Button("RegView")) {
regViewOpen=!regViewOpen;
}
ImGui::SameLine();
if (ImGui::Button("Stats")) {
statsOpen=!statsOpen;
}
ImGui::Separator();
ImGui::Button("Panic");
ImGui::SameLine();
if (ImGui::Button("Settings")) {
mobileMenuOpen=false;
}
ImGui::SameLine();
if (ImGui::Button("About")) {
mobileMenuOpen=false;
mobileMenuPos=0.0f;
aboutOpen=true;
}
if (ImGui::Button("Switch to Desktop Mode")) {
toggleMobileUI(!mobileUI);
}
break;
}
}
}
}
ImGui::End();
@ -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();

View File

@ -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;

View File

@ -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
@ -2640,7 +2772,7 @@ bool FurnaceGUI::loop() {
if (settings.powerSave) SDL_WaitEventTimeout(NULL,500);
}
eventTimeBegin=SDL_GetPerformanceCounter();
bool updateWindow = false;
bool updateWindow=false;
while (SDL_PollEvent(&ev)) {
WAKE_UP;
ImGui_ImplSDL2_ProcessEvent(&ev);
@ -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();
drawPattern();
drawPiano();
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;

View File

@ -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);

View File

@ -316,27 +316,31 @@ const char* macroRelativeMode="Relative";
const char* macroQSoundMode="QSound";
const char* macroDummyMode="Bug";
String macroHoverNote(int id, float val) {
if (val<-60 || val>=120) return "???";
return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]);
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;
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (mobileUI) {
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
ImGui::SetNextWindowPos(patWindowPos);
ImGui::SetNextWindowSize(patWindowSize);
} else {
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
}
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
if (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);
ImGui::TextUnformatted(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",&macroRandMin,1,10);
ImGui::InputInt("Max",&macroRandMax,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();
}

View File

@ -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);
}

View File

@ -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,192 +204,192 @@ void FurnaceGUI::drawPiano() {
ImGui::EndTable();
}
ImGui::TableNextColumn();
}
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 size=ImGui::GetContentRegionAvail();
ImDrawList* dl=ImGui::GetWindowDrawList();
} else {
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 size=ImGui::GetContentRegionAvail();
ImDrawList* dl=ImGui::GetWindowDrawList();
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
minArea.x+size.x,
minArea.y+size.y
);
ImRect rect=ImRect(minArea,maxArea);
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
minArea.x+size.x,
minArea.y+size.y
);
ImRect rect=ImRect(minArea,maxArea);
// render piano
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"));
if (view) {
int notes=oct*12;
// evaluate input
for (TouchPoint& i: activePoints) {
if (rect.Contains(ImVec2(i.x,i.y))) {
int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off;
if (note<0) continue;
if (note>=180) continue;
pianoKeyPressed[note]=true;
}
}
for (int i=0; i<notes; i++) {
int note=i+12*off;
if (note<0) continue;
if (note>=180) continue;
float pkh=pianoKeyHit[note];
ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
if (pianoKeyPressed[note]) {
color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
} else {
ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
color.x+=(colorHit.x-color.x)*pkh;
color.y+=(colorHit.y-color.y)*pkh;
color.z+=(colorHit.z-color.z)*pkh;
color.w+=(colorHit.w-color.w)*pkh;
}
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f));
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f));
p1.x-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
if ((i%12)==0) {
String label=fmt::sprintf("%d",(note-60)/12);
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
dl->AddText(pText,0xff404040,label.c_str());
}
}
} else {
int bottomNotes=7*oct;
// evaluate input
for (TouchPoint& i: activePoints) {
if (rect.Contains(ImVec2(i.x,i.y))) {
// top
int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct;
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f));
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f));
bool foundTopKey=false;
for (int j=0; j<5; j++) {
int note=topKeyNotes[j]+12*(o+off);
// render piano
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"));
if (view) {
int notes=oct*12;
// evaluate input
for (TouchPoint& i: activePoints) {
if (rect.Contains(ImVec2(i.x,i.y))) {
int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off;
if (note<0) continue;
if (note>=180) continue;
ImRect keyRect=ImRect(
ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)),
ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f))
);
if (keyRect.Contains(ImVec2(i.x,i.y))) {
pianoKeyPressed[note]=true;
foundTopKey=true;
break;
}
pianoKeyPressed[note]=true;
}
if (foundTopKey) continue;
// bottom
int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes;
int note=bottomKeyNotes[n%7]+12*((n/7)+off);
if (note<0) continue;
if (note>=180) continue;
pianoKeyPressed[note]=true;
}
}
for (int i=0; i<bottomNotes; i++) {
int note=bottomKeyNotes[i%7]+12*((i/7)+off);
if (note<0) continue;
if (note>=180) continue;
float pkh=pianoKeyHit[note];
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
if (pianoKeyPressed[note]) {
color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
} else {
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
color.x+=(colorHit.x-color.x)*pkh;
color.y+=(colorHit.y-color.y)*pkh;
color.z+=(colorHit.z-color.z)*pkh;
color.w+=(colorHit.w-color.w)*pkh;
}
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f));
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f));
p1.x-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
if ((i%7)==0) {
String label=fmt::sprintf("%d",(note-60)/12);
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
dl->AddText(pText,0xff404040,label.c_str());
}
}
for (int i=0; i<oct; i++) {
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/oct,0.0f));
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/oct,1.0f));
for (int j=0; j<5; j++) {
int note=topKeyNotes[j]+12*(i+off);
for (int i=0; i<notes; i++) {
int note=i+12*off;
if (note<0) continue;
if (note>=180) continue;
float pkh=pianoKeyHit[note];
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP];
ImVec4 color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
if (pianoKeyPressed[note]) {
color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE];
color=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
} else {
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT];
ImVec4 colorHit=isTopKey[i%12]?uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT]:uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
color.x+=(colorHit.x-color.x)*pkh;
color.y+=(colorHit.y-color.y)*pkh;
color.z+=(colorHit.z-color.z)*pkh;
color.w+=(colorHit.w-color.w)*pkh;
}
ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f));
ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f));
dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND]));
p0.x+=dpiScale;
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/notes,0.0f));
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/notes,1.0f));
p1.x-=dpiScale;
p1.y-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
if ((i%12)==0) {
String label=fmt::sprintf("%d",(note-60)/12);
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
dl->AddText(pText,0xff404040,label.c_str());
}
}
} else {
int bottomNotes=7*oct;
// evaluate input
for (TouchPoint& i: activePoints) {
if (rect.Contains(ImVec2(i.x,i.y))) {
// top
int o=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*oct;
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)o/oct,0.0f));
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(o+1)/oct,1.0f));
bool foundTopKey=false;
for (int j=0; j<5; j++) {
int note=topKeyNotes[j]+12*(o+off);
if (note<0) continue;
if (note>=180) continue;
ImRect keyRect=ImRect(
ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f)),
ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f))
);
if (keyRect.Contains(ImVec2(i.x,i.y))) {
pianoKeyPressed[note]=true;
foundTopKey=true;
break;
}
}
if (foundTopKey) continue;
// bottom
int n=((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*bottomNotes;
int note=bottomKeyNotes[n%7]+12*((n/7)+off);
if (note<0) continue;
if (note>=180) continue;
pianoKeyPressed[note]=true;
}
}
for (int i=0; i<bottomNotes; i++) {
int note=bottomKeyNotes[i%7]+12*((i/7)+off);
if (note<0) continue;
if (note>=180) continue;
float pkh=pianoKeyHit[note];
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM];
if (pianoKeyPressed[note]) {
color=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE];
} else {
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_BOTTOM_HIT];
color.x+=(colorHit.x-color.x)*pkh;
color.y+=(colorHit.y-color.y)*pkh;
color.z+=(colorHit.z-color.z)*pkh;
color.w+=(colorHit.w-color.w)*pkh;
}
ImVec2 p0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/bottomNotes,0.0f));
ImVec2 p1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/bottomNotes,1.0f));
p1.x-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
if ((i%7)==0) {
String label=fmt::sprintf("%d",(note-60)/12);
ImVec2 pText=ImLerp(p0,p1,ImVec2(0.5f,1.0f));
ImVec2 labelSize=ImGui::CalcTextSize(label.c_str());
pText.x-=labelSize.x*0.5f;
pText.y-=labelSize.y+ImGui::GetStyle().ItemSpacing.y;
dl->AddText(pText,0xff404040,label.c_str());
}
}
for (int i=0; i<oct; i++) {
ImVec2 op0=ImLerp(rect.Min,rect.Max,ImVec2((float)i/oct,0.0f));
ImVec2 op1=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/oct,1.0f));
for (int j=0; j<5; j++) {
int note=topKeyNotes[j]+12*(i+off);
if (note<0) continue;
if (note>=180) continue;
float pkh=pianoKeyHit[note];
ImVec4 color=uiColors[GUI_COLOR_PIANO_KEY_TOP];
if (pianoKeyPressed[note]) {
color=uiColors[GUI_COLOR_PIANO_KEY_TOP_ACTIVE];
} else {
ImVec4 colorHit=uiColors[GUI_COLOR_PIANO_KEY_TOP_HIT];
color.x+=(colorHit.x-color.x)*pkh;
color.y+=(colorHit.y-color.y)*pkh;
color.z+=(colorHit.z-color.z)*pkh;
color.w+=(colorHit.w-color.w)*pkh;
}
ImVec2 p0=ImLerp(op0,op1,ImVec2(topKeyStarts[j]-0.05f,0.0f));
ImVec2 p1=ImLerp(op0,op1,ImVec2(topKeyStarts[j]+0.05f,0.64f));
dl->AddRectFilled(p0,p1,ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND]));
p0.x+=dpiScale;
p1.x-=dpiScale;
p1.y-=dpiScale;
dl->AddRectFilled(p0,p1,ImGui::ColorConvertFloat4ToU32(color));
}
}
}
const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12;
for (int i=0; i<180; i++) {
pianoKeyHit[i]-=reduction;
if (pianoKeyHit[i]<0) pianoKeyHit[i]=0;
}
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
pianoOptions=!pianoOptions;
}
// first check released keys
for (int i=0; i<180; i++) {
int note=i-60;
if (!pianoKeyPressed[i]) {
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
e->synchronized([this,note]() {
e->autoNoteOff(-1,note);
});
}
}
}
const float reduction=ImGui::GetIO().DeltaTime*60.0f*0.12;
// then pressed ones
for (int i=0; i<180; i++) {
pianoKeyHit[i]-=reduction;
if (pianoKeyHit[i]<0) pianoKeyHit[i]=0;
}
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
pianoOptions=!pianoOptions;
}
// first check released keys
for (int i=0; i<180; i++) {
int note=i-60;
if (!pianoKeyPressed[i]) {
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
e->synchronized([this,note]() {
e->autoNoteOff(-1,note);
});
}
}
}
// then pressed ones
for (int i=0; i<180; i++) {
int note=i-60;
if (pianoKeyPressed[i]) {
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
e->synchronized([this,note]() {
e->autoNoteOn(-1,curIns,note);
});
if (edit) noteInput(note,0);
int note=i-60;
if (pianoKeyPressed[i]) {
if (pianoKeyPressed[i]!=oldPianoKeyPressed[i]) {
e->synchronized([this,note]() {
e->autoNoteOn(-1,curIns,note);
});
if (edit) noteInput(note,0);
}
}
}
}

View File

@ -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);
}

View File

@ -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));

View File

@ -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");
}

View File

@ -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) {
@ -2403,7 +2412,7 @@ void FurnaceGUI::syncSettings() {
}
void FurnaceGUI::commitSettings() {
bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") ||
bool sampleROMsChanged=settings.yrw801Path!=e->getConfString("yrw801Path","") ||
settings.tg100Path!=e->getConfString("tg100Path","") ||
settings.mu5Path!=e->getConfString("mu5Path","");
@ -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());

View File

@ -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)) {

View File

@ -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;

View File

@ -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];
ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (mobileUI) {
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
ImGui::SetNextWindowPos(patWindowPos);
ImGui::SetNextWindowSize(patWindowSize);
} else {
ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
}
if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
if (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",&amp,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);
}
}
}

49
test/assert_delta.c Normal file
View File

@ -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;
}

View File

@ -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 "OK"
else
echo "FAIL FAIL FAIL"
ffmpeg -loglevel quiet -i "test/delta/$testDir/$i" -lavfi showspectrumpic "test/delta/$testDir/$i.png"
fi
done
fi