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).
- `1Dxx`: set attack of operator 4.
- 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.
- `1Axx`: set attack of operator 1.
- `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.
- `1Cxx`: set attack of operator 3.
- `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.
- `y` is the denominator.
- 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.
- `y` is the denominator.
- 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.
- `1Cxx`: set attack of operator 3.
- `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;
case DIV_SYSTEM_FDS:
dispatch=new DivPlatformFDS;
((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCore",0)==1);
break;
case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA;

View file

@ -20,11 +20,12 @@
#include "fds.h"
#include "sound/nes/cpu_inline.h"
#include "../engine.h"
#include "sound/nes_nsfplay/nes_fds.h"
#include <math.h>
#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[]={
"IOCtrl", "4023",
@ -78,7 +79,7 @@ const char* DivPlatformFDS::getEffectName(unsigned char effect) {
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++) {
extcl_apu_tick_FDS(fds);
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() {
// TODO: master volume
rWrite(0x4089,0x80);
@ -423,7 +456,11 @@ void DivPlatformFDS::reset() {
addWrite(0xffffffff,0);
}
if (useNP) {
fds_NP->Reset();
} else {
fds_reset(fds);
}
memset(regPool,0,128);
rWrite(0x4023,0);
@ -435,6 +472,10 @@ bool DivPlatformFDS::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformFDS::setNSFPlay(bool use) {
useNP=use;
}
void DivPlatformFDS::setFlags(unsigned int flags) {
if (flags==2) { // Dendy
rate=COLOR_PAL*2.0/5.0;
@ -445,6 +486,10 @@ void DivPlatformFDS::setFlags(unsigned int flags) {
}
chipClock=rate;
oscBuf->rate=rate/32;
if (useNP) {
fds_NP->SetClock(rate);
fds_NP->SetRate(rate);
}
}
void DivPlatformFDS::notifyInsDeletion(void* ins) {
@ -467,7 +512,11 @@ int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int f
dumpWrites=false;
skipRegisterWrites=false;
writeOscBuf=0;
if (useNP) {
fds_NP=new xgm::NES_FDS;
} else {
fds=new struct _fds;
}
oscBuf=new DivDispatchOscBuffer;
for (int i=0; i<1; i++) {
isMuted[i]=false;
@ -475,12 +524,16 @@ int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int f
setFlags(flags);
reset();
return 5;
return 1;
}
void DivPlatformFDS::quit() {
delete oscBuf;
if (useNP) {
delete fds_NP;
} else {
delete fds;
}
}
DivPlatformFDS::~DivPlatformFDS() {

View file

@ -24,6 +24,8 @@
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/nes_nsfplay/nes_fds.h"
class DivPlatformFDS: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, prevFreq, note, modFreq, ins;
@ -69,13 +71,19 @@ class DivPlatformFDS: public DivDispatch {
DivWaveSynth ws;
unsigned char apuType;
unsigned char writeOscBuf;
bool useNP;
struct _fds* fds;
xgm::NES_FDS* fds_NP;
unsigned char regPool[128];
void updateWave();
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:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
@ -88,6 +96,7 @@ class DivPlatformFDS: public DivDispatch {
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
void setNSFPlay(bool use);
void setFlags(unsigned int flags);
void notifyInsDeletion(void* ins);
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].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) {
//rWrite(16+i*5+2,8);
@ -343,7 +341,7 @@ void DivPlatformNES::tick(bool sysTick) {
}
// PCM
if (chan[4].freqChanged) {
if (chan[4].freqChanged || chan[4].keyOn) {
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false);
if (chan[4].furnaceDac) {
double off=1.0;
@ -352,11 +350,27 @@ void DivPlatformNES::tick(bool sysTick) {
off=(double)s->centerRate/8363.0;
}
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));
}
if (dumpWrites) addWrite(0xffff0001,dacRate);
}
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
}
if (chan[4].keyOn) chan[4].keyOn=false;
chan[4].freqChanged=false;
}
}
@ -378,25 +392,13 @@ int DivPlatformNES::dispatch(DivCommand c) {
dacPos=0;
dacPeriod=0;
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].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=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 {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
@ -492,7 +494,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true;
break;
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;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
@ -554,7 +556,11 @@ int DivPlatformNES::dispatch(DivCommand c) {
break;
case DIV_CMD_LEGATO:
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].freqChanged=true;
chan[c.chan].note=c.value;
break;

View file

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

View file

@ -19,6 +19,7 @@
#include "su.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
@ -119,7 +120,12 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
for (int i=0; i<8; i++) {
chan[i].std.next();
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;
}
chWrite(i,0x02,chan[i].outVol);
}
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) {
//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);
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,0x01,chan[i].freq>>8);
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {
//rWrite(16+i*5,0x80);
//chWrite(i,0x04,0x80|chan[i].vol);
if (chan[i].pcm) {
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) {
chWrite(i,0x02,0);
@ -199,6 +240,11 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
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) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
@ -247,7 +293,6 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
chan[c.chan].keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
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);
}
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) {
parent=p;
dumpWrites=false;

View file

@ -86,6 +86,7 @@ class DivPlatformSoundUnit: public DivDispatch {
short tempR;
unsigned char sampleBank, lfoMode, lfoSpeed;
SoundUnit* su;
size_t sampleMemLen;
unsigned char regPool[128];
void writeControl(int ch);
void writeControlUpper(int ch);
@ -110,6 +111,10 @@ class DivPlatformSoundUnit: public DivDispatch {
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
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);
void quit();
~DivPlatformSoundUnit();

View file

@ -86,7 +86,7 @@ struct DivSample {
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 offSegaPCM, offQSound, offX1_010;
unsigned int offSegaPCM, offQSound, offX1_010, offSU;
unsigned int samples;
@ -247,6 +247,7 @@ struct DivSample {
offSegaPCM(0),
offQSound(0),
offX1_010(0),
offSU(0),
samples(0) {}
~DivSample();
};

View file

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

View file

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

View file

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

View file

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