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

* 'master' of https://github.com/tildearrow/furnace:
  document (NON-WORKING) extended op param effects
  FDS: add NSFPlay core
  NES: fix Furnace-style DPCM
  SoundUnit: fix PCM pitch
  SoundUnit: PCM support

# Conflicts:
#	src/gui/debugWindow.cpp
This commit is contained in:
cam900 2022-05-03 10:23:36 +09:00
commit 2412f688de
19 changed files with 519 additions and 35 deletions

View file

@ -44,3 +44,37 @@ afterwards everyone moved to Windows and software mixed PCM streaming...
- only in 4-op mode (OPL3). - only in 4-op mode (OPL3).
- `1Dxx`: set attack of operator 4. - `1Dxx`: set attack of operator 4.
- only in 4-op mode (OPL3). - only in 4-op mode (OPL3).
- `2Axy`: set waveform of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` is the value.
- only in OPL2 or higher.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` is the value.
- `53xy`: set VIB of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` determines whether VIB is on.
- `54xy` set KSL of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SUS of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` determines whether SUS is on.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `58xx`: set DR of operator 3.
- only in 4-op mode (OPL3).
- `58xx`: set DR of operator 4.
- only in 4-op mode (OPL3).
- `5Bxy`: set KSR of operator.
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
- `y` determines whether KSR is on.

View file

@ -37,3 +37,27 @@ the YM2413 is equipped with the following features:
- `19xx`: set attack of all operators. - `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1. - `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2. - `1Bxx`: set attack of operator 2.
- `50xy`: set AM of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` is the value.
- `53xy`: set VIB of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` determines whether VIB is on.
- `54xy` set KSL of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set EGT of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` determines whether EGT is on.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `5Bxy`: set KSR of operator.
- `x` is the operator (1-2). a value of 0 means "all operators".
- `y` determines whether KSR is on.

100
papers/doc/7-systems/opz.md Normal file
View file

@ -0,0 +1,100 @@
# Yamaha OPZ (YM2414)
this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too.
it adds these features on top of the YM2151:
- 8 waveforms (but they're different from the OPL ones)
- per-channel (possibly) linear volume control separate from TL
- increased multiplier precision (in 1/16ths)
- 4-step envelope generator shift (minimum TL)
- another LFO
- no per-operator key on/off
- fixed frequency mode per operator (kind of like OPN family's extended channel mode but with a bit less precision and for all 8 channels)
- "reverb" effect (actually extends release)
unlike the YM2151, this chip is officially undocumented. very few efforts have been made to study the chip and document it...
therefore emulation of this chip in Furnace is incomplete and uncertain.
no plans have been made for TX81Z MIDI passthrough, because:
- Furnace works with register writes rather than MIDI commands
- the MIDI protocol is slow (would not be enough).
- the TX81Z is very slow to process a note on/off or parameter change event.
- the TL range has been reduced to 0-99, but the chip goes from 0-127.
# effects
- `10xx`: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it.
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `17xx`: set LFO speed.
- `18xx`: set LFO waveform. `xx` may be one of the following:
- `00`: saw
- `01`: square
- `02`: triangle
- `03`: noise
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `28xy`: set reverb of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `29xy`: set EG shift of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `2Axy`: set waveform of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `2Fxx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `3xyy`: set fixed frequency of operator 1/2.
- `x` is the block (0-7 for operator 1; 8-F for operator 2).
- `y` is the frequency. fixed frequency mode will be disabled if this is less than 8.
- the actual frequency is: `y*(2^x)`.
- `4xyy`: set fixed frequency of operator 3/4.
- `x` is the block (0-7 for operator 3; 8-F for operator 4).
- `y` is the frequency. fixed frequency mode will be disabled if this is less than 8.
- the actual frequency is: `y*(2^x)`.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set DT2 of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -26,3 +26,41 @@ it also was present on several pinball machines and synthesizers of the era, and
- `1Bxx`: set attack of operator 2. - `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3. - `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4. - `1Dxx`: set attack of operator 4.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set DT2 of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -57,3 +57,43 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different
- `x` is the numerator. - `x` is the numerator.
- `y` is the denominator. - `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode. - if `x` or `y` are 0 this will disable auto-envelope mode.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -56,3 +56,43 @@ it is backward compatible with the original chip.
- `x` is the numerator. - `x` is the numerator.
- `y` is the denominator. - `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode. - if `x` or `y` are 0 this will disable auto-envelope mode.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -25,3 +25,43 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operat
- `1Bxx`: set attack of operator 2. - `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3. - `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4. - `1Dxx`: set attack of operator 4.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -228,6 +228,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
break; break;
case DIV_SYSTEM_FDS: case DIV_SYSTEM_FDS:
dispatch=new DivPlatformFDS; dispatch=new DivPlatformFDS;
((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCore",0)==1);
break; break;
case DIV_SYSTEM_TIA: case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA; dispatch=new DivPlatformTIA;

View file

@ -20,11 +20,12 @@
#include "fds.h" #include "fds.h"
#include "sound/nes/cpu_inline.h" #include "sound/nes/cpu_inline.h"
#include "../engine.h" #include "../engine.h"
#include "sound/nes_nsfplay/nes_fds.h"
#include <math.h> #include <math.h>
#define CHIP_FREQBASE 262144 #define CHIP_FREQBASE 262144
#define rWrite(a,v) if (!skipRegisterWrites) {fds_wr_mem(fds,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
const char* regCheatSheetFDS[]={ const char* regCheatSheetFDS[]={
"IOCtrl", "4023", "IOCtrl", "4023",
@ -78,7 +79,7 @@ const char* DivPlatformFDS::getEffectName(unsigned char effect) {
return NULL; return NULL;
} }
void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivPlatformFDS::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t i=start; i<start+len; i++) { for (size_t i=start; i<start+len; i++) {
extcl_apu_tick_FDS(fds); extcl_apu_tick_FDS(fds);
int sample=isMuted[0]?0:fds->snd.main.output; int sample=isMuted[0]?0:fds->snd.main.output;
@ -92,6 +93,38 @@ void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len)
} }
} }
void DivPlatformFDS::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) {
int out[2];
for (size_t i=start; i<start+len; i++) {
fds_NP->Tick(1);
fds_NP->Render(out);
int sample=isMuted[0]?0:(out[0]<<1);
if (sample>32767) sample=32767;
if (sample<-32768) sample=-32768;
bufL[i]=sample;
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf->data[oscBuf->needle++]=sample<<1;
}
}
}
void DivPlatformFDS::doWrite(unsigned short addr, unsigned char data) {
if (useNP) {
fds_NP->Write(addr,data);
} else {
fds_wr_mem(fds,addr,data);
}
}
void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) {
if (useNP) {
acquire_NSFPlay(bufL,bufR,start,len);
} else {
acquire_puNES(bufL,bufR,start,len);
}
}
void DivPlatformFDS::updateWave() { void DivPlatformFDS::updateWave() {
// TODO: master volume // TODO: master volume
rWrite(0x4089,0x80); rWrite(0x4089,0x80);
@ -423,7 +456,11 @@ void DivPlatformFDS::reset() {
addWrite(0xffffffff,0); addWrite(0xffffffff,0);
} }
if (useNP) {
fds_NP->Reset();
} else {
fds_reset(fds); fds_reset(fds);
}
memset(regPool,0,128); memset(regPool,0,128);
rWrite(0x4023,0); rWrite(0x4023,0);
@ -435,6 +472,10 @@ bool DivPlatformFDS::keyOffAffectsArp(int ch) {
return true; return true;
} }
void DivPlatformFDS::setNSFPlay(bool use) {
useNP=use;
}
void DivPlatformFDS::setFlags(unsigned int flags) { void DivPlatformFDS::setFlags(unsigned int flags) {
if (flags==2) { // Dendy if (flags==2) { // Dendy
rate=COLOR_PAL*2.0/5.0; rate=COLOR_PAL*2.0/5.0;
@ -445,6 +486,10 @@ void DivPlatformFDS::setFlags(unsigned int flags) {
} }
chipClock=rate; chipClock=rate;
oscBuf->rate=rate/32; oscBuf->rate=rate/32;
if (useNP) {
fds_NP->SetClock(rate);
fds_NP->SetRate(rate);
}
} }
void DivPlatformFDS::notifyInsDeletion(void* ins) { void DivPlatformFDS::notifyInsDeletion(void* ins) {
@ -467,7 +512,11 @@ int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int f
dumpWrites=false; dumpWrites=false;
skipRegisterWrites=false; skipRegisterWrites=false;
writeOscBuf=0; writeOscBuf=0;
if (useNP) {
fds_NP=new xgm::NES_FDS;
} else {
fds=new struct _fds; fds=new struct _fds;
}
oscBuf=new DivDispatchOscBuffer; oscBuf=new DivDispatchOscBuffer;
for (int i=0; i<1; i++) { for (int i=0; i<1; i++) {
isMuted[i]=false; isMuted[i]=false;
@ -475,13 +524,17 @@ int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int f
setFlags(flags); setFlags(flags);
reset(); reset();
return 5; return 1;
} }
void DivPlatformFDS::quit() { void DivPlatformFDS::quit() {
delete oscBuf; delete oscBuf;
if (useNP) {
delete fds_NP;
} else {
delete fds; delete fds;
} }
}
DivPlatformFDS::~DivPlatformFDS() { DivPlatformFDS::~DivPlatformFDS() {
} }

View file

@ -24,6 +24,8 @@
#include "../macroInt.h" #include "../macroInt.h"
#include "../waveSynth.h" #include "../waveSynth.h"
#include "sound/nes_nsfplay/nes_fds.h"
class DivPlatformFDS: public DivDispatch { class DivPlatformFDS: public DivDispatch {
struct Channel { struct Channel {
int freq, baseFreq, pitch, pitch2, prevFreq, note, modFreq, ins; int freq, baseFreq, pitch, pitch2, prevFreq, note, modFreq, ins;
@ -69,13 +71,19 @@ class DivPlatformFDS: public DivDispatch {
DivWaveSynth ws; DivWaveSynth ws;
unsigned char apuType; unsigned char apuType;
unsigned char writeOscBuf; unsigned char writeOscBuf;
bool useNP;
struct _fds* fds; struct _fds* fds;
xgm::NES_FDS* fds_NP;
unsigned char regPool[128]; unsigned char regPool[128];
void updateWave(); void updateWave();
friend void putDispatchChan(void*,int,int); friend void putDispatchChan(void*,int,int);
void doWrite(unsigned short addr, unsigned char data);
void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len);
void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len);
public: public:
void acquire(short* bufL, short* bufR, size_t start, size_t len); void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c); int dispatch(DivCommand c);
@ -88,6 +96,7 @@ class DivPlatformFDS: public DivDispatch {
void tick(bool sysTick=true); void tick(bool sysTick=true);
void muteChannel(int ch, bool mute); void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch); bool keyOffAffectsArp(int ch);
void setNSFPlay(bool use);
void setFlags(unsigned int flags); void setFlags(unsigned int flags);
void notifyInsDeletion(void* ins); void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val); void poke(unsigned int addr, unsigned short val);

View file

@ -313,8 +313,6 @@ void DivPlatformNES::tick(bool sysTick) {
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
} }
if (chan[i].keyOn) { if (chan[i].keyOn) {
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
//rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
} }
if (chan[i].keyOff) { if (chan[i].keyOff) {
//rWrite(16+i*5+2,8); //rWrite(16+i*5+2,8);
@ -343,7 +341,7 @@ void DivPlatformNES::tick(bool sysTick) {
} }
// PCM // PCM
if (chan[4].freqChanged) { if (chan[4].freqChanged || chan[4].keyOn) {
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false); chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false);
if (chan[4].furnaceDac) { if (chan[4].furnaceDac) {
double off=1.0; double off=1.0;
@ -352,11 +350,27 @@ void DivPlatformNES::tick(bool sysTick) {
off=(double)s->centerRate/8363.0; off=(double)s->centerRate/8363.0;
} }
dacRate=MIN(chan[4].freq*off,32000); dacRate=MIN(chan[4].freq*off,32000);
if (dpcmMode && !skipRegisterWrites) { if (chan[4].keyOn) {
if (dpcmMode && !skipRegisterWrites && dacSample>=0 && dacSample<parent->song.sampleLen) {
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
if (dpcmLen>255) dpcmLen=255;
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(dacRate));
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
dpcmBank=dpcmAddr>>14;
}
} else {
if (dpcmMode) {
rWrite(0x4010,calcDPCMRate(dacRate)); rWrite(0x4010,calcDPCMRate(dacRate));
} }
if (dumpWrites) addWrite(0xffff0001,dacRate);
} }
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
}
if (chan[4].keyOn) chan[4].keyOn=false;
chan[4].freqChanged=false; chan[4].freqChanged=false;
} }
} }
@ -378,25 +392,13 @@ int DivPlatformNES::dispatch(DivCommand c) {
dacPos=0; dacPos=0;
dacPeriod=0; dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
} }
chan[c.chan].active=true; chan[c.chan].active=true;
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
chan[c.chan].furnaceDac=true; chan[c.chan].furnaceDac=true;
if (dpcmMode && !skipRegisterWrites) {
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
if (dpcmLen>255) dpcmLen=255;
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(chan[c.chan].baseFreq));
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
dpcmBank=dpcmAddr>>14;
}
} else { } else {
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
@ -492,7 +494,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
break; break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2); int destFreq=(c.chan==4)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2));
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value; chan[c.chan].baseFreq+=c.value;
@ -554,7 +556,11 @@ int DivPlatformNES::dispatch(DivCommand c) {
break; break;
case DIV_CMD_LEGATO: case DIV_CMD_LEGATO:
if (c.chan==3) break; if (c.chan==3) break;
if (c.chan==4) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false);
} else {
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].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].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
break; break;

View file

@ -21,6 +21,8 @@ NES_FDS::NES_FDS ()
sm[0] = 128; sm[0] = 128;
sm[1] = 128; sm[1] = 128;
mask=0;
Reset(); Reset();
} }

View file

@ -19,6 +19,7 @@
#include "su.h" #include "su.h"
#include "../engine.h" #include "../engine.h"
#include "../../ta-log.h"
#include <math.h> #include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v; //#define rWrite(a,v) pendingWrites[a]=v;
@ -119,7 +120,12 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
chan[i].std.next(); chan[i].std.next();
if (chan[i].std.vol.had) { if (chan[i].std.vol.had) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
if (ins->type==DIV_INS_AMIGA) {
chan[i].outVol=((chan[i].vol&127)*MIN(64,chan[i].std.vol.val))>>6;
} else {
chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol.val))>>7; chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol.val))>>7;
}
chWrite(i,0x02,chan[i].outVol); chWrite(i,0x02,chan[i].outVol);
} }
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
@ -178,12 +184,47 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample);
if (sample!=NULL) {
double off=0.25;
if (sample->centerRate<1) {
off=0.25;
} else {
off=(double)sample->centerRate/(8363.0*4.0);
}
chan[i].freq=(double)chan[i].freq*off;
}
}
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>65535) chan[i].freq=65535;
chWrite(i,0x00,chan[i].freq&0xff); chWrite(i,0x00,chan[i].freq&0xff);
chWrite(i,0x01,chan[i].freq>>8); chWrite(i,0x01,chan[i].freq>>8);
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) { if (chan[i].keyOn) {
//rWrite(16+i*5,0x80); if (chan[i].pcm) {
//chWrite(i,0x04,0x80|chan[i].vol); DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample);
if (sample!=NULL) {
unsigned int sampleEnd=sample->offSU+sample->samples;
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;
chWrite(i,0x0a,sample->offSU&0xff);
chWrite(i,0x0b,sample->offSU>>8);
chWrite(i,0x0c,sampleEnd&0xff);
chWrite(i,0x0d,sampleEnd>>8);
if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) {
unsigned int sampleLoop=sample->offSU+sample->loopStart;
if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1;
chWrite(i,0x0e,sampleLoop&0xff);
chWrite(i,0x0f,sampleLoop>>8);
chan[i].pcmLoop=true;
} else {
chan[i].pcmLoop=false;
}
writeControl(i);
writeControlUpper(i);
}
}
} }
if (chan[i].keyOff) { if (chan[i].keyOff) {
chWrite(i,0x02,0); chWrite(i,0x02,0);
@ -199,6 +240,11 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) {
writeControl(c.chan);
writeControlUpper(c.chan);
}
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
@ -247,7 +293,6 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
break; break;
case DIV_CMD_WAVE: case DIV_CMD_WAVE:
chan[c.chan].wave=c.value; chan[c.chan].wave=c.value;
chan[c.chan].keyOn=true;
break; break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2); int destFreq=NOTE_FREQUENCY(c.value2);
@ -385,6 +430,42 @@ void DivPlatformSoundUnit::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
} }
const void* DivPlatformSoundUnit::getSampleMem(int index) {
return (index==0)?su->pcm:NULL;
}
size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) {
return (index==0)?8192:0;
}
size_t DivPlatformSoundUnit::getSampleMemUsage(int index) {
return (index==0)?sampleMemLen:0;
}
void DivPlatformSoundUnit::renderSamples() {
memset(su->pcm,0,getSampleMemCapacity(0));
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int paddedLen=s->samples;
if (memPos>=getSampleMemCapacity(0)) {
logW("out of PCM memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
memcpy(su->pcm+memPos,s->data8,getSampleMemCapacity(0)-memPos);
logW("out of PCM memory for sample %d!",i);
} else {
memcpy(su->pcm+memPos,s->data8,paddedLen);
}
s->offSU=memPos;
memPos+=paddedLen;
}
sampleMemLen=memPos;
}
int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p; parent=p;
dumpWrites=false; dumpWrites=false;

View file

@ -86,6 +86,7 @@ class DivPlatformSoundUnit: public DivDispatch {
short tempR; short tempR;
unsigned char sampleBank, lfoMode, lfoSpeed; unsigned char sampleBank, lfoMode, lfoSpeed;
SoundUnit* su; SoundUnit* su;
size_t sampleMemLen;
unsigned char regPool[128]; unsigned char regPool[128];
void writeControl(int ch); void writeControl(int ch);
void writeControlUpper(int ch); void writeControlUpper(int ch);
@ -110,6 +111,10 @@ class DivPlatformSoundUnit: public DivDispatch {
void poke(std::vector<DivRegWrite>& wlist); void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet(); const char** getRegisterSheet();
const char* getEffectName(unsigned char effect); const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit(); void quit();
~DivPlatformSoundUnit(); ~DivPlatformSoundUnit();

View file

@ -86,7 +86,7 @@ struct DivSample {
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010; unsigned int offSegaPCM, offQSound, offX1_010, offSU;
unsigned int samples; unsigned int samples;
@ -247,6 +247,7 @@ struct DivSample {
offSegaPCM(0), offSegaPCM(0),
offQSound(0), offQSound(0),
offX1_010(0), offX1_010(0),
offSU(0),
samples(0) {} samples(0) {}
~DivSample(); ~DivSample();
}; };

View file

@ -457,7 +457,7 @@ struct DivSong {
system[0]=DIV_SYSTEM_YM2612; system[0]=DIV_SYSTEM_YM2612;
system[1]=DIV_SYSTEM_SMS; system[1]=DIV_SYSTEM_SMS;
nullInsOPLL.fm.opllPreset=7; nullInsOPLL.fm.opllPreset=0;
nullInsOPLL.fm.op[1].tl=0; nullInsOPLL.fm.op[1].tl=0;
nullInsOPLL.name="This is a bug! Report!"; nullInsOPLL.name="This is a bug! Report!";

View file

@ -179,6 +179,7 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("offSegaPCM: %x",sample->offSegaPCM); ImGui::Text("offSegaPCM: %x",sample->offSegaPCM);
ImGui::Text("offQSound: %x",sample->offQSound); ImGui::Text("offQSound: %x",sample->offQSound);
ImGui::Text("offX1_010: %x",sample->offX1_010); ImGui::Text("offX1_010: %x",sample->offX1_010);
ImGui::Text("offSU: %x",sample->offSU);
ImGui::Text("samples: %d",sample->samples); ImGui::Text("samples: %d",sample->samples);
ImGui::TreePop(); ImGui::TreePop();

View file

@ -789,6 +789,7 @@ class FurnaceGUI {
int ym2612Core; int ym2612Core;
int saaCore; int saaCore;
int nesCore; int nesCore;
int fdsCore;
int mainFont; int mainFont;
int patFont; int patFont;
int audioRate; int audioRate;
@ -872,6 +873,7 @@ class FurnaceGUI {
ym2612Core(0), ym2612Core(0),
saaCore(1), saaCore(1),
nesCore(0), nesCore(0),
fdsCore(0),
mainFont(0), mainFont(0),
patFont(0), patFont(0),
audioRate(44100), audioRate(44100),

View file

@ -868,6 +868,10 @@ void FurnaceGUI::drawSettings() {
ImGui::SameLine(); ImGui::SameLine();
ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2); ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2);
ImGui::Text("FDS core");
ImGui::SameLine();
ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2);
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ImGui::BeginTabItem("Appearance")) { if (ImGui::BeginTabItem("Appearance")) {
@ -1741,6 +1745,7 @@ void FurnaceGUI::syncSettings() {
settings.ym2612Core=e->getConfInt("ym2612Core",0); settings.ym2612Core=e->getConfInt("ym2612Core",0);
settings.saaCore=e->getConfInt("saaCore",1); settings.saaCore=e->getConfInt("saaCore",1);
settings.nesCore=e->getConfInt("nesCore",0); settings.nesCore=e->getConfInt("nesCore",0);
settings.fdsCore=e->getConfInt("fdsCore",0);
settings.mainFont=e->getConfInt("mainFont",0); settings.mainFont=e->getConfInt("mainFont",0);
settings.patFont=e->getConfInt("patFont",0); settings.patFont=e->getConfInt("patFont",0);
settings.mainFontPath=e->getConfString("mainFontPath",""); settings.mainFontPath=e->getConfString("mainFontPath","");
@ -1816,6 +1821,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.ym2612Core,0,1); clampSetting(settings.ym2612Core,0,1);
clampSetting(settings.saaCore,0,1); clampSetting(settings.saaCore,0,1);
clampSetting(settings.nesCore,0,1); clampSetting(settings.nesCore,0,1);
clampSetting(settings.fdsCore,0,1);
clampSetting(settings.mainFont,0,6); clampSetting(settings.mainFont,0,6);
clampSetting(settings.patFont,0,6); clampSetting(settings.patFont,0,6);
clampSetting(settings.patRowsBase,0,1); clampSetting(settings.patRowsBase,0,1);
@ -1919,6 +1925,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("ym2612Core",settings.ym2612Core); e->setConf("ym2612Core",settings.ym2612Core);
e->setConf("saaCore",settings.saaCore); e->setConf("saaCore",settings.saaCore);
e->setConf("nesCore",settings.nesCore); e->setConf("nesCore",settings.nesCore);
e->setConf("fdsCore",settings.fdsCore);
e->setConf("mainFont",settings.mainFont); e->setConf("mainFont",settings.mainFont);
e->setConf("patFont",settings.patFont); e->setConf("patFont",settings.patFont);
e->setConf("mainFontPath",settings.mainFontPath); e->setConf("mainFontPath",settings.mainFontPath);