mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-27 06:53:01 +00:00
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:
commit
2412f688de
19 changed files with 519 additions and 35 deletions
|
@ -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.
|
|
@ -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
100
papers/doc/7-systems/opz.md
Normal 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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -21,6 +21,8 @@ NES_FDS::NES_FDS ()
|
|||
sm[0] = 128;
|
||||
sm[1] = 128;
|
||||
|
||||
mask=0;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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!";
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue