Merge branch 'master' into es5506_alt

This commit is contained in:
cam900 2022-10-12 15:31:38 +09:00 committed by GitHub
commit 081773b2da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 822 additions and 342 deletions

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@ -505,6 +505,7 @@ size | description
| - 41: YMZ280B
| - 42: RF5C68
| - 43: MSM5232
| - 44: T6W28
1 | reserved
STR | instrument name
--- | **FM instrument data**

View File

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

View File

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

View File

@ -1,9 +1,11 @@
// T6W28_Snd_Emu
#include "T6W28_Apu.h"
#include <stdlib.h>
#include <assert.h>
#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 );

View File

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

View File

@ -11,6 +11,7 @@ namespace MDFN_IEN_NGP
struct T6W28_Osc
{
Fake_Buffer* outputs[4];
int output_select;
int delay;

View File

@ -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 <math.h>
//#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; h<start+len; h++) {
cycles=0;
while (!writes.empty() && cycles<16) {
QueuedWrite w=writes.front();
if (w.addr) {
t6w->write_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<DivRegWrite>& 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() {
}

103
src/engine/platform/t6w28.h Normal file
View File

@ -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 <queue>
#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<QueuedWrite> 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<DivRegWrite>& wlist);
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformT6W28();
};
#endif

View File

@ -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; h<start+len; h++) {
// PCM part
/*
for (int i=0; i<6; i++) {
if (chan[i].pcm && chan[i].dacSample!=-1) {
chan[i].dacPeriod+=chan[i].dacRate;
if (chan[i].dacPeriod>rate) {
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].dacSample<parent->song.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].dacSample<parent->song.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;

View File

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

View File

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

View File

@ -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<unsigned int> chipVol;
for (int i=0; i<DIV_MAX_CHANS; i++) {
loopTimer[i]=0;
@ -983,6 +998,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
case DIV_SYSTEM_SMS:
if (!hasSN) {
hasSN=disCont[i].dispatch->chipClock;
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; i<song.systemLen; i++) {
if (isSecond[i]) {
wantsExtraHeader=true;
break;
}
}*/
// write chips and stuff
w->writeI(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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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