diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e84fd40..18bcf72c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,6 +435,8 @@ src/engine/platform/sound/ymz280b.cpp src/engine/platform/sound/vsu.cpp +src/engine/platform/sound/t6w28/T6W28_Apu.cpp + src/engine/platform/sound/rf5c68.cpp src/engine/platform/sound/oki/msm5232.cpp @@ -506,6 +508,7 @@ src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp src/engine/platform/su.cpp src/engine/platform/swan.cpp +src/engine/platform/t6w28.cpp src/engine/platform/vb.cpp src/engine/platform/vera.cpp src/engine/platform/zxbeeper.cpp diff --git a/demos/FEDMS.fur b/demos/FEDMS.fur deleted file mode 100644 index 4c521dad..00000000 Binary files a/demos/FEDMS.fur and /dev/null differ diff --git a/demos/Fiorella YM2610B+YM2203.fur b/demos/Fiorella YM2610B+YM2203.fur new file mode 100644 index 00000000..492f93a9 Binary files /dev/null and b/demos/Fiorella YM2610B+YM2203.fur differ diff --git a/demos/Utter Determination YM2610B.fur b/demos/Utter Determination YM2610B.fur new file mode 100644 index 00000000..67467ba2 Binary files /dev/null and b/demos/Utter Determination YM2610B.fur differ diff --git a/demos/double-dragon-stage1.fur b/demos/double-dragon-stage1.fur new file mode 100644 index 00000000..8b029bc3 Binary files /dev/null and b/demos/double-dragon-stage1.fur differ diff --git a/papers/doc/7-systems/snes.md b/papers/doc/7-systems/snes.md index 5bb1ae5f..25a77126 100644 --- a/papers/doc/7-systems/snes.md +++ b/papers/doc/7-systems/snes.md @@ -1,18 +1,50 @@ # Super NES -The successor to NES to compete with Genesis. Now packing with superior graphics and sample-based audio. Also known as Super Famicom. +The successor to NES to compete with Genesis. Now packing superior graphics and sample-based audio. Also known as Super Famicom in Japan. Its audio subsystem, developed by Sony, features the DSP chip, SPC700 microcontroller and 64KB of dedicated SRAM used by both. This whole system itself is pretty much a separate computer that the main CPU needs to upload its program and samples to. -The DSP chip can +Furnace communicates with the DSP directly and provides a full 64KB of memory. This memory might be reduced excessively on ROM export to make up for playback engine and pattern data. As of version 0.6pre2, you can go to `window -> statistics` to see how much memory your samples are using. -Furnace communicates with the DSP directly and provide a full 64KB memory. This memory might be reduced excessively on ROM export to make up for playback engine and pattern data. +Some notable features of the DSP are: +- It has pitch modulation, meaning that you can use 2 channels to make a basic FM synth without eating up too much memory +- It has a built in noise generator, useful for hihats, cymbals, rides, sfx, among other things. +- It famously features per-channel echo, which unfortunately eats up a lot of memory but can be used to save channels in songs. +- It can loop samples, but the loop points have to be multiples of 16. +- It can invert the left and/or right channels, for surround sound. +- It features ADSR, similar to the Commodore 64, but its functionality is closer to the OPL(L|1|2|3)'s implementation of ADSR. +- It features an 8-tap FIR filter, which is basically a procedural low-pass filter that you can edit however you want. +- 7-bit volume, per-channel. +- Per-channel interpolation, which is basically a low-pass filter that gets affected by the pitch of the channel. + +Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) in order to create more 'animated' sounds, using less memory than regular samples. This, however, is not a hardware feature, and might be difficult to implement on real hardware. # effects Note: this chip has a signed left/right level. Which can be used for inverted (surround) stereo. A signed 8-bit value means 80 - FF = -128 - -1. Other values work normally. A value of -128 is not recommended as it could cause overflows. -- `10xx`: Set echo feedback level. This effect will apply to all channels. -- `11xx`: Set echo left level (signed 8-bit). This effect will apply to all channels. -- `12xx`: Set echo right level (signed 8-bit). This effect will apply to all channels. -- `13xx`: Set the length of the echo delay buffer. This will also affect the size of the sample RAM! +- `10xx`: Set waveform. +- `11xx`: Toggle noise generator mode. +- `12xx`: Toggle echo on this channel. +- `13xx`: Toggle pitch modulation. +- `14xy`: Toggle inverting the left or right channels. (x: left, y: right) +- `15xx`: Set envelope mode. (0: ADSR, 1: gain/direct, 2: decrement, 3: exponential, 4: increment, 5: bent) +- `16xx`: Set gain. (00 to 7F if direct, 00 to 1F otherwise) +- `18xx`: Enable echo buffer. +- `19xx`: Set echo delay. (0 to F) +- `1Axx`: Set left echo channel volume. +- `1Bxx`: Set right echo channel volume. +- `1Cxx`: Set echo feedback. +- `1Dxx`: Set noise generator frequency. (00 to 1F) +- `20xx`: Set attack (0 to F) +- `21xx`: Set decay (0 to 7) +- `22xx`: Set sustain (0 to 7) +- `23xx`: Set release (00 to 1F) +- `30xx`: Set echo filter coefficient 0 +- `31xx`: Set echo filter coefficient 1 +- `32xx`: Set echo filter coefficient 2 +- `33xx`: Set echo filter coefficient 3 +- `34xx`: Set echo filter coefficient 4 +- `35xx`: Set echo filter coefficient 5 +- `36xx`: Set echo filter coefficient 6 +- `37xx`: Set echo filter coefficient 7 diff --git a/papers/format.md b/papers/format.md index 67f30eb2..66ffc82d 100644 --- a/papers/format.md +++ b/papers/format.md @@ -505,6 +505,7 @@ size | description | - 41: YMZ280B | - 42: RF5C68 | - 43: MSM5232 + | - 44: T6W28 1 | reserved STR | instrument name --- | **FM instrument data** diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 65560a2b..b0d2b69a 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -32,6 +32,7 @@ #include "platform/nes.h" #include "platform/c64.h" #include "platform/arcade.h" +#include "platform/t6w28.h" #include "platform/tx81z.h" #include "platform/ym2203.h" #include "platform/ym2203ext.h" @@ -344,6 +345,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SWAN: dispatch=new DivPlatformSwan; break; + case DIV_SYSTEM_T6W28: + dispatch=new DivPlatformT6W28; + break; case DIV_SYSTEM_VBOY: dispatch=new DivPlatformVB; break; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 10b6f537..818717cc 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -73,6 +73,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_YMZ280B=41, DIV_INS_RF5C68=42, DIV_INS_MSM5232=43, + DIV_INS_T6W28=44, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/sound/t6w28/T6W28_Apu.cpp b/src/engine/platform/sound/t6w28/T6W28_Apu.cpp index 110bb5ae..82767909 100644 --- a/src/engine/platform/sound/t6w28/T6W28_Apu.cpp +++ b/src/engine/platform/sound/t6w28/T6W28_Apu.cpp @@ -1,9 +1,11 @@ // T6W28_Snd_Emu #include "T6W28_Apu.h" +#include +#include #undef require -#define require( expr ) assert( expr ) +#define require( expr ) if (! (expr) ) return; /* Copyright (C) 2003-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -21,6 +23,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ namespace MDFN_IEN_NGP { +void fakeBufOffset(int time, int delta, Fake_Buffer* buf) { + buf->curValue+=delta; +} + T6W28_Osc::T6W28_Osc() { outputs [0] = NULL; // always stays NULL @@ -41,7 +47,7 @@ void T6W28_Osc::reset() // T6W28_Square -blip_inline void T6W28_Square::reset() +void T6W28_Square::reset() { period = 0; phase = 0; @@ -55,13 +61,13 @@ void T6W28_Square::run( sms_time_t time, sms_time_t end_time ) // ignore 16kHz and higher if ( last_amp_left ) { - synth->offset( time, -last_amp_left, outputs[2] ); + fakeBufOffset( time, -last_amp_left, outputs[2] ); last_amp_left = 0; } if ( last_amp_right ) { - synth->offset( time, -last_amp_right, outputs[1] ); + fakeBufOffset( time, -last_amp_right, outputs[1] ); last_amp_right = 0; } @@ -90,21 +96,21 @@ void T6W28_Square::run( sms_time_t time, sms_time_t end_time ) if ( delta_left ) { last_amp_left = amp_left; - synth->offset( time, delta_left, outputs[2] ); + fakeBufOffset( time, delta_left, outputs[2] ); } if ( delta_right ) { last_amp_right = amp_right; - synth->offset( time, delta_right, outputs[1] ); + fakeBufOffset( time, delta_right, outputs[1] ); } } time += delay; if ( time < end_time ) { - Blip_Buffer* const output_left = this->outputs[2]; - Blip_Buffer* const output_right = this->outputs[1]; + Fake_Buffer* const output_left = this->outputs[2]; + Fake_Buffer* const output_right = this->outputs[1]; int delta_left = amp_left * 2; int delta_right = amp_right * 2; @@ -113,8 +119,8 @@ void T6W28_Square::run( sms_time_t time, sms_time_t end_time ) delta_left = -delta_left; delta_right = -delta_right; - synth->offset_inline( time, delta_left, output_left ); - synth->offset_inline( time, delta_right, output_right ); + fakeBufOffset( time, delta_left, output_left ); + fakeBufOffset( time, delta_right, output_right ); time += period; phase ^= 1; } @@ -131,7 +137,7 @@ void T6W28_Square::run( sms_time_t time, sms_time_t end_time ) static const int noise_periods [3] = { 0x100, 0x200, 0x400 }; -blip_inline void T6W28_Noise::reset() +void T6W28_Noise::reset() { period = &noise_periods [0]; shifter = 0x4000; @@ -158,13 +164,13 @@ void T6W28_Noise::run( sms_time_t time, sms_time_t end_time ) if ( delta_left ) { last_amp_left = amp_left; - synth.offset( time, delta_left, outputs[2] ); + fakeBufOffset( time, delta_left, outputs[2] ); } if ( delta_right ) { last_amp_right = amp_right; - synth.offset( time, delta_right, outputs[1] ); + fakeBufOffset( time, delta_right, outputs[1] ); } } @@ -175,8 +181,8 @@ void T6W28_Noise::run( sms_time_t time, sms_time_t end_time ) if ( time < end_time ) { - Blip_Buffer* const output_left = this->outputs[2]; - Blip_Buffer* const output_right = this->outputs[1]; + Fake_Buffer* const output_left = this->outputs[2]; + Fake_Buffer* const output_right = this->outputs[1]; unsigned l_shifter = this->shifter; int delta_left = amp_left * 2; @@ -193,10 +199,10 @@ void T6W28_Noise::run( sms_time_t time, sms_time_t end_time ) if ( changed ) { delta_left = -delta_left; - synth.offset_inline( time, delta_left, output_left ); + fakeBufOffset( time, delta_left, output_left ); delta_right = -delta_right; - synth.offset_inline( time, delta_right, output_right ); + fakeBufOffset( time, delta_right, output_right ); } time += l_period; } @@ -215,12 +221,10 @@ T6W28_Apu::T6W28_Apu() { for ( int i = 0; i < 3; i++ ) { - squares [i].synth = &square_synth; oscs [i] = &squares [i]; } oscs [3] = &noise; - volume( 1.0 ); reset(); } @@ -229,9 +233,9 @@ T6W28_Apu::~T6W28_Apu() } -void T6W28_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +void T6W28_Apu::osc_output( int index, Fake_Buffer* center, Fake_Buffer* left, Fake_Buffer* right ) { - require( (unsigned) index < osc_count ); + require( (unsigned int) index < osc_count ); require( (center && left && right) || (!center && !left && !right) ); T6W28_Osc& osc = *oscs [index]; osc.outputs [1] = right; @@ -239,7 +243,7 @@ void T6W28_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, B osc.outputs [3] = center; } -void T6W28_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +void T6W28_Apu::output( Fake_Buffer* center, Fake_Buffer* left, Fake_Buffer* right ) { for ( int i = 0; i < osc_count; i++ ) osc_output( i, center, left, right ); diff --git a/src/engine/platform/sound/t6w28/T6W28_Apu.h b/src/engine/platform/sound/t6w28/T6W28_Apu.h index e783fa81..2fb80224 100644 --- a/src/engine/platform/sound/t6w28/T6W28_Apu.h +++ b/src/engine/platform/sound/t6w28/T6W28_Apu.h @@ -8,6 +8,13 @@ namespace MDFN_IEN_NGP typedef long sms_time_t; // clock cycle count +struct Fake_Buffer { + int curValue; + Fake_Buffer(): + curValue(0) {} +}; + + } #include "T6W28_Oscs.h" @@ -38,15 +45,15 @@ public: // Assign all oscillator outputs to specified buffer(s). If buffer // is NULL, silences all oscillators. - void output( Blip_Buffer* mono ); - void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + void output( Fake_Buffer* mono ); + void output( Fake_Buffer* center, Fake_Buffer* left, Fake_Buffer* right ); // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, // which refer to Square 1, Square 2, Square 3, and Noise. If buffer is NULL, // silences oscillator. enum { osc_count = 4 }; - void osc_output( int index, Blip_Buffer* mono ); - void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + void osc_output( int index, Fake_Buffer* mono ); + void osc_output( int index, Fake_Buffer* center, Fake_Buffer* left, Fake_Buffer* right ); // Reset oscillators and internal state void reset(); @@ -79,9 +86,9 @@ private: void run_until( sms_time_t ); }; -inline void T6W28_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } +inline void T6W28_Apu::output( Fake_Buffer* b ) { output( b, b, b ); } -inline void T6W28_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } +inline void T6W28_Apu::osc_output( int i, Fake_Buffer* b ) { osc_output( i, b, b, b ); } } diff --git a/src/engine/platform/sound/t6w28/T6W28_Oscs.h b/src/engine/platform/sound/t6w28/T6W28_Oscs.h index 876b3ed1..d31f09da 100644 --- a/src/engine/platform/sound/t6w28/T6W28_Oscs.h +++ b/src/engine/platform/sound/t6w28/T6W28_Oscs.h @@ -11,6 +11,7 @@ namespace MDFN_IEN_NGP struct T6W28_Osc { + Fake_Buffer* outputs[4]; int output_select; int delay; diff --git a/src/engine/platform/t6w28.cpp b/src/engine/platform/t6w28.cpp new file mode 100644 index 00000000..6ff4cc12 --- /dev/null +++ b/src/engine/platform/t6w28.cpp @@ -0,0 +1,358 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "t6w28.h" +#include "../engine.h" +#include "sound/t6w28/T6W28_Apu.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_DIVIDER 16 + +const char* regCheatSheetT6W28[]={ + "Data0", "0", + "Data1", "1", + NULL +}; + +const char** DivPlatformT6W28::getRegisterSheet() { + return regCheatSheetT6W28; +} + +void DivPlatformT6W28::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hwrite_data_right(cycles,w.val); + } else { + t6w->write_data_left(cycles,w.val); + } + regPool[w.addr&1]=w.val; + //cycles+=2; + writes.pop(); + } + t6w->end_frame(16); + + tempL=0; + tempR=0; + for (int i=0; i<4; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(out[i][1].curValue+out[i][2].curValue)<<6; + tempL+=out[i][1].curValue<<7; + tempR+=out[i][2].curValue<<7; + } + + if (tempL<-32768) tempL=-32768; + if (tempL>32767) tempL=32767; + if (tempR<-32768) tempR=-32768; + if (tempR>32767) tempR=32767; + + bufL[h]=tempL; + bufR[h]=tempR; + } +} + +void DivPlatformT6W28::writeOutVol(int ch) { + int left=15-CLAMP(chan[ch].outVol+chan[ch].panL-15,0,15); + int right=15-CLAMP(chan[ch].outVol+chan[ch].panR-15,0,15); + rWrite(0,0x90|(ch<<5)|(isMuted[ch]?15:left)); + rWrite(1,0x90|(ch<<5)|(isMuted[ch]?15:right)); +} + +void DivPlatformT6W28::tick(bool sysTick) { + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val); + chan[i].baseFreq=NOTE_PERIODIC(noiseSeek); + } + chan[i].freqChanged=true; + } + if (chan[i].std.panL.had) { + chan[i].panL=chan[i].std.panL.val&15; + } + if (chan[i].std.panR.had) { + chan[i].panR=chan[i].std.panR.val&15; + } + if (chan[i].std.vol.had || chan[i].std.panL.had || chan[i].std.panR.had) { + writeOutVol(i); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + if (chan[i].freq>1023) chan[i].freq=1023; + if (i==3) { + rWrite(1,0xe7); + rWrite(1,0x80|(2<<5)|(chan[3].freq&15)); + rWrite(1,chan[3].freq>>4); + } else { + rWrite(0,0x80|i<<5|(chan[i].freq&15)); + rWrite(0,chan[i].freq>>4); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformT6W28::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + if (chan[c.chan].active) { + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + break; + case DIV_CMD_PANNING: { + chan[c.chan].panL=c.value>>4; + chan[c.chan].panR=c.value2>>4; + writeOutVol(c.chan); + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 31; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformT6W28::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformT6W28::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformT6W28::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformT6W28::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformT6W28::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformT6W28::getRegisterPool() { + return regPool; +} + +int DivPlatformT6W28::getRegisterPoolSize() { + return 112; +} + +void DivPlatformT6W28::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<4; i++) { + chan[i]=DivPlatformT6W28::Channel(); + chan[i].std.setEngine(parent); + + out[i][0].curValue=0; + out[i][1].curValue=0; + out[i][2].curValue=0; + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + t6w->reset(); + lastPan=0xff; + tempL=0; + tempR=0; + cycles=0; + curChan=-1; + delay=0; +} + +bool DivPlatformT6W28::isStereo() { + return true; +} + +bool DivPlatformT6W28::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformT6W28::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformT6W28::setFlags(const DivConfig& flags) { + chipClock=3072000.0; + rate=chipClock/16; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } + + if (t6w!=NULL) { + delete t6w; + t6w=NULL; + } + t6w=new MDFN_IEN_NGP::T6W28_Apu; + for (int i=0; i<4; i++) { + t6w->osc_output(i,&out[i][0],&out[i][1],&out[i][2]); + } +} + +void DivPlatformT6W28::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformT6W28::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformT6W28::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<4; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + t6w=NULL; + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformT6W28::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } + if (t6w!=NULL) { + delete t6w; + t6w=NULL; + } +} + +DivPlatformT6W28::~DivPlatformT6W28() { +} diff --git a/src/engine/platform/t6w28.h b/src/engine/platform/t6w28.h new file mode 100644 index 00000000..bc445991 --- /dev/null +++ b/src/engine/platform/t6w28.h @@ -0,0 +1,103 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _T6W28_H +#define _T6W28_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "sound/t6w28/T6W28_Apu.h" + +class DivPlatformT6W28: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note; + int ins; + unsigned char panL, panR; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise; + signed char vol, outVol; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + ins(-1), + panL(15), + panR(15), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + noise(false), + vol(15), + outVol(15) {} + }; + Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; + bool isMuted[4]; + bool antiClickEnabled; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char lastPan; + + int cycles, curChan, delay; + int tempL, tempR; + MDFN_IEN_NGP::T6W28_Apu* t6w; + MDFN_IEN_NGP::Fake_Buffer out[4][3]; + unsigned char regPool[128]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + void writeOutVol(int ch); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void setFlags(const DivConfig& flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformT6W28(); +}; + +#endif diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index e3d5a6a0..29ff6a93 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -35,55 +35,55 @@ const char* regCheatSheetVB[]={ "Wave4", "200", "ModTable", "280", - "S0INT", "400", - "S0LRV", "404", - "S0FQL", "408", - "S0FQH", "40C", - "S0EV0", "410", - "S0EV1", "414", - "S0RAM", "418", + "S1INT", "400", + "S1LRV", "404", + "S1FQL", "408", + "S1FQH", "40C", + "S1EV0", "410", + "S1EV1", "414", + "S1RAM", "418", - "S1INT", "440", - "S1LRV", "444", - "S1FQL", "448", - "S1FQH", "44C", - "S1EV0", "450", - "S1EV1", "454", - "S1RAM", "458", + "S2INT", "440", + "S2LRV", "444", + "S2FQL", "448", + "S2FQH", "44C", + "S2EV0", "450", + "S2EV1", "454", + "S2RAM", "458", - "S2INT", "480", - "S2LRV", "484", - "S2FQL", "488", - "S2FQH", "48C", - "S2EV0", "480", - "S2EV1", "484", - "S2RAM", "488", + "S3INT", "480", + "S3LRV", "484", + "S3FQL", "488", + "S3FQH", "48C", + "S3EV0", "480", + "S3EV1", "484", + "S3RAM", "488", - "S3INT", "4C0", - "S3LRV", "4C4", - "S3FQL", "4C8", - "S3FQH", "4CC", - "S3EV0", "4C0", - "S3EV1", "4C4", - "S3RAM", "4C8", + "S4INT", "4C0", + "S4LRV", "4C4", + "S4FQL", "4C8", + "S4FQH", "4CC", + "S4EV0", "4C0", + "S4EV1", "4C4", + "S4RAM", "4C8", - "S4INT", "500", - "S4LRV", "504", - "S4FQL", "508", - "S4FQH", "50C", - "S4EV0", "510", - "S4EV1", "514", - "S4RAM", "518", + "S5INT", "500", + "S5LRV", "504", + "S5FQL", "508", + "S5FQH", "50C", + "S5EV0", "510", + "S5EV1", "514", + "S5RAM", "518", - "S4SWP", "51C", + "S5SWP", "51C", - "S5INT", "540", - "S5LRV", "544", - "S5FQL", "548", - "S5FQH", "54C", - "S5EV0", "550", - "S5EV1", "554", - "S5RAM", "558", + "S6INT", "540", + "S6LRV", "544", + "S6FQL", "548", + "S6FQH", "54C", + "S6EV0", "550", + "S6EV1", "554", + "S6RAM", "558", NULL }; @@ -93,46 +93,11 @@ const char** DivPlatformVB::getRegisterSheet() { void DivPlatformVB::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hrate) { - DivSample* s=parent->getSample(chan[i].dacSample); - if (s->samples<=0) { - chan[i].dacSample=-1; - continue; - } - chWrite(i,0x07,0); - signed char dacData=((signed char)((unsigned char)s->data8[chan[i].dacPos]^0x80))>>3; - chan[i].dacOut=CLAMP(dacData,-16,15); - if (!isMuted[i]) { - chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].outVol)); - chWrite(i,0x06,chan[i].dacOut&0x1f); - } else { - chWrite(i,0x04,0xc0); - chWrite(i,0x06,0x10); - } - chan[i].dacPos++; - if (s->isLoopable() && chan[i].dacPos>=(unsigned int)s->loopEnd) { - chan[i].dacPos=s->loopStart; - } else if (chan[i].dacPos>=s->samples) { - chan[i].dacSample=-1; - } - chan[i].dacPeriod-=rate; - } - } - } - */ - - // VB part cycles=0; while (!writes.empty()) { QueuedWrite w=writes.front(); vb->Write(cycles,w.addr,w.val); - regPool[w.addr]=w.val; - //cycles+=2; + regPool[w.addr>>2]=w.val; writes.pop(); } vb->EndFrame(16); @@ -158,63 +123,32 @@ void DivPlatformVB::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformVB::updateWave(int ch) { if (ch>=5) return; - if (chan[ch].pcm) { - chan[ch].deferredWaveUpdate=true; - return; - } for (int i=0; i<32; i++) { rWrite((ch<<7)+(i<<2),chan[ch].ws.output[i]); - //chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]); - } - chan[ch].antiClickWavePos&=31; - if (chan[ch].active) { - //chWrite(ch,0x04,0x80|chan[ch].outVol); - } - if (chan[ch].deferredWaveUpdate) { - chan[ch].deferredWaveUpdate=false; } } -// TODO: in octave 6 the noise table changes to a tonal one -static unsigned char noiseFreq[12]={ - 4,13,15,18,21,23,25,27,29,31,0,2 -}; +void DivPlatformVB::writeEnv(int ch, bool upperByteToo) { + chWrite(ch,0x04,(chan[ch].outVol<<4)|(chan[ch].envLow&15)); + if (ch<5 || upperByteToo) { + chWrite(ch,0x05,chan[ch].envHigh); + } +} void DivPlatformVB::tick(bool sysTick) { for (int i=0; i<6; i++) { - // anti-click - if (antiClickEnabled && sysTick && chan[i].freq>0) { - chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f)); - chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq; - chan[i].antiClickPeriodCount%=chan[i].freq; - } - chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); - if (chan[i].furnaceDac && chan[i].pcm) { - // ignore for now - } else { - chWrite(i,0x04,chan[i].outVol<<4); - } - } - if (chan[i].std.duty.had && i>=4) { - chan[i].noise=chan[i].std.duty.val; - chan[i].freqChanged=true; - int noiseSeek=chan[i].note; - if (noiseSeek<0) noiseSeek=0; - chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + writeEnv(i); } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val); - chan[i].baseFreq=NOTE_PERIODIC(noiseSeek); - if (noiseSeek<0) noiseSeek=0; - chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; } - if (chan[i].std.wave.had && !chan[i].pcm) { + if (chan[i].std.wave.had) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave.val; chan[i].ws.changeWave1(chan[i].wave); @@ -242,39 +176,15 @@ void DivPlatformVB::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { - if (chan[i].furnaceDac && chan[i].pcm) { - if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { - chan[i].dacPos=0; - chan[i].dacPeriod=0; - //chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol)); - addWrite(0xffff0000+(i<<8),chan[i].dacSample); - chan[i].keyOn=true; - } - } - chan[i].antiClickWavePos=0; - chan[i].antiClickPeriodCount=0; + // ??? } if (chan[i].active) { - if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) { + if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) { updateWave(i); } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); - if (chan[i].furnaceDac && chan[i].pcm) { - double off=1.0; - if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { - DivSample* s=parent->getSample(chan[i].dacSample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } - chan[i].dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); - if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dacRate); - } if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq>2047) chan[i].freq=2047; chan[i].freq=2048-chan[i].freq; @@ -296,64 +206,6 @@ int DivPlatformVB::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); - chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:31; - if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { - chan[c.chan].pcm=true; - } else if (chan[c.chan].furnaceDac) { - chan[c.chan].pcm=false; - } - if (chan[c.chan].pcm) { - if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { - chan[c.chan].furnaceDac=true; - if (skipRegisterWrites) break; - chan[c.chan].dacSample=ins->amiga.getSample(c.value); - if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { - chan[c.chan].dacSample=-1; - if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); - break; - } else { - if (dumpWrites) { - //chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); - addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); - } - } - chan[c.chan].dacPos=0; - chan[c.chan].dacPeriod=0; - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - chan[c.chan].freqChanged=true; - chan[c.chan].note=c.value; - } - chan[c.chan].active=true; - chan[c.chan].macroInit(ins); - if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { - chan[c.chan].outVol=chan[c.chan].vol; - } - //chan[c.chan].keyOn=true; - } else { - chan[c.chan].furnaceDac=false; - if (skipRegisterWrites) break; - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].note=c.value; - } - chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12; - if (chan[c.chan].dacSample>=parent->song.sampleLen) { - chan[c.chan].dacSample=-1; - if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); - break; - } else { - if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); - } - chan[c.chan].dacPos=0; - chan[c.chan].dacPeriod=0; - chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; - if (dumpWrites) { - //chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); - addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); - } - } - break; - } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; @@ -362,8 +214,15 @@ int DivPlatformVB::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); + if (chan[c.chan].insChanged && ins->fds.initModTableWithFirstWave) { + for (int i=0; i<32; i++) { + modTable[i]=ins->fds.modTable[i]; + rWrite(0x280+(i<<2),modTable[i]); + } + } if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; + writeEnv(c.chan); } if (chan[c.chan].wave<0) { chan[c.chan].wave=0; @@ -374,9 +233,6 @@ int DivPlatformVB::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - chan[c.chan].dacSample=-1; - if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); - chan[c.chan].pcm=false; chan[c.chan].active=false; chan[c.chan].keyOff=true; chan[c.chan].macroInit(NULL); @@ -396,8 +252,8 @@ int DivPlatformVB::dispatch(DivCommand c) { chan[c.chan].vol=c.value; if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; - if (chan[c.chan].active && !chan[c.chan].pcm) { - chWrite(c.chan,0x04,chan[c.chan].outVol<<4); + if (chan[c.chan].active) { + writeEnv(c.chan); } } } @@ -417,20 +273,6 @@ int DivPlatformVB::dispatch(DivCommand c) { chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; - case DIV_CMD_PCE_LFO_MODE: - if (c.value==0) { - lfoMode=0; - } else { - lfoMode=c.value; - } - rWrite(0x08,lfoSpeed); - rWrite(0x09,lfoMode); - break; - case DIV_CMD_PCE_LFO_SPEED: - lfoSpeed=255-c.value; - rWrite(0x08,lfoSpeed); - rWrite(0x09,lfoMode); - break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; @@ -455,18 +297,60 @@ int DivPlatformVB::dispatch(DivCommand c) { break; } case DIV_CMD_STD_NOISE_MODE: - chan[c.chan].noise=c.value; - chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|chan[c.chan].note):0); + if (c.chan!=5) break; + chan[c.chan].envHigh&=~0x70; + chan[c.chan].envHigh|=(c.value&7)<<4; + writeEnv(c.chan,true); break; - case DIV_CMD_SAMPLE_MODE: - chan[c.chan].pcm=c.value; + case DIV_CMD_STD_NOISE_FREQ: + chan[c.chan].envHigh&=~3; + chan[c.chan].envHigh|=(c.value>>4)&3; + chan[c.chan].envLow=c.value&15; + writeEnv(c.chan); break; - case DIV_CMD_SAMPLE_BANK: - sampleBank=c.value; - if (sampleBank>(parent->song.sample.size()/12)) { - sampleBank=parent->song.sample.size()/12; + case DIV_CMD_FDS_MOD_DEPTH: // set modulation + if (c.chan!=4) break; + modulation=(c.value<<4)&15; + modType=true; + chWrite(4,0x07,modulation); + if (modulation!=0) { + chan[c.chan].envHigh&=~0x70; + chan[c.chan].envHigh|=0x40|((c.value&15)<<4); + } else { + chan[c.chan].envHigh&=~0x70; + } + writeEnv(4); + break; + case DIV_CMD_GB_SWEEP_TIME: // set sweep + if (c.chan!=4) break; + modulation=c.value; + modType=false; + chWrite(4,0x07,modulation); + if (modulation!=0) { + chan[c.chan].envHigh&=~0x70; + chan[c.chan].envHigh|=0x10; + } else { + chan[c.chan].envHigh&=~0x70; + } + writeEnv(4); + break; + case DIV_CMD_FDS_MOD_WAVE: { // set modulation wave + if (c.chan!=4) break; + DivWavetable* wt=parent->getWave(c.value); + for (int i=0; i<32; i++) { + if (wt->max<1 || wt->len<1) { + modTable[i]=0; + rWrite(0x280+(i<<2),0); + } else { + int data=(wt->data[i*wt->len/32]*255/wt->max)-128; + if (data<-128) data=-128; + if (data>127) data=127; + modTable[i]=data; + rWrite(0x280+(i<<2),modTable[i]); + } } break; + } case DIV_CMD_PANNING: { chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); chWrite(c.chan,0x01,isMuted[c.chan]?0:chan[c.chan].pan); @@ -499,10 +383,6 @@ int DivPlatformVB::dispatch(DivCommand c) { void DivPlatformVB::muteChannel(int ch, bool mute) { isMuted[ch]=mute; chWrite(ch,0x01,isMuted[ch]?0:chan[ch].pan); - if (!isMuted[ch] && (chan[ch].pcm && chan[ch].dacSample!=-1)) { - //chWrite(ch,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol)); - //chWrite(ch,0x06,chan[ch].dacOut&0x1f); - } } void DivPlatformVB::forceIns() { @@ -531,7 +411,7 @@ unsigned char* DivPlatformVB::getRegisterPool() { } int DivPlatformVB::getRegisterPoolSize() { - return 0x600; + return 0x180; } int DivPlatformVB::getRegisterPoolDepth() { @@ -551,14 +431,13 @@ void DivPlatformVB::reset() { addWrite(0xffffffff,0); } vb->Power(); - lastPan=0xff; tempL=0; tempR=0; cycles=0; curChan=-1; - sampleBank=0; - lfoMode=0; - lfoSpeed=255; + modulation=0; + modType=false; + memset(modTable,0,32); // set per-channel initial values for (int i=0; i<6; i++) { chWrite(i,0x01,isMuted[i]?0:chan[i].pan); @@ -598,7 +477,6 @@ void DivPlatformVB::notifyInsDeletion(void* ins) { void DivPlatformVB::setFlags(const DivConfig& flags) { chipClock=5000000.0; - antiClickEnabled=!flags.getBool("noAntiClick",false); rate=chipClock/16; for (int i=0; i<6; i++) { oscBuf[i]->rate=rate; diff --git a/src/engine/platform/vb.h b/src/engine/platform/vb.h index d01b9c05..baefc03f 100644 --- a/src/engine/platform/vb.h +++ b/src/engine/platform/vb.h @@ -28,14 +28,11 @@ class DivPlatformVB: public DivDispatch { struct Channel { - int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos; - int dacPeriod, dacRate, dacOut; - unsigned int dacPos; - int dacSample, ins; - unsigned char pan; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate; + int freq, baseFreq, pitch, pitch2, note; + int ins; + unsigned char pan, envLow, envHigh; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, deferredWaveUpdate; signed char vol, outVol, wave; - int macroVolMul; DivMacroInt std; DivWaveSynth ws; void macroInit(DivInstrument* which) { @@ -48,15 +45,10 @@ class DivPlatformVB: public DivDispatch { pitch(0), pitch2(0), note(0), - antiClickPeriodCount(0), - antiClickWavePos(0), - dacPeriod(0), - dacRate(0), - dacOut(0), - dacPos(0), - dacSample(-1), ins(-1), pan(255), + envLow(0), + envHigh(0), active(false), insChanged(true), freqChanged(false), @@ -64,18 +56,14 @@ class DivPlatformVB: public DivDispatch { keyOff(false), inPorta(false), noise(false), - pcm(false), - furnaceDac(false), deferredWaveUpdate(false), - vol(31), - outVol(31), - wave(-1), - macroVolMul(31) {} + vol(15), + outVol(15), + wave(-1) {} }; Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; - bool antiClickEnabled; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -87,10 +75,13 @@ class DivPlatformVB: public DivDispatch { int cycles, curChan, delay; int tempL; int tempR; - unsigned char sampleBank, lfoMode, lfoSpeed; + unsigned char modulation; + bool modType; + signed char modTable[32]; VSU* vb; unsigned char regPool[0x600]; void updateWave(int ch); + void writeEnv(int ch, bool upperByteToo=false); friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index fc2bbfd1..8c83e598 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1188,7 +1188,14 @@ void DivEngine::registerSystems() { {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE}, {DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, {}, - waveOnlyEffectHandlerMap + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set noise length (0 to 7)"}}, + {0x12, {DIV_CMD_STD_NOISE_FREQ, "12xy: Setup envelope (x: enabled/loop (1: enable, 3: enable+loop); y: speed/direction (0-7: down, 8-F: up))"}}, + {0x13, {DIV_CMD_GB_SWEEP_TIME, "13xy: Setup sweep (x: speed; y: shift; channel 5 only)"}}, + {0x14, {DIV_CMD_FDS_MOD_DEPTH, "14xy: Setup modulation (x: enabled/loop (1: enable, 3: enable+loop); y: speed; channel 5 only)"}}, + {0x15, {DIV_CMD_FDS_MOD_WAVE, "15xx: Set modulation waveform (x: wavetable; channel 5 only)"}}, + } ); sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef( @@ -1695,7 +1702,7 @@ void DivEngine::registerSystems() { {"Square 1", "Square 2", "Square 3", "Noise"}, {"S1", "S2", "S3", "NO"}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE}, - {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, + {DIV_INS_T6W28, DIV_INS_T6W28, DIV_INS_T6W28, DIV_INS_T6W28}, {}, { {0x20, {DIV_CMD_STD_NOISE_MODE, "20xy: Set noise mode (x: preset/variable; y: thin pulse/noise)"}} diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 29e2b06c..7d5642f0 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -846,6 +846,20 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } } +#define CHIP_VOL(_id,_mult) { \ + double _vol=fabs(song.systemVol[i])*4.0*_mult; \ + if (_vol<0.0) _vol=0.0; \ + if (_vol>32767.0) _vol=32767.0; \ + chipVol.push_back((_id)|(0x80000000)|(((unsigned int)_vol)<<16)); \ +} + +#define CHIP_VOL_SECOND(_id,_mult) { \ + double _vol=fabs(song.systemVol[i])*4.0*_mult; \ + if (_vol<0.0) _vol=0.0; \ + if (_vol>32767.0) _vol=32767.0; \ + chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \ +} + SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) { if (version<0x150) { lastError="VGM version is too low"; @@ -949,6 +963,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p double loopFreq[DIV_MAX_CHANS]; int loopSample[DIV_MAX_CHANS]; bool sampleDir[DIV_MAX_CHANS]; + std::vector chipVol; for (int i=0; ichipClock; + CHIP_VOL(0,1.0); willExport[i]=true; switch (song.systemFlags[i].getInt("chipType",0)) { case 1: // real SN @@ -1001,6 +1017,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p } else if (!(hasSN&0x40000000)) { isSecond[i]=true; willExport[i]=true; + CHIP_VOL_SECOND(0,1.0); hasSN|=0x40000000; howManyChips++; } @@ -1407,14 +1424,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p } } - //bool wantsExtraHeader=false; - /*for (int i=0; iwriteI(hasSN); w->writeI(hasOPLL); @@ -1476,8 +1485,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeC(0); // OPN w->writeC(0); // OPNA } + if (version>=0x160) { + int calcVolume=32.0*(log(song.masterVol)/log(2.0)); + if (calcVolume<-63) calcVolume=-63; + if (calcVolume>192) calcVolume=192; + w->writeC(calcVolume&0xff); // volume + } else { + w->writeC(0); // volume + } // currently not used but is part of 1.60 - w->writeC(0); // volume w->writeC(0); // reserved w->writeC(0); // loop count // 1.51 @@ -1561,15 +1577,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); } - /* TODO unsigned int exHeaderOff=w->tell(); - if (wantsExtraHeader) { - w->writeI(4); + if (version>=0x170) { + logD("writing extended header..."); + w->writeI(8); + w->writeI(0); w->writeI(4); - // write clocks - w->writeC(howManyChips); - }*/ + // write chip volumes + logD("writing chip volumes (%ld)...",chipVol.size()); + w->writeC(chipVol.size()); + for (unsigned int& i: chipVol) { + logV("- %.8x",i); + w->writeI(i); + } + } unsigned int songOff=w->tell(); @@ -2082,10 +2104,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p } w->seek(0x34,SEEK_SET); w->writeI(songOff-0x34); - /*if (wantsExtraHeader) { + if (version>=0x170) { w->seek(0xbc,SEEK_SET); w->writeI(exHeaderOff-0xbc); - }*/ + } remainingLoops=-1; playing=false; diff --git a/src/gui/about.cpp b/src/gui/about.cpp index d14cd4d1..6610af22 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -134,7 +134,7 @@ const char* aboutLine[]={ "MAME YMZ280B core by Aaron Giles", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", - "Mednafen PCE and WonderSwan audio cores", + "Mednafen PCE, WonderSwan, T6W28 and Virtual Boy audio cores", "SNES DSP core by Blargg", "puNES (NES, MMC5 and FDS) by FHorse", "NSFPlay (NES and FDS) by Brad Smith and Brezza", diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index e3ad642c..93053135 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -34,9 +34,9 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); } - ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); + InvCheckbox("Compatible noise layout on NES and PC Engine",&e->song.properNoiseLayout); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); + ImGui::SetTooltip("use a rather unusual compatible noise frequency layout.\nremoves some noise frequencies on PC Engine."); } ImGui::Checkbox("Game Boy instrument duty is wave volume",&e->song.waveDutyIsVol); if (ImGui::IsItemHovered()) { @@ -55,13 +55,13 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("delay arpeggio by one tick on every new note."); } - ImGui::Checkbox("Reset slides after note off",&e->song.noteOffResetsSlides); + InvCheckbox("Don't reset slides after note off",&e->song.noteOffResetsSlides); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, note off will reset the channel's slide effect."); + ImGui::SetTooltip("when enabled, note off will not reset the channel's slide effect."); } - ImGui::Checkbox("Reset portamento after reaching target",&e->song.targetResetsSlides); + InvCheckbox("Don't reset portamento after reaching target",&e->song.targetResetsSlides); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, the slide effect is disabled after it reaches its target."); + ImGui::SetTooltip("when enabled, the slide effect will not be disabled after it reaches its target."); } ImGui::Checkbox("Ignore duplicate slide effects",&e->song.ignoreDuplicateSlides); if (ImGui::IsItemHovered()) { @@ -103,9 +103,9 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if this is on, a pitch slide that crosses the octave boundary will stop for one tick and then continue from the nearest octave boundary.\nfor .dmf compatibility."); } - ImGui::Checkbox("Apply Game Boy envelope on note-less instrument change",&e->song.gbInsAffectsEnvelope); + InvCheckbox("Don't apply Game Boy envelope on note-less instrument change",&e->song.gbInsAffectsEnvelope); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("if this is on, an instrument change will also affect the envelope."); + ImGui::SetTooltip("if this is on, an instrument change will not affect the envelope."); } ImGui::Checkbox("Ignore DAC mode change outside of intended channel in ExtCh mode",&e->song.ignoreDACModeOutsideIntendedChannel); if (ImGui::IsItemHovered()) { @@ -123,17 +123,17 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, duty macro will always reset phase, even if its value hasn't changed."); } - ImGui::Checkbox("Pitch macro is linear",&e->song.pitchMacroIsLinear); + InvCheckbox("Pitch macro is not linear",&e->song.pitchMacroIsLinear); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space."); + ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in frequency/period space."); } - ImGui::Checkbox("Proper volume scaling strategy",&e->song.newVolumeScaling); + InvCheckbox("Broken volume scaling strategy",&e->song.newVolumeScaling); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when disabled:\n- log scaling: multiply\n- linear scaling: subtract\nwhen enabled:\n- log scaling: subtract\n- linear scaling: multiply"); + ImGui::SetTooltip("when enabled:\n- log scaling: multiply\n- linear scaling: subtract\nwhen disabled:\n- log scaling: subtract\n- linear scaling: multiply"); } - ImGui::Checkbox("Persist volume macro after it finishes",&e->song.volMacroLinger); + InvCheckbox("Don't persist volume macro after it finishes",&e->song.volMacroLinger); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when disabled, a value in the volume column that happens after the volume macro is done will disregard the macro."); + ImGui::SetTooltip("when enabled, a value in the volume column that happens after the volume macro is done will disregard the macro."); } ImGui::Checkbox("Broken output volume on instrument change",&e->song.brokenOutVol); if (ImGui::IsItemHovered()) { @@ -257,19 +257,19 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } - ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); + InvCheckbox("Don't allow instrument change during slides",&e->song.newInsTriggersInPorta); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } - ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); + InvCheckbox("Don't reset note to base on arpeggio stop",&e->song.arp0Reset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } - ImGui::Checkbox("ExtCh channel status is shared among operators",&e->song.sharedExtStat); + InvCheckbox("ExtCh channel status is not shared among operators",&e->song.sharedExtStat); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } - ImGui::Checkbox("New SegaPCM features (macros and better panning)",&e->song.newSegaPCM); + InvCheckbox("Disable new SegaPCM features (macros and better panning)",&e->song.newSegaPCM); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } @@ -277,7 +277,7 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } - ImGui::Checkbox("No OPN2 DAC volume control",&e->song.noOPN2Vol); + ImGui::Checkbox("Disable OPN2 DAC volume control",&e->song.noOPN2Vol); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6pre1"); } diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 9a1740be..f45028ff 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -382,6 +382,10 @@ void FurnaceGUI::drawInsList(bool asChild) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM5232]); name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; + case DIV_INS_T6W28: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_T6W28]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f9e1237f..df32aaa0 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -491,6 +491,15 @@ bool FurnaceGUI::CWVSliderInt(const char* label, const ImVec2& size, int* v, int return CWVSliderScalar(label,size,ImGuiDataType_S32,v,&v_min,&v_max,format,flags); } +bool FurnaceGUI::InvCheckbox(const char* label, bool* value) { + bool t=!(*value); + if (ImGui::Checkbox(label,&t)) { + *value=!t; + return true; + } + return false; +} + const char* FurnaceGUI::getSystemName(DivSystem which) { /* if (settings.chipNames) { diff --git a/src/gui/gui.h b/src/gui/gui.h index bb23ccee..136c385f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -171,6 +171,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_YMZ280B, GUI_COLOR_INSTR_RF5C68, GUI_COLOR_INSTR_MSM5232, + GUI_COLOR_INSTR_T6W28, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, @@ -1644,6 +1645,9 @@ class FurnaceGUI { bool CWSliderFloat(const char* label, float* v, float v_min, float v_max, const char* format="%.3f", ImGuiSliderFlags flags=0); bool CWVSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format="%d", ImGuiSliderFlags flags=0); + // inverted checkbox + bool InvCheckbox(const char* label, bool* value); + void updateWindowTitle(); void autoDetectSystem(); void prepareLayout(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 7afefffd..12a300f3 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -124,6 +124,7 @@ const char* insTypes[DIV_INS_MAX+1]={ "YMZ280B", "RF5C68", "MSM5232", + "T6W28", NULL }; @@ -801,6 +802,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_YMZ280B,"",ImVec4(0.4f,0.5f,1.0f,1.0f)), D(GUI_COLOR_INSTR_RF5C68,"",ImVec4(1.0f,0.3f,0.3f,1.0f)), D(GUI_COLOR_INSTR_MSM5232,"",ImVec4(0.5f,0.9f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_T6W28,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), @@ -906,6 +908,7 @@ const int availableSystems[]={ DIV_SYSTEM_YM2610_FULL_EXT, DIV_SYSTEM_YM2610B, DIV_SYSTEM_YM2610B_EXT, + DIV_SYSTEM_T6W28, DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_PCSPKR, @@ -1002,6 +1005,7 @@ const int chipsSquare[]={ DIV_SYSTEM_SAA1099, DIV_SYSTEM_VIC20, DIV_SYSTEM_MSM5232, + DIV_SYSTEM_T6W28, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e1bd4ab3..006ffc9e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4056,7 +4056,7 @@ void FurnaceGUI::drawInsEdit() { // Wavetable if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES) { ImGui::BeginDisabled(ins->amiga.useNoteMap||ins->amiga.transWave.enable); - P(ImGui::Checkbox("Use wavetable (Amiga/SNES only)",&ins->amiga.useWave)); + P(ImGui::Checkbox("Use wavetable (Amiga/SNES/Generic DAC only)",&ins->amiga.useWave)); if (ins->amiga.useWave) { int len=ins->amiga.waveLen+1; int origLen=len; @@ -4397,6 +4397,37 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_VBOY) if (ImGui::BeginTabItem("Virtual Boy")) { + float modTable[32]; + P(ImGui::Checkbox("Set modulation table (channel 5 only)",&ins->fds.initModTableWithFirstWave)); + + ImGui::BeginDisabled(!ins->fds.initModTableWithFirstWave); + for (int i=0; i<32; i++) { + modTable[i]=ins->fds.modTable[i]; + } + ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,256.0f*dpiScale); + PlotCustom("ModTable",modTable,32,0,NULL,-128,127,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; + macroDragMin=-128; + macroDragMax=127; + macroDragBitOff=0; + macroDragBitMode=false; + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=32; + macroDragActive=true; + macroDragCTarget=(unsigned char*)ins->fds.modTable; + macroDragChar=true; + macroDragLineMode=false; + macroDragLineInitial=ImVec2(0,0); + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + + ImGui::EndDisabled(); + ImGui::EndTabItem(); + } if (ins->type==DIV_INS_ES5506) if (ImGui::BeginTabItem("ES5506")) { if (ImGui::BeginTable("ESParams",2,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); @@ -4941,7 +4972,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_SAA1099) { waveBitMode=true; } - if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_NES) waveMax=0; + if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_NES || ins->type==DIV_INS_T6W28) waveMax=0; if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; @@ -5065,7 +5096,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO || ins->type==DIV_INS_RF5C68 || - ins->type==DIV_INS_VBOY) { + ins->type==DIV_INS_VBOY || ins->type==DIV_INS_T6W28) { panMax=15; } if (ins->type==DIV_INS_SEGAPCM) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index a5acecb4..0c48df3d 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -444,6 +444,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Virtual Boy", { + DIV_SYSTEM_VBOY, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Seta/Allumer X1-010", { DIV_SYSTEM_X1_010, 64, 0, 0, @@ -710,6 +716,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Virtual Boy", { + DIV_SYSTEM_VBOY, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Gamate", { DIV_SYSTEM_AY8910, 64, 0, 73, diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f16f2588..e18c9a00 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1680,6 +1680,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Instrument Types")) { UI_COLOR_CONFIG(GUI_COLOR_INSTR_FM,"FM (OPN)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_STD,"SN76489/Sega PSG"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_T6W28,"T6W28"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_GB,"Game Boy"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_C64,"C64"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_AMIGA,"Amiga/Generic Sample"); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index b9b8d65b..19b13f26 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -153,6 +153,8 @@ void FurnaceGUI::doGenerateWave() { if (finalResult[i]>1.0f) finalResult[i]=1.0f; wave->data[i]=round(finalResult[i]*wave->max); } + + e->notifyWaveChange(curWave); } #define CENTER_TEXT(text) \ @@ -254,7 +256,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::TableNextColumn(); ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG, Virtual Boy and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); @@ -268,7 +270,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010 and SCC\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS and Virtual Boy\n- 255 for X1-010 and SCC\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); diff --git a/src/main.cpp b/src/main.cpp index aadc8ded..83bb31b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -179,7 +179,8 @@ TAParamResult pVersion(String) { printf("- VERA core by Frank van den Hoef (BSD 2-clause)\n"); printf("- SAASound by Dave Hooper and Simon Owen (BSD 3-clause)\n"); printf("- SameBoy by Lior Halphon (MIT)\n"); - printf("- Mednafen PCE and WonderSwan by Mednafen Team (GPLv2)\n"); + printf("- Mednafen PCE, WonderSwan and Virtual Boy by Mednafen Team (GPLv2)\n"); + printf("- Mednafen T6W28 by Blargg (GPLv2)\n"); printf("- SNES DSP core by Blargg (LGPLv2.1)\n"); printf("- puNES by FHorse (GPLv2)\n"); printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n");