mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-26 22:43:01 +00:00
Merge branch 'cps2' of https://github.com/superctr/furnace into superctr-cps2
This commit is contained in:
commit
e6ee28ce0d
18 changed files with 1775 additions and 17 deletions
|
@ -274,6 +274,8 @@ src/engine/platform/sound/ymfm/ymfm_ssg.cpp
|
|||
|
||||
src/engine/platform/sound/lynx/Mikey.cpp
|
||||
|
||||
src/engine/platform/sound/qsound.c
|
||||
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
||||
src/engine/blip_buf.c
|
||||
|
@ -308,6 +310,7 @@ src/engine/platform/ay8930.cpp
|
|||
src/engine/platform/tia.cpp
|
||||
src/engine/platform/saa.cpp
|
||||
src/engine/platform/amiga.cpp
|
||||
src/engine/platform/qsound.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
src/engine/platform/lynx.cpp
|
||||
)
|
||||
|
|
18
papers/doc/7-systems/qsound.md
Normal file
18
papers/doc/7-systems/qsound.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Capcom QSound (DL-1425)
|
||||
|
||||
This chip was used in Capcom's CP System Dash, CP System II and ZN arcade PCBs.
|
||||
|
||||
It supports 16 PCM channels and uses the patented (now expired) QSound stereo expansion algorithm, as the name implies.
|
||||
|
||||
Because the chip lacks sample interpolation, it's recommended that you try to play samples at around 24038 Hz to avoid aliasing. This is especially important for e.g. cymbals.
|
||||
|
||||
The QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. It is however possible to adjust the feedback and length of the echo buffer. The initial values can be set in the "Configure system" dialog.
|
||||
|
||||
There are also 3 ADPCM channels, however they cannot be used in Furnace yet. They have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz.
|
||||
|
||||
# effects
|
||||
|
||||
- `08xx`: Set panning. Valid range is 00-20. 00 for full left, 10 for center and 20 for full right. It is also possible to bypass the QSound algorithm by using the range 30-50.
|
||||
- `10xx`: Set echo feedback level. This effect will apply to all channels.
|
||||
- `11xx`: Set echo level.
|
||||
- `3xxx`: Set the length of the echo delay buffer.
|
|
@ -98,6 +98,10 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_SAA_ENVELOPE,
|
||||
|
||||
DIV_CMD_LYNX_LFSR_LOAD,
|
||||
|
||||
DIV_CMD_QSOUND_ECHO_FEEDBACK,
|
||||
DIV_CMD_QSOUND_ECHO_DELAY,
|
||||
DIV_CMD_QSOUND_ECHO_LEVEL,
|
||||
|
||||
DIV_ALWAYS_SET_VOLUME,
|
||||
|
||||
|
@ -219,6 +223,13 @@ class DivDispatch {
|
|||
*/
|
||||
virtual int getRegisterPoolSize();
|
||||
|
||||
/**
|
||||
* get the bit depth of the register pool of this dispatch.
|
||||
* If the result is 16, it should be casted to unsigned short
|
||||
* @return the depth. Default value is 8
|
||||
*/
|
||||
virtual int getRegisterPoolDepth();
|
||||
|
||||
/**
|
||||
* get this dispatch's state. DO NOT IMPLEMENT YET.
|
||||
* @return a pointer to the dispatch's state. must be deallocated manually!
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "platform/tia.h"
|
||||
#include "platform/saa.h"
|
||||
#include "platform/amiga.h"
|
||||
#include "platform/qsound.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "../ta-log.h"
|
||||
|
@ -201,7 +202,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
}
|
||||
case DIV_SYSTEM_LYNX:
|
||||
dispatch = new DivPlatformLynx;
|
||||
dispatch=new DivPlatformLynx;
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
dispatch=new DivPlatformQSound;
|
||||
break;
|
||||
default:
|
||||
logW("this system is not supported yet! using dummy platform.\n");
|
||||
|
|
|
@ -597,6 +597,41 @@ void DivEngine::renderSamples() {
|
|||
memPos+=s->adpcmRendLength;
|
||||
}
|
||||
adpcmMemLen=memPos+256;
|
||||
|
||||
// step 4: allocate qsound pcm samples
|
||||
if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216];
|
||||
|
||||
memset(qsoundMem, 0, 16777216);
|
||||
|
||||
memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
int length = s->rendLength;
|
||||
if(length > 65536-16)
|
||||
length = 65536-16;
|
||||
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
|
||||
memPos=(memPos+0xffff)&0xff0000;
|
||||
}
|
||||
if (memPos>=16777216) {
|
||||
logW("out of QSound PCM memory for sample %d!\n",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+length>=16777216) {
|
||||
for(unsigned int i=0; i<16777216-(memPos+length); i++)
|
||||
{
|
||||
qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0);
|
||||
}
|
||||
logW("out of QSound PCM memory for sample %d!\n",i);
|
||||
} else {
|
||||
for(int i=0; i<length; i++)
|
||||
{
|
||||
qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0);
|
||||
}
|
||||
}
|
||||
s->rendOffQsound=memPos ^ 0x8000;
|
||||
memPos+=length+16;
|
||||
}
|
||||
qsoundMemLen=memPos+256;
|
||||
}
|
||||
|
||||
void DivEngine::createNew() {
|
||||
|
@ -729,10 +764,11 @@ void* DivEngine::getDispatchChanState(int ch) {
|
|||
return disCont[dispatchOfChan[ch]].dispatch->getChanState(dispatchChanOfChan[ch]);
|
||||
}
|
||||
|
||||
unsigned char* DivEngine::getRegisterPool(int sys, int& size) {
|
||||
unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) {
|
||||
if (sys<0 || sys>=song.systemLen) return NULL;
|
||||
if (disCont[sys].dispatch==NULL) return NULL;
|
||||
size=disCont[sys].dispatch->getRegisterPoolSize();
|
||||
depth=disCont[sys].dispatch->getRegisterPoolDepth();
|
||||
return disCont[sys].dispatch->getRegisterPool();
|
||||
}
|
||||
|
||||
|
@ -1887,7 +1923,7 @@ bool DivEngine::addSampleFromFile(const char* path) {
|
|||
delete[] buf;
|
||||
sample->rate=si.samplerate;
|
||||
if (sample->rate<4000) sample->rate=4000;
|
||||
if (sample->rate>32000) sample->rate=32000;
|
||||
if (sample->rate>96000) sample->rate=96000;
|
||||
|
||||
song.sample.push_back(sample);
|
||||
song.sampleLen=sampleCount+1;
|
||||
|
|
|
@ -531,7 +531,7 @@ class DivEngine {
|
|||
void* getDispatchChanState(int chan);
|
||||
|
||||
// get register pool
|
||||
unsigned char* getRegisterPool(int sys, int& size);
|
||||
unsigned char* getRegisterPool(int sys, int& size, int& depth);
|
||||
|
||||
// enable command stream dumping
|
||||
void enableCommandStream(bool enable);
|
||||
|
@ -624,6 +624,8 @@ class DivEngine {
|
|||
size_t adpcmMemLen;
|
||||
unsigned char* adpcmBMem;
|
||||
size_t adpcmBMemLen;
|
||||
unsigned char* qsoundMem;
|
||||
size_t qsoundMemLen;
|
||||
|
||||
DivEngine():
|
||||
output(NULL),
|
||||
|
@ -681,6 +683,8 @@ class DivEngine {
|
|||
adpcmMem(NULL),
|
||||
adpcmMemLen(0),
|
||||
adpcmBMem(NULL),
|
||||
adpcmBMemLen(0) {}
|
||||
adpcmBMemLen(0),
|
||||
qsoundMem(NULL),
|
||||
qsoundMemLen(0) {}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -956,7 +956,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
// while version 32 stored this value, it was unused.
|
||||
if (ds.version>=38) {
|
||||
sample->centerRate=reader.readS();
|
||||
sample->centerRate=(unsigned short) reader.readS();
|
||||
} else {
|
||||
reader.readS();
|
||||
}
|
||||
|
|
|
@ -37,6 +37,10 @@ int DivDispatch::getRegisterPoolSize() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int DivDispatch::getRegisterPoolDepth() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
void* DivDispatch::getState() {
|
||||
return NULL;
|
||||
}
|
||||
|
|
625
src/engine/platform/qsound.cpp
Normal file
625
src/engine/platform/qsound.cpp
Normal file
|
@ -0,0 +1,625 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "qsound.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
#include <map>
|
||||
|
||||
#define CHIP_DIVIDER (1248*2)
|
||||
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false)
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }}
|
||||
#define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);}
|
||||
|
||||
const char* regCheatSheetQSound[]={
|
||||
"Ch15_Bank", "00",
|
||||
"Ch00_Start", "01",
|
||||
"Ch00_Freq", "02",
|
||||
"Ch00_Phase", "03",
|
||||
"Ch00_Loop", "04",
|
||||
"Ch00_End", "05",
|
||||
"Ch00_Volume", "06",
|
||||
"Ch00_Bank", "08",
|
||||
"Ch01_Start", "09",
|
||||
"Ch01_Freq", "0A",
|
||||
"Ch01_Phase", "0B",
|
||||
"Ch01_Loop", "0C",
|
||||
"Ch01_End", "0D",
|
||||
"Ch01_Volume", "0E",
|
||||
"Ch01_Bank", "10",
|
||||
"Ch02_Start", "11",
|
||||
"Ch02_Freq", "12",
|
||||
"Ch02_Phase", "13",
|
||||
"Ch02_Loop", "14",
|
||||
"Ch02_End", "15",
|
||||
"Ch02_Volume", "16",
|
||||
"Ch02_Bank", "18",
|
||||
"Ch03_Start", "19",
|
||||
"Ch03_Freq", "1A",
|
||||
"Ch03_Phase", "1B",
|
||||
"Ch03_Loop", "1C",
|
||||
"Ch03_End", "1D",
|
||||
"Ch03_Volume", "1E",
|
||||
"Ch03_Bank", "20",
|
||||
"Ch04_Start", "21",
|
||||
"Ch04_Freq", "22",
|
||||
"Ch04_Phase", "23",
|
||||
"Ch04_Loop", "24",
|
||||
"Ch04_End", "25",
|
||||
"Ch04_Volume", "26",
|
||||
"Ch04_Bank", "28",
|
||||
"Ch05_Start", "29",
|
||||
"Ch05_Freq", "2A",
|
||||
"Ch05_Phase", "2B",
|
||||
"Ch05_Loop", "2C",
|
||||
"Ch05_End", "2D",
|
||||
"Ch05_Volume", "2E",
|
||||
"Ch05_Bank", "30",
|
||||
"Ch06_Start", "31",
|
||||
"Ch06_Freq", "32",
|
||||
"Ch06_Phase", "33",
|
||||
"Ch06_Loop", "34",
|
||||
"Ch06_End", "35",
|
||||
"Ch06_Volume", "36",
|
||||
"Ch06_Bank", "38",
|
||||
"Ch07_Start", "39",
|
||||
"Ch07_Freq", "3A",
|
||||
"Ch07_Phase", "3B",
|
||||
"Ch07_Loop", "3C",
|
||||
"Ch07_End", "3D",
|
||||
"Ch07_Volume", "3E",
|
||||
"Ch07_Bank", "40",
|
||||
"Ch08_Start", "41",
|
||||
"Ch08_Freq", "42",
|
||||
"Ch08_Phase", "43",
|
||||
"Ch08_Loop", "44",
|
||||
"Ch08_End", "45",
|
||||
"Ch08_Volume", "46",
|
||||
"Ch08_Bank", "48",
|
||||
"Ch09_Start", "49",
|
||||
"Ch09_Freq", "4A",
|
||||
"Ch09_Phase", "4B",
|
||||
"Ch09_Loop", "4C",
|
||||
"Ch09_End", "4D",
|
||||
"Ch09_Volume", "4E",
|
||||
"Ch09_Bank", "50",
|
||||
"Ch10_Start", "51",
|
||||
"Ch10_Freq", "52",
|
||||
"Ch10_Phase", "53",
|
||||
"Ch10_Loop", "54",
|
||||
"Ch10_End", "55",
|
||||
"Ch10_Volume", "56",
|
||||
"Ch10_Bank", "58",
|
||||
"Ch11_Start", "59",
|
||||
"Ch11_Freq", "5A",
|
||||
"Ch11_Phase", "5B",
|
||||
"Ch11_Loop", "5C",
|
||||
"Ch11_End", "5D",
|
||||
"Ch11_Volume", "5E",
|
||||
"Ch11_Bank", "60",
|
||||
"Ch12_Start", "61",
|
||||
"Ch12_Freq", "62",
|
||||
"Ch12_Phase", "63",
|
||||
"Ch12_Loop", "64",
|
||||
"Ch12_End", "65",
|
||||
"Ch12_Volume", "66",
|
||||
"Ch12_Bank", "68",
|
||||
"Ch13_Start", "69",
|
||||
"Ch13_Freq", "6A",
|
||||
"Ch13_Phase", "6B",
|
||||
"Ch13_Loop", "6C",
|
||||
"Ch13_End", "6D",
|
||||
"Ch13_Volume", "6E",
|
||||
"Ch13_Bank", "70",
|
||||
"Ch14_Start", "71",
|
||||
"Ch14_Freq", "72",
|
||||
"Ch14_Phase", "73",
|
||||
"Ch14_Loop", "74",
|
||||
"Ch14_End", "75",
|
||||
"Ch14_Volume", "76",
|
||||
"Ch14_Bank", "78",
|
||||
"Ch15_Start", "79",
|
||||
"Ch15_Freq", "7A",
|
||||
"Ch15_Phase", "7B",
|
||||
"Ch15_Loop", "7C",
|
||||
"Ch15_End", "7D",
|
||||
"Ch15_Volume", "7E",
|
||||
"Ch00_Panning", "80",
|
||||
"Ch01_Panning", "81",
|
||||
"Ch02_Panning", "82",
|
||||
"Ch03_Panning", "83",
|
||||
"Ch04_Panning", "84",
|
||||
"Ch05_Panning", "85",
|
||||
"Ch06_Panning", "86",
|
||||
"Ch07_Panning", "87",
|
||||
"Ch08_Panning", "88",
|
||||
"Ch09_Panning", "89",
|
||||
"Ch10_Panning", "8A",
|
||||
"Ch11_Panning", "8B",
|
||||
"Ch12_Panning", "8C",
|
||||
"Ch13_Panning", "8D",
|
||||
"Ch14_Panning", "8E",
|
||||
"Ch15_Panning", "8F",
|
||||
"Adpcm0_Panning","90",
|
||||
"Adpcm1_Panning","91",
|
||||
"Adpcm2_Panning","92",
|
||||
"Echo_Feedback","93",
|
||||
"Ch00_Echo", "BA",
|
||||
"Ch01_Echo", "BB",
|
||||
"Ch02_Echo", "BC",
|
||||
"Ch03_Echo", "BD",
|
||||
"Ch04_Echo", "BE",
|
||||
"Ch05_Echo", "BF",
|
||||
"Ch06_Echo", "C0",
|
||||
"Ch07_Echo", "C1",
|
||||
"Ch08_Echo", "C2",
|
||||
"Ch09_Echo", "C3",
|
||||
"Ch10_Echo", "C4",
|
||||
"Ch11_Echo", "C5",
|
||||
"Ch12_Echo", "C6",
|
||||
"Ch13_Echo", "C7",
|
||||
"Ch14_Echo", "C8",
|
||||
"Ch15_Echo", "C9",
|
||||
"Adpcm0_Start", "CA",
|
||||
"Adpcm0_End", "CB",
|
||||
"Adpcm0_Bank", "CC",
|
||||
"Adpcm0_Volume","CD",
|
||||
"Adpcm1_Start", "CE",
|
||||
"Adpcm1_End", "CF",
|
||||
"Adpcm1_Bank", "D0",
|
||||
"Adpcm1_Volume","D1",
|
||||
"Adpcm2_Start", "D2",
|
||||
"Adpcm2_End", "D3",
|
||||
"Adpcm2_Bank", "D4",
|
||||
"Adpcm2_Volume","D5",
|
||||
"Adpcm0_KeyOn", "D6",
|
||||
"Adpcm1_KeyOn", "D7",
|
||||
"Adpcm2_KeyOn", "D8",
|
||||
"Echo_Delay", "D9",
|
||||
"L_Wet_Filter", "DA",
|
||||
"L_Dry_Filter", "DB",
|
||||
"R_Wet_Filter", "DC",
|
||||
"R_Dry_Filter", "DD",
|
||||
"L_Wet_Delay", "DE",
|
||||
"L_Dry_Delay", "DF",
|
||||
"R_Wet_Delay", "E0",
|
||||
"R_Dry_Delay", "E1",
|
||||
"Delay_Flag", "E2",
|
||||
"Mode_Select", "E3", //valid: 0000,0288,0039,061A,004F
|
||||
"L_Wet_Volume", "E4",
|
||||
"L_Dry_Volume", "E5",
|
||||
"R_Wet_Volume", "E6",
|
||||
"R_Dry_Volume", "E7",
|
||||
NULL
|
||||
};
|
||||
enum q1_register_name {
|
||||
Q1V_BANK = 0,
|
||||
Q1V_START = 1,
|
||||
Q1V_FREQ = 2,
|
||||
Q1V_PHASE = 3,
|
||||
Q1V_LOOP = 4,
|
||||
Q1V_END = 5,
|
||||
Q1V_VOL = 6,
|
||||
Q1V_REG_COUNT = 7,
|
||||
|
||||
Q1_PAN = 0x80,
|
||||
Q1_ECHO = 0xba,
|
||||
|
||||
Q1A_PAN = 0x90,
|
||||
Q1A_START = 0xca,
|
||||
Q1A_END = 0xcb,
|
||||
Q1A_BANK = 0xcc,
|
||||
Q1A_VOL = 0xcd,
|
||||
|
||||
Q1A_KEYON = 0xd6,
|
||||
|
||||
Q1_ECHO_FEEDBACK = 0x93,
|
||||
Q1_ECHO_LENGTH = 0xd9,
|
||||
};
|
||||
|
||||
const unsigned char q1_reg_map[Q1V_REG_COUNT][16] = {
|
||||
{0x78,0x00,0x08,0x10,0x18,0x20,0x28,0x30,0x38,0x40,0x48,0x50,0x58,0x60,0x68,0x70},
|
||||
{0x01,0x09,0x11,0x19,0x21,0x29,0x31,0x39,0x41,0x49,0x51,0x59,0x61,0x69,0x71,0x79},
|
||||
{0x02,0x0a,0x12,0x1a,0x22,0x2a,0x32,0x3a,0x42,0x4a,0x52,0x5a,0x62,0x6a,0x72,0x7a},
|
||||
{0x03,0x0b,0x13,0x1b,0x23,0x2b,0x33,0x3b,0x43,0x4b,0x53,0x5b,0x63,0x6b,0x73,0x7b},
|
||||
{0x04,0x0c,0x14,0x1c,0x24,0x2c,0x34,0x3c,0x44,0x4c,0x54,0x5c,0x64,0x6c,0x74,0x7c},
|
||||
{0x05,0x0d,0x15,0x1d,0x25,0x2d,0x35,0x3d,0x45,0x4d,0x55,0x5d,0x65,0x6d,0x75,0x7d},
|
||||
{0x06,0x0e,0x16,0x1e,0x26,0x2e,0x36,0x3e,0x46,0x4e,0x56,0x5e,0x66,0x6e,0x76,0x7e},
|
||||
};
|
||||
|
||||
const char** DivPlatformQSound::getRegisterSheet() {
|
||||
return regCheatSheetQSound;
|
||||
}
|
||||
|
||||
const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set echo feedback level (00 to FF)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set channel echo level (00 to FF)";
|
||||
break;
|
||||
default:
|
||||
if((effect & 0xf0) == 0x30)
|
||||
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
chip.rom_data = parent->qsoundMem;
|
||||
chip.rom_mask = 0xffffff;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
qsound_update(&chip);
|
||||
bufL[h]=chip.out[0];
|
||||
bufR[h]=chip.out[1];
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformQSound::tick() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
chan[i].outVol=((chan[i].vol%256)*MIN(255,chan[i].std.vol << 2))>>8;
|
||||
// Check if enabled and write volume
|
||||
if(chan[i].active)
|
||||
{
|
||||
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].outVol << 5);
|
||||
//logW("ch %d vol=%04x (hadVol)!\n",i,chan[i].outVol << 5);
|
||||
}
|
||||
}
|
||||
uint16_t qsound_bank = 0;
|
||||
uint16_t qsound_addr = 0;
|
||||
uint16_t qsound_loop = 0;
|
||||
uint16_t qsound_end = 0;
|
||||
double off=1.0;
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->song.sample[chan[i].sample];
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
qsound_bank = 0x8000 | (s->rendOffQsound >> 16);
|
||||
qsound_addr = s->rendOffQsound & 0xffff;
|
||||
|
||||
int length = s->length;
|
||||
if(length > 65536 - 16)
|
||||
length = 65536 - 16;
|
||||
if(s->loopStart == -1 || s->loopStart >= length)
|
||||
{
|
||||
qsound_end = s->rendOffQsound + length + 15;
|
||||
qsound_loop = 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
qsound_end = s->rendOffQsound + length;
|
||||
qsound_loop = length - s->loopStart;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
//if (chan[i].note>0x5d) chan[i].freq=0x01; //????
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
rWrite(q1_reg_map[Q1V_END][i], qsound_end);
|
||||
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
|
||||
rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
|
||||
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
|
||||
//logW("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!\n",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
|
||||
// Write sample address. Enable volume
|
||||
if (!chan[i].std.hadVol) {
|
||||
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 5);
|
||||
//logW("ch %d vol=%04x (!hadVol)!\n",i,chan[i].vol << 5);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
rWrite(q1_reg_map[Q1V_VOL][i], 0);
|
||||
rWrite(q1_reg_map[Q1V_FREQ][i], 0);
|
||||
// Disable volume
|
||||
}
|
||||
else if (chan[i].active) {
|
||||
//logW("ch %d frequency set to %04x, off=%f, note=%d, %04x!\n",i,chan[i].freq,off,chan[i].note,QS_NOTE_FREQUENCY(chan[i].note));
|
||||
rWrite(q1_reg_map[Q1V_FREQ][i], chan[i].freq);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformQSound::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->song.sample[chan[c.chan].sample];
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.hasVol) {
|
||||
// Check if enabled and write volume
|
||||
chan[c.chan].outVol=c.value;
|
||||
if(chan[c.chan].active && c.chan < 16)
|
||||
{
|
||||
rWrite(q1_reg_map[Q1V_VOL][c.chan], chan[c.chan].outVol << 5);
|
||||
//logW("ch %d vol=%04x (cmd vol)!\n",c.chan,chan[c.chan].outVol << 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.hasVol) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
immWrite(Q1_PAN+c.chan, c.value + 0x110);
|
||||
break;
|
||||
case DIV_CMD_QSOUND_ECHO_LEVEL:
|
||||
immWrite(Q1_ECHO+c.chan, c.value << 7);
|
||||
break;
|
||||
case DIV_CMD_QSOUND_ECHO_FEEDBACK:
|
||||
immWrite(Q1_ECHO_FEEDBACK, c.value << 6);
|
||||
break;
|
||||
case DIV_CMD_QSOUND_ECHO_DELAY:
|
||||
immWrite(Q1_ECHO_LENGTH, (c.value > 2725 ? 0xfff : 0xfff - (2725 - c.value)));
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->song.sample[chan[c.chan].sample];
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
int destFreq=off*QS_NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->song.sample[chan[c.chan].sample];
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp-12):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformQSound::muteChannel(int ch, bool mute) {
|
||||
if(mute)
|
||||
chip.mute_mask |= (1 << ch);
|
||||
else
|
||||
chip.mute_mask &= ~(1 << ch);
|
||||
}
|
||||
|
||||
void DivPlatformQSound::forceIns() {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformQSound::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
void DivPlatformQSound::reset() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformQSound::Channel();
|
||||
}
|
||||
qsound_reset(&chip);
|
||||
while(!chip.ready_flag)
|
||||
qsound_update(&chip);
|
||||
|
||||
immWrite(Q1_ECHO_LENGTH, 0xfff - (2725 - echoDelay));
|
||||
immWrite(Q1_ECHO_FEEDBACK, echoFeedback << 6);
|
||||
}
|
||||
|
||||
bool DivPlatformQSound::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformQSound::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformQSound::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformQSound::notifyWaveChange(int wave) {
|
||||
// TODO when wavetables are added
|
||||
}
|
||||
|
||||
void DivPlatformQSound::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformQSound::setFlags(unsigned int flags) {
|
||||
echoDelay = 2725 - (flags & 0xfff);
|
||||
echoFeedback = (flags >> 12) & 255;
|
||||
|
||||
if(echoDelay < 0)
|
||||
echoDelay = 0;
|
||||
if(echoDelay > 2725)
|
||||
echoDelay = 2725;
|
||||
//rate=chipClock/CHIP_DIVIDER;
|
||||
}
|
||||
|
||||
void DivPlatformQSound::poke(unsigned int addr, unsigned short val) {
|
||||
immWrite(addr, val);
|
||||
immWrite(addr, val);
|
||||
}
|
||||
|
||||
void DivPlatformQSound::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformQSound::getRegisterPool() {
|
||||
unsigned short* regPoolPtr = regPool;
|
||||
for(int i=0; i<256; i++)
|
||||
{
|
||||
uint16_t data = qsound_read_data(&chip, i);
|
||||
*regPoolPtr++ = data;
|
||||
}
|
||||
return (unsigned char*)regPool;
|
||||
}
|
||||
|
||||
int DivPlatformQSound::getRegisterPoolSize() {
|
||||
return 256;
|
||||
}
|
||||
|
||||
int DivPlatformQSound::getRegisterPoolDepth() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
|
||||
// for (int i=0; i<16; i++) {
|
||||
// isMuted[i]=false;
|
||||
// }
|
||||
setFlags(flags);
|
||||
|
||||
chipClock=60000000;
|
||||
rate = qsound_start(&chip, chipClock);
|
||||
chip.rom_data = (unsigned char*)&chip.rom_mask;
|
||||
chip.rom_mask = 0;
|
||||
reset();
|
||||
return 19;
|
||||
}
|
||||
|
||||
void DivPlatformQSound::quit() {
|
||||
}
|
93
src/engine/platform/qsound.h
Normal file
93
src/engine/platform/qsound.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _QSOUND_H
|
||||
#define _QSOUND_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "sound/qsound.h"
|
||||
|
||||
class DivPlatformQSound: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch;
|
||||
unsigned short audLen;
|
||||
unsigned int audPos;
|
||||
int sample, wave;
|
||||
unsigned char ins;
|
||||
int note;
|
||||
int panning;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave;
|
||||
int vol, outVol;
|
||||
DivMacroInt std;
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
audLen(0),
|
||||
audPos(0),
|
||||
sample(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panning(0x10),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
vol(255),
|
||||
outVol(255) {}
|
||||
};
|
||||
Channel chan[19];
|
||||
int echoDelay;
|
||||
int echoFeedback;
|
||||
|
||||
struct qsound_chip chip;
|
||||
unsigned short regPool[512];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
int getRegisterPoolDepth();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
||||
#endif
|
700
src/engine/platform/sound/qsound.c
Normal file
700
src/engine/platform/sound/qsound.c
Normal file
|
@ -0,0 +1,700 @@
|
|||
/*
|
||||
|
||||
Capcom DL-1425 QSound emulator
|
||||
==============================
|
||||
|
||||
by superctr (Ian Karlsson)
|
||||
with thanks to Valley Bell
|
||||
|
||||
2018-05-12 - 2018-05-15
|
||||
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "qsound.h"
|
||||
|
||||
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
|
||||
|
||||
// ============================================================================
|
||||
|
||||
static const int16_t qsound_dry_mix_table[33] = {
|
||||
-16384,-16384,-16384,-16384,-16384,-16384,-16384,-16384,
|
||||
-16384,-16384,-16384,-16384,-16384,-16384,-16384,-16384,
|
||||
-16384,-14746,-13107,-11633,-10486,-9175,-8520,-7209,
|
||||
-6226,-5226,-4588,-3768,-3277,-2703,-2130,-1802,
|
||||
0
|
||||
};
|
||||
|
||||
static const int16_t qsound_wet_mix_table[33] = {
|
||||
0,-1638,-1966,-2458,-2949,-3441,-4096,-4669,
|
||||
-4915,-5120,-5489,-6144,-7537,-8831,-9339,-9830,
|
||||
-10240,-10322,-10486,-10568,-10650,-11796,-12288,-12288,
|
||||
-12534,-12648,-12780,-12829,-12943,-13107,-13418,-14090,
|
||||
-16384
|
||||
};
|
||||
|
||||
static const int16_t qsound_linear_mix_table[33] = {
|
||||
-16379,-16338,-16257,-16135,-15973,-15772,-15531,-15251,
|
||||
-14934,-14580,-14189,-13763,-13303,-12810,-12284,-11729,
|
||||
-11729,-11144,-10531,-9893,-9229,-8543,-7836,-7109,
|
||||
-6364,-5604,-4829,-4043,-3246,-2442,-1631,-817,
|
||||
0
|
||||
};
|
||||
|
||||
static const int16_t qsound_filter_data[5][95] = {
|
||||
{ // d53 - 0
|
||||
0,0,0,6,44,-24,-53,-10,59,-40,-27,1,39,-27,56,127,174,36,-13,49,
|
||||
212,142,143,-73,-20,66,-108,-117,-399,-265,-392,-569,-473,-71,95,-319,-218,-230,331,638,
|
||||
449,477,-180,532,1107,750,9899,3828,-2418,1071,-176,191,-431,64,117,-150,-274,-97,-238,165,
|
||||
166,250,-19,4,37,204,186,-6,140,-77,-1,1,18,-10,-151,-149,-103,-9,55,23,
|
||||
-102,-97,-11,13,-48,-27,5,18,-61,-30,64,72,0,0,0,
|
||||
},
|
||||
{ // db2 - 1 - default left filter
|
||||
0,0,0,85,24,-76,-123,-86,-29,-14,-20,-7,6,-28,-87,-89,-5,100,154,160,
|
||||
150,118,41,-48,-78,-23,59,83,-2,-176,-333,-344,-203,-66,-39,2,224,495,495,280,
|
||||
432,1340,2483,5377,1905,658,0,97,347,285,35,-95,-78,-82,-151,-192,-171,-149,-147,-113,
|
||||
-22,71,118,129,127,110,71,31,20,36,46,23,-27,-63,-53,-21,-19,-60,-92,-69,
|
||||
-12,25,29,30,40,41,29,30,46,39,-15,-74,0,0,0,
|
||||
},
|
||||
{ // e11 - 2 - default right filter
|
||||
0,0,0,23,42,47,29,10,2,-14,-54,-92,-93,-70,-64,-77,-57,18,94,113,
|
||||
87,69,67,50,25,29,58,62,24,-39,-131,-256,-325,-234,-45,58,78,223,485,496,
|
||||
127,6,857,2283,2683,4928,1328,132,79,314,189,-80,-90,35,-21,-186,-195,-99,-136,-258,
|
||||
-189,82,257,185,53,41,84,68,38,63,77,14,-60,-71,-71,-120,-151,-84,14,29,
|
||||
-8,7,66,69,12,-3,54,92,52,-6,-15,-2,0,0,0,
|
||||
},
|
||||
{ // e70 - 3
|
||||
0,0,0,2,-28,-37,-17,0,-9,-22,-3,35,52,39,20,7,-6,2,55,121,
|
||||
129,67,8,1,9,-6,-16,16,66,96,118,130,75,-47,-92,43,223,239,151,219,
|
||||
440,475,226,206,940,2100,2663,4980,865,49,-33,186,231,103,42,114,191,184,116,29,
|
||||
-47,-72,-21,60,96,68,31,32,63,87,76,39,7,14,55,85,67,18,-12,-3,
|
||||
21,34,29,6,-27,-49,-37,-2,16,0,-21,-16,0,0,0,
|
||||
},
|
||||
{ // ecf - 4
|
||||
0,0,0,48,7,-22,-29,-10,24,54,59,29,-36,-117,-185,-213,-185,-99,13,90,
|
||||
83,24,-5,23,53,47,38,56,67,57,75,107,16,-242,-440,-355,-120,-33,-47,152,
|
||||
501,472,-57,-292,544,1937,2277,6145,1240,153,47,200,152,36,64,134,74,-82,-208,-266,
|
||||
-268,-188,-42,65,74,56,89,133,114,44,-3,-1,17,29,29,-2,-76,-156,-187,-151,
|
||||
-85,-31,-5,7,20,32,24,-5,-20,6,48,62,0,0,0,
|
||||
}
|
||||
};
|
||||
|
||||
static const int16_t qsound_filter_data2[209] = {
|
||||
// f2e - following 95 values used for "disable output" filter
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,
|
||||
|
||||
// f73 - following 45 values used for "mode 2" filter (overlaps with f2e)
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,
|
||||
-371,-196,-268,-512,-303,-315,-184,-76,276,-256,298,196,990,236,1114,-126,4377,6549,791,
|
||||
|
||||
// fa0 - filtering disabled (for 95-taps) (use fa3 or fa4 for mode2 filters)
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,-16384,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
static const int16_t adpcm_step_table[16] = {
|
||||
154, 154, 128, 102, 77, 58, 58, 58,
|
||||
58, 58, 58, 58, 77, 102, 128, 154
|
||||
};
|
||||
|
||||
// DSP states
|
||||
enum {
|
||||
STATE_INIT1 = 0x288,
|
||||
STATE_INIT2 = 0x61a,
|
||||
STATE_REFRESH1 = 0x039,
|
||||
STATE_REFRESH2 = 0x04f,
|
||||
STATE_NORMAL1 = 0x314,
|
||||
STATE_NORMAL2 = 0x6b2,
|
||||
};
|
||||
|
||||
enum {
|
||||
PANTBL_LEFT = 0,
|
||||
PANTBL_RIGHT = 1,
|
||||
PANTBL_DRY = 0,
|
||||
PANTBL_WET = 1,
|
||||
};
|
||||
|
||||
static void init_pan_tables(struct qsound_chip *chip);
|
||||
static void init_register_map(struct qsound_chip *chip);
|
||||
|
||||
static void state_init(struct qsound_chip *chip);
|
||||
static void state_refresh_filter_1(struct qsound_chip *chip);
|
||||
static void state_refresh_filter_2(struct qsound_chip *chip);
|
||||
static void state_normal_update(struct qsound_chip *chip);
|
||||
|
||||
static inline int16_t get_sample(struct qsound_chip *chip, uint16_t bank,uint16_t address);
|
||||
static inline int16_t* get_filter_table(struct qsound_chip *chip, uint16_t offset);
|
||||
static inline int16_t pcm_update(struct qsound_chip *chip, int voice_no, int32_t *echo_out);
|
||||
static inline void adpcm_update(struct qsound_chip *chip, int voice_no, int nibble);
|
||||
static inline int16_t echo(struct qsound_echo *r,int32_t input);
|
||||
static inline int32_t fir(struct qsound_fir *f, int16_t input);
|
||||
static inline int32_t delay(struct qsound_delay *d, int32_t input);
|
||||
static inline void delay_update(struct qsound_delay *d);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
long qsound_start(struct qsound_chip *chip, int clock)
|
||||
{
|
||||
memset(chip,0,sizeof(*chip));
|
||||
|
||||
init_pan_tables(chip);
|
||||
init_register_map(chip);
|
||||
|
||||
return clock / 2 / 1248; // DSP program uses 1248 machine cycles per iteration
|
||||
}
|
||||
|
||||
void qsound_reset(struct qsound_chip *chip)
|
||||
{
|
||||
chip->ready_flag = 0;
|
||||
chip->out[0] = chip->out[1] = 0;
|
||||
chip->state = 0;
|
||||
chip->state_counter = 0;
|
||||
}
|
||||
|
||||
uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples)
|
||||
{
|
||||
// Clear the buffers
|
||||
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
|
||||
memset(outputs[1], 0, samples * sizeof(*outputs[1]));
|
||||
|
||||
for (int i = 0; i < samples; i ++)
|
||||
{
|
||||
qsound_update(chip);
|
||||
outputs[0][i] = chip->out[0];
|
||||
outputs[1][i] = chip->out[1];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data)
|
||||
{
|
||||
switch (offset)
|
||||
{
|
||||
case 0:
|
||||
chip->data_latch = (chip->data_latch & 0x00ff) | (data << 8);
|
||||
break;
|
||||
case 1:
|
||||
chip->data_latch = (chip->data_latch & 0xff00) | data;
|
||||
break;
|
||||
case 2:
|
||||
qsound_write_data(chip, data, chip->data_latch);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t qsound_r(struct qsound_chip *chip)
|
||||
{
|
||||
// ready bit (0x00 = busy, 0x80 == ready)
|
||||
return chip->ready_flag;
|
||||
}
|
||||
|
||||
void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data)
|
||||
{
|
||||
uint16_t *destination = chip->register_map[address];
|
||||
if(destination)
|
||||
*destination = data;
|
||||
chip->ready_flag = 0;
|
||||
}
|
||||
|
||||
uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address)
|
||||
{
|
||||
uint16_t data = 0;
|
||||
uint16_t *source = chip->register_map[address];
|
||||
if(source)
|
||||
data = *source;
|
||||
return data;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
static void init_pan_tables(struct qsound_chip *chip)
|
||||
{
|
||||
int i;
|
||||
for(i=0;i<33;i++)
|
||||
{
|
||||
// dry mixing levels
|
||||
chip->pan_tables[PANTBL_LEFT][PANTBL_DRY][i] = qsound_dry_mix_table[i];
|
||||
chip->pan_tables[PANTBL_RIGHT][PANTBL_DRY][i] = qsound_dry_mix_table[32-i];
|
||||
// wet mixing levels
|
||||
chip->pan_tables[PANTBL_LEFT][PANTBL_WET][i] = qsound_wet_mix_table[i];
|
||||
chip->pan_tables[PANTBL_RIGHT][PANTBL_WET][i] = qsound_wet_mix_table[32-i];
|
||||
// linear panning, only for dry component. wet component is muted.
|
||||
chip->pan_tables[PANTBL_LEFT][PANTBL_DRY][i+0x30] = qsound_linear_mix_table[i];
|
||||
chip->pan_tables[PANTBL_RIGHT][PANTBL_DRY][i+0x30] = qsound_linear_mix_table[32-i];
|
||||
}
|
||||
}
|
||||
|
||||
static void init_register_map(struct qsound_chip *chip)
|
||||
{
|
||||
int i;
|
||||
|
||||
// unused registers
|
||||
for(i=0;i<256;i++)
|
||||
chip->register_map[i] = NULL;
|
||||
|
||||
// PCM registers
|
||||
for(i=0;i<16;i++) // PCM voices
|
||||
{
|
||||
chip->register_map[(i<<3)+0] = (uint16_t*)&chip->voice[(i+1)%16].bank; // Bank applies to the next channel
|
||||
chip->register_map[(i<<3)+1] = (uint16_t*)&chip->voice[i].addr; // Current sample position and start position.
|
||||
chip->register_map[(i<<3)+2] = (uint16_t*)&chip->voice[i].rate; // 4.12 fixed point decimal.
|
||||
chip->register_map[(i<<3)+3] = (uint16_t*)&chip->voice[i].phase;
|
||||
chip->register_map[(i<<3)+4] = (uint16_t*)&chip->voice[i].loop_len;
|
||||
chip->register_map[(i<<3)+5] = (uint16_t*)&chip->voice[i].end_addr;
|
||||
chip->register_map[(i<<3)+6] = (uint16_t*)&chip->voice[i].volume;
|
||||
chip->register_map[(i<<3)+7] = NULL; // unused
|
||||
chip->register_map[i+0x80] = (uint16_t*)&chip->voice_pan[i];
|
||||
chip->register_map[i+0xba] = (uint16_t*)&chip->voice[i].echo;
|
||||
}
|
||||
|
||||
// ADPCM registers
|
||||
for(i=0;i<3;i++) // ADPCM voices
|
||||
{
|
||||
// ADPCM sample rate is fixed to 8khz. (one channel is updated every third sample)
|
||||
chip->register_map[(i<<2)+0xca] = (uint16_t*)&chip->adpcm[i].start_addr;
|
||||
chip->register_map[(i<<2)+0xcb] = (uint16_t*)&chip->adpcm[i].end_addr;
|
||||
chip->register_map[(i<<2)+0xcc] = (uint16_t*)&chip->adpcm[i].bank;
|
||||
chip->register_map[(i<<2)+0xcd] = (uint16_t*)&chip->adpcm[i].volume;
|
||||
chip->register_map[i+0xd6] = (uint16_t*)&chip->adpcm[i].flag; // non-zero to start ADPCM playback
|
||||
chip->register_map[i+0x90] = (uint16_t*)&chip->voice_pan[16+i];
|
||||
}
|
||||
|
||||
// QSound registers
|
||||
chip->register_map[0x93] = (uint16_t*)&chip->echo.feedback;
|
||||
chip->register_map[0xd9] = (uint16_t*)&chip->echo.end_pos;
|
||||
chip->register_map[0xe2] = (uint16_t*)&chip->delay_update; // non-zero to update delays
|
||||
chip->register_map[0xe3] = (uint16_t*)&chip->next_state;
|
||||
for(i=0;i<2;i++) // left, right
|
||||
{
|
||||
// Wet
|
||||
chip->register_map[(i<<1)+0xda] = (uint16_t*)&chip->filter[i].table_pos;
|
||||
chip->register_map[(i<<1)+0xde] = (uint16_t*)&chip->wet[i].delay;
|
||||
chip->register_map[(i<<1)+0xe4] = (uint16_t*)&chip->wet[i].volume;
|
||||
// Dry
|
||||
chip->register_map[(i<<1)+0xdb] = (uint16_t*)&chip->alt_filter[i].table_pos;
|
||||
chip->register_map[(i<<1)+0xdf] = (uint16_t*)&chip->dry[i].delay;
|
||||
chip->register_map[(i<<1)+0xe5] = (uint16_t*)&chip->dry[i].volume;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int16_t get_sample(struct qsound_chip *chip, uint16_t bank,uint16_t address)
|
||||
{
|
||||
uint32_t rom_addr;
|
||||
uint8_t sample_data;
|
||||
|
||||
if (! chip->rom_mask)
|
||||
return 0; // no ROM loaded
|
||||
if (! (bank & 0x8000))
|
||||
return 0; // ignore attempts to read from DSP program ROM
|
||||
|
||||
bank &= 0x7FFF;
|
||||
rom_addr = (bank << 16) | (address << 0);
|
||||
|
||||
sample_data = chip->rom_data[rom_addr];
|
||||
|
||||
return (int16_t)((sample_data << 8) | (sample_data << 0)); // MAME currently expands the 8 bit ROM data to 16 bits this way.
|
||||
}
|
||||
|
||||
static inline int16_t* get_filter_table(struct qsound_chip *chip, uint16_t offset)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (offset >= 0xf2e && offset < 0xfff)
|
||||
return (int16_t*)&qsound_filter_data2[offset-0xf2e]; // overlapping filter data
|
||||
|
||||
index = (offset-0xd53)/95;
|
||||
if(index >= 0 && index < 5)
|
||||
return (int16_t*)&qsound_filter_data[index]; // normal tables
|
||||
|
||||
return NULL; // no filter found.
|
||||
}
|
||||
|
||||
/********************************************************************/
|
||||
|
||||
// updates one DSP sample
|
||||
void qsound_update(struct qsound_chip *chip)
|
||||
{
|
||||
switch(chip->state)
|
||||
{
|
||||
default:
|
||||
case STATE_INIT1:
|
||||
case STATE_INIT2:
|
||||
state_init(chip); return;
|
||||
case STATE_REFRESH1:
|
||||
state_refresh_filter_1(chip); return;
|
||||
case STATE_REFRESH2:
|
||||
state_refresh_filter_2(chip); return;
|
||||
case STATE_NORMAL1:
|
||||
case STATE_NORMAL2:
|
||||
state_normal_update(chip); return;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization routine
|
||||
static void state_init(struct qsound_chip *chip)
|
||||
{
|
||||
int mode = (chip->state == STATE_INIT2) ? 1 : 0;
|
||||
int i;
|
||||
|
||||
// we're busy for 4 samples, including the filter refresh.
|
||||
if(chip->state_counter >= 2)
|
||||
{
|
||||
chip->state_counter = 0;
|
||||
chip->state = chip->next_state;
|
||||
return;
|
||||
}
|
||||
else if(chip->state_counter == 1)
|
||||
{
|
||||
chip->state_counter++;
|
||||
return;
|
||||
}
|
||||
|
||||
memset(chip->voice, 0, sizeof(chip->voice));
|
||||
memset(chip->adpcm, 0, sizeof(chip->adpcm));
|
||||
memset(chip->filter, 0, sizeof(chip->filter));
|
||||
memset(chip->alt_filter, 0, sizeof(chip->alt_filter));
|
||||
memset(chip->wet, 0, sizeof(chip->wet));
|
||||
memset(chip->dry, 0, sizeof(chip->dry));
|
||||
memset(&chip->echo, 0, sizeof(chip->echo));
|
||||
|
||||
for(i=0;i<19;i++)
|
||||
{
|
||||
chip->voice_pan[i] = 0x120;
|
||||
chip->voice_output[i] = 0;
|
||||
}
|
||||
|
||||
for(i=0;i<16;i++)
|
||||
chip->voice[i].bank = 0x8000;
|
||||
for(i=0;i<3;i++)
|
||||
chip->adpcm[i].bank = 0x8000;
|
||||
|
||||
if(mode == 0)
|
||||
{
|
||||
// mode 1
|
||||
chip->wet[0].delay = 0;
|
||||
chip->dry[0].delay = 46;
|
||||
chip->wet[1].delay = 0;
|
||||
chip->dry[1].delay = 48;
|
||||
chip->filter[0].table_pos = 0xdb2;
|
||||
chip->filter[1].table_pos = 0xe11;
|
||||
chip->echo.end_pos = 0x554 + 6;
|
||||
chip->next_state = STATE_REFRESH1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// mode 2
|
||||
chip->wet[0].delay = 1;
|
||||
chip->dry[0].delay = 0;
|
||||
chip->wet[1].delay = 0;
|
||||
chip->dry[1].delay = 0;
|
||||
chip->filter[0].table_pos = 0xf73;
|
||||
chip->filter[1].table_pos = 0xfa4;
|
||||
chip->alt_filter[0].table_pos = 0xf73;
|
||||
chip->alt_filter[1].table_pos = 0xfa4;
|
||||
chip->echo.end_pos = 0x53c + 6;
|
||||
chip->next_state = STATE_REFRESH2;
|
||||
}
|
||||
|
||||
chip->wet[0].volume = 0x3fff;
|
||||
chip->dry[0].volume = 0x3fff;
|
||||
chip->wet[1].volume = 0x3fff;
|
||||
chip->dry[1].volume = 0x3fff;
|
||||
|
||||
chip->delay_update = 1;
|
||||
chip->ready_flag = 0;
|
||||
chip->state_counter = 1;
|
||||
}
|
||||
|
||||
// Updates filter parameters for mode 1
|
||||
static void state_refresh_filter_1(struct qsound_chip *chip)
|
||||
{
|
||||
const int16_t *table;
|
||||
|
||||
for(int ch=0; ch<2; ch++)
|
||||
{
|
||||
chip->filter[ch].delay_pos = 0;
|
||||
chip->filter[ch].tap_count = 95;
|
||||
|
||||
table = get_filter_table(chip,chip->filter[ch].table_pos);
|
||||
if (table != NULL)
|
||||
memcpy(chip->filter[ch].taps, table, 95 * sizeof(int16_t));
|
||||
}
|
||||
|
||||
chip->state = chip->next_state = STATE_NORMAL1;
|
||||
}
|
||||
|
||||
// Updates filter parameters for mode 2
|
||||
static void state_refresh_filter_2(struct qsound_chip *chip)
|
||||
{
|
||||
const int16_t *table;
|
||||
|
||||
for(int ch=0; ch<2; ch++)
|
||||
{
|
||||
chip->filter[ch].delay_pos = 0;
|
||||
chip->filter[ch].tap_count = 45;
|
||||
|
||||
table = get_filter_table(chip,chip->filter[ch].table_pos);
|
||||
if (table != NULL)
|
||||
memcpy(chip->filter[ch].taps, table, 45 * sizeof(int16_t));
|
||||
|
||||
chip->alt_filter[ch].delay_pos = 0;
|
||||
chip->alt_filter[ch].tap_count = 44;
|
||||
|
||||
table = get_filter_table(chip,chip->alt_filter[ch].table_pos);
|
||||
if (table != NULL)
|
||||
memcpy(chip->alt_filter[ch].taps, table, 44 * sizeof(int16_t));
|
||||
}
|
||||
|
||||
chip->state = chip->next_state = STATE_NORMAL2;
|
||||
}
|
||||
|
||||
// Updates a PCM voice. There are 16 voices, each are updated every sample
|
||||
// with full rate and volume control.
|
||||
static inline int16_t pcm_update(struct qsound_chip *chip, int voice_no, int32_t *echo_out)
|
||||
{
|
||||
struct qsound_voice* v = &chip->voice[voice_no];
|
||||
|
||||
int32_t new_phase;
|
||||
int16_t output = 0;
|
||||
|
||||
if(!(chip->mute_mask & (1<<voice_no)))
|
||||
{
|
||||
// Read sample from rom and apply volume
|
||||
output = (v->volume * get_sample(chip, v->bank, v->addr))>>14;
|
||||
*echo_out += (output * v->echo)<<2;
|
||||
}
|
||||
|
||||
// Add delta to the phase and loop back if required
|
||||
new_phase = v->rate + ((v->addr<<12) | (v->phase>>4));
|
||||
|
||||
if((new_phase>>12) >= v->end_addr)
|
||||
new_phase -= (v->loop_len<<12);
|
||||
|
||||
new_phase = CLAMP(new_phase, -0x8000000, 0x7FFFFFF);
|
||||
v->addr = new_phase>>12;
|
||||
v->phase = (new_phase<<4)&0xffff;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Updates an ADPCM voice. There are 3 voices, one is updated every sample
|
||||
// (effectively making the ADPCM rate 1/3 of the master sample rate), and
|
||||
// volume is set when starting samples only.
|
||||
// The ADPCM algorithm is supposedly similar to Yamaha ADPCM. It also seems
|
||||
// like Capcom never used it, so this was not emulated in the earlier QSound
|
||||
// emulators.
|
||||
static inline void adpcm_update(struct qsound_chip *chip, int voice_no, int nibble)
|
||||
{
|
||||
struct qsound_adpcm *v = &chip->adpcm[voice_no];
|
||||
|
||||
int32_t delta;
|
||||
int8_t step;
|
||||
|
||||
if(!nibble)
|
||||
{
|
||||
// Mute voice when it reaches the end address.
|
||||
if(v->cur_addr == v->end_addr)
|
||||
v->cur_vol = 0;
|
||||
|
||||
// Playback start flag
|
||||
if(v->flag)
|
||||
{
|
||||
chip->voice_output[16+voice_no] = 0;
|
||||
v->flag = 0;
|
||||
v->step_size = 10;
|
||||
v->cur_vol = v->volume;
|
||||
v->cur_addr = v->start_addr;
|
||||
}
|
||||
|
||||
// get top nibble
|
||||
step = get_sample(chip, v->bank, v->cur_addr) >> 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
// get bottom nibble
|
||||
step = get_sample(chip, v->bank, v->cur_addr++) >> 4;
|
||||
}
|
||||
|
||||
// shift with sign extend
|
||||
step >>= 4;
|
||||
|
||||
// delta = (0.5 + abs(v->step)) * v->step_size
|
||||
delta = ((1+abs(step<<1)) * v->step_size)>>1;
|
||||
if(step <= 0)
|
||||
delta = -delta;
|
||||
delta += chip->voice_output[16+voice_no];
|
||||
delta = CLAMP(delta,-32768,32767);
|
||||
|
||||
if(chip->mute_mask & (1<<(16+voice_no)))
|
||||
chip->voice_output[16+voice_no] = 0;
|
||||
else
|
||||
chip->voice_output[16+voice_no] = (delta * v->cur_vol)>>16;
|
||||
|
||||
v->step_size = (adpcm_step_table[8+step] * v->step_size) >> 6;
|
||||
v->step_size = CLAMP(v->step_size, 1, 2000);
|
||||
}
|
||||
|
||||
// The echo effect is pretty simple. A moving average filter is used on
|
||||
// the output from the delay line to smooth samples over time.
|
||||
static inline int16_t echo(struct qsound_echo *r,int32_t input)
|
||||
{
|
||||
// get average of last 2 samples from the delay line
|
||||
int32_t new_sample;
|
||||
int32_t old_sample = r->delay_line[r->delay_pos];
|
||||
int32_t last_sample = r->last_sample;
|
||||
|
||||
r->last_sample = old_sample;
|
||||
old_sample = (old_sample+last_sample) >> 1;
|
||||
|
||||
// add current sample to the delay line
|
||||
new_sample = input + ((old_sample * r->feedback)<<2);
|
||||
r->delay_line[r->delay_pos++] = new_sample>>16;
|
||||
|
||||
if(r->delay_pos >= r->length)
|
||||
r->delay_pos = 0;
|
||||
|
||||
return old_sample;
|
||||
}
|
||||
|
||||
// Process a sample update
|
||||
static void state_normal_update(struct qsound_chip *chip)
|
||||
{
|
||||
int v, ch;
|
||||
int32_t echo_input = 0;
|
||||
int16_t echo_output;
|
||||
|
||||
chip->ready_flag = 0x80;
|
||||
|
||||
// recalculate echo length
|
||||
if(chip->state == STATE_NORMAL2)
|
||||
chip->echo.length = chip->echo.end_pos - 0x53c;
|
||||
else
|
||||
chip->echo.length = chip->echo.end_pos - 0x554;
|
||||
|
||||
chip->echo.length = CLAMP(chip->echo.length, 0, 1024);
|
||||
|
||||
// update PCM voices
|
||||
for(v=0; v<16; v++)
|
||||
{
|
||||
chip->voice_output[v] = pcm_update(chip, v, &echo_input);
|
||||
}
|
||||
|
||||
// update ADPCM voices (one every third sample)
|
||||
adpcm_update(chip, chip->state_counter % 3, chip->state_counter / 3);
|
||||
|
||||
echo_output = echo(&chip->echo,echo_input);
|
||||
|
||||
// now, we do the magic stuff
|
||||
for(ch=0; ch<2; ch++)
|
||||
{
|
||||
// Echo is output on the unfiltered component of the left channel and
|
||||
// the filtered component of the right channel.
|
||||
int32_t wet = (ch == 1) ? echo_output<<16 : 0;
|
||||
int32_t dry = (ch == 0) ? echo_output<<16 : 0;
|
||||
int32_t output = 0;
|
||||
|
||||
for(int v=0; v<19; v++)
|
||||
{
|
||||
uint16_t pan_index = chip->voice_pan[v]-0x110;
|
||||
if(pan_index > 97)
|
||||
pan_index = 97;
|
||||
|
||||
// Apply different volume tables on the dry and wet inputs.
|
||||
dry -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_DRY][pan_index])<<2;
|
||||
wet -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_WET][pan_index])<<2;
|
||||
}
|
||||
|
||||
// Apply FIR filter on 'wet' input
|
||||
wet = fir(&chip->filter[ch], wet >> 16);
|
||||
|
||||
// in mode 2, we do this on the 'dry' input too
|
||||
if(chip->state == STATE_NORMAL2)
|
||||
dry = fir(&chip->alt_filter[ch], dry >> 16);
|
||||
|
||||
// output goes through a delay line and attenuation
|
||||
output = (delay(&chip->wet[ch], wet) + delay(&chip->dry[ch], dry));
|
||||
|
||||
// DSP round function
|
||||
output = (output + 0x2000) >> 14;
|
||||
chip->out[ch] = CLAMP(output, -0x7fff, 0x7fff);
|
||||
|
||||
if(chip->delay_update)
|
||||
{
|
||||
delay_update(&chip->wet[ch]);
|
||||
delay_update(&chip->dry[ch]);
|
||||
}
|
||||
}
|
||||
|
||||
chip->delay_update = 0;
|
||||
|
||||
// after 6 samples, the next state is executed.
|
||||
chip->state_counter++;
|
||||
if(chip->state_counter > 5)
|
||||
{
|
||||
chip->state_counter = 0;
|
||||
chip->state = chip->next_state;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the FIR filter used as the Q1 transfer function
|
||||
static inline int32_t fir(struct qsound_fir *f, int16_t input)
|
||||
{
|
||||
int32_t output = 0, tap = 0;
|
||||
|
||||
for(; tap < (f->tap_count-1); tap++)
|
||||
{
|
||||
output -= (f->taps[tap] * f->delay_line[f->delay_pos++])<<2;
|
||||
|
||||
if(f->delay_pos >= f->tap_count-1)
|
||||
f->delay_pos = 0;
|
||||
}
|
||||
|
||||
output -= (f->taps[tap] * input)<<2;
|
||||
|
||||
f->delay_line[f->delay_pos++] = input;
|
||||
if(f->delay_pos >= f->tap_count-1)
|
||||
f->delay_pos = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Apply delay line and component volume
|
||||
static inline int32_t delay(struct qsound_delay *d, int32_t input)
|
||||
{
|
||||
int32_t output;
|
||||
|
||||
d->delay_line[d->write_pos++] = input>>16;
|
||||
if(d->write_pos >= 51)
|
||||
d->write_pos = 0;
|
||||
|
||||
output = d->delay_line[d->read_pos++]*d->volume;
|
||||
if(d->read_pos >= 51)
|
||||
d->read_pos = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Update the delay read position to match new delay length
|
||||
static inline void delay_update(struct qsound_delay *d)
|
||||
{
|
||||
int16_t new_read_pos = (d->write_pos - d->delay) % 51;
|
||||
if(new_read_pos < 0)
|
||||
new_read_pos += 51;
|
||||
|
||||
d->read_pos = new_read_pos;
|
||||
}
|
||||
|
121
src/engine/platform/sound/qsound.h
Normal file
121
src/engine/platform/sound/qsound.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
#ifndef QSOUND_H
|
||||
#define QSOUND_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
|
||||
Capcom DL-1425 QSound emulator
|
||||
==============================
|
||||
|
||||
by superctr (Ian Karlsson)
|
||||
with thanks to Valley Bell
|
||||
|
||||
2018-05-12 - 2018-05-15
|
||||
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct qsound_voice {
|
||||
uint16_t bank;
|
||||
int16_t addr; // top word is the sample address
|
||||
uint16_t phase;
|
||||
uint16_t rate;
|
||||
int16_t loop_len;
|
||||
int16_t end_addr;
|
||||
int16_t volume;
|
||||
int16_t echo;
|
||||
};
|
||||
|
||||
struct qsound_adpcm {
|
||||
uint16_t start_addr;
|
||||
uint16_t end_addr;
|
||||
uint16_t bank;
|
||||
int16_t volume;
|
||||
uint16_t flag;
|
||||
int16_t cur_vol;
|
||||
int16_t step_size;
|
||||
uint16_t cur_addr;
|
||||
};
|
||||
|
||||
// Q1 Filter
|
||||
struct qsound_fir {
|
||||
int tap_count; // usually 95
|
||||
int delay_pos;
|
||||
int16_t table_pos;
|
||||
int16_t taps[95];
|
||||
int16_t delay_line[95];
|
||||
};
|
||||
|
||||
// Delay line
|
||||
struct qsound_delay {
|
||||
int16_t delay;
|
||||
int16_t volume;
|
||||
int16_t write_pos;
|
||||
int16_t read_pos;
|
||||
int16_t delay_line[51];
|
||||
};
|
||||
|
||||
struct qsound_echo {
|
||||
uint16_t end_pos;
|
||||
|
||||
int16_t feedback;
|
||||
int16_t length;
|
||||
int16_t last_sample;
|
||||
int16_t delay_line[1024];
|
||||
int16_t delay_pos;
|
||||
};
|
||||
|
||||
struct qsound_chip {
|
||||
|
||||
unsigned long rom_mask;
|
||||
uint8_t *rom_data;
|
||||
|
||||
uint32_t mute_mask;
|
||||
|
||||
uint16_t data_latch;
|
||||
int16_t out[2];
|
||||
|
||||
int16_t pan_tables[2][2][98];
|
||||
|
||||
struct qsound_voice voice[16];
|
||||
struct qsound_adpcm adpcm[3];
|
||||
|
||||
uint16_t voice_pan[16+3];
|
||||
int16_t voice_output[16+3];
|
||||
|
||||
struct qsound_echo echo;
|
||||
|
||||
struct qsound_fir filter[2];
|
||||
struct qsound_fir alt_filter[2];
|
||||
|
||||
struct qsound_delay wet[2];
|
||||
struct qsound_delay dry[2];
|
||||
|
||||
uint16_t state;
|
||||
uint16_t next_state;
|
||||
|
||||
uint16_t delay_update;
|
||||
|
||||
int state_counter;
|
||||
int ready_flag;
|
||||
|
||||
uint16_t *register_map[256];
|
||||
};
|
||||
|
||||
long qsound_start(struct qsound_chip *chip, int clock);
|
||||
void qsound_reset(struct qsound_chip *chip);
|
||||
void qsound_update(struct qsound_chip *chip);
|
||||
|
||||
uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples);
|
||||
uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data);
|
||||
uint8_t qsound_r(struct qsound_chip *chip);
|
||||
void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data);
|
||||
uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
#endif
|
|
@ -111,6 +111,10 @@ const char* cmdName[DIV_CMD_MAX]={
|
|||
|
||||
"LYNX_LFSR_LOAD",
|
||||
|
||||
"QSOUND_ECHO_FEEDBACK",
|
||||
"QSOUND_ECHO_DELAY",
|
||||
"QSOUND_ECHO_LEVEL",
|
||||
|
||||
"ALWAYS_SET_VOLUME"
|
||||
};
|
||||
|
||||
|
@ -227,6 +231,24 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
|||
break;
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
switch (effect) {
|
||||
case 0x10: // echo feedback
|
||||
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_FEEDBACK,ch,effectVal));
|
||||
break;
|
||||
case 0x11: // echo level
|
||||
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal));
|
||||
break;
|
||||
default:
|
||||
if ((effect & 0xf0)==0x30) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ struct DivSample {
|
|||
// - 16: 16-bit PCM
|
||||
unsigned char depth;
|
||||
short* data;
|
||||
unsigned int rendLength, adpcmRendLength, rendOff, rendOffP, rendOffContiguous;
|
||||
unsigned int rendLength, adpcmRendLength, rendOff, rendOffP, rendOffContiguous, rendOffQsound;
|
||||
short* rendData;
|
||||
unsigned char* adpcmRendData;
|
||||
|
||||
|
@ -54,6 +54,7 @@ struct DivSample {
|
|||
rendOff(0),
|
||||
rendOffP(0),
|
||||
rendOffContiguous(0),
|
||||
rendOffQsound(0),
|
||||
rendData(NULL),
|
||||
adpcmRendData(NULL) {}
|
||||
~DivSample();
|
||||
|
|
|
@ -87,7 +87,8 @@ enum DivSystem {
|
|||
DIV_SYSTEM_YM2610_FULL,
|
||||
DIV_SYSTEM_YM2610_FULL_EXT,
|
||||
DIV_SYSTEM_OPLL_DRUMS,
|
||||
DIV_SYSTEM_LYNX
|
||||
DIV_SYSTEM_LYNX,
|
||||
DIV_SYSTEM_QSOUND
|
||||
};
|
||||
|
||||
struct DivSong {
|
||||
|
@ -211,6 +212,12 @@ struct DivSong {
|
|||
// - 1: Amiga 1200
|
||||
// - bit 8-14: stereo separation
|
||||
// - 0 is 0% while 127 is 100%
|
||||
// - QSound:
|
||||
// - bit 12-20: echo feedback
|
||||
// - Valid values are 0-255
|
||||
// - bit 0-11: echo delay length
|
||||
// - Valid values are 0-2725
|
||||
// - 0 is max length, 2725 is min length
|
||||
unsigned int systemFlags[32];
|
||||
|
||||
// song information
|
||||
|
|
|
@ -131,6 +131,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
|
|||
return DIV_SYSTEM_OPLL_DRUMS;
|
||||
case 0xa8:
|
||||
return DIV_SYSTEM_LYNX;
|
||||
case 0xe0:
|
||||
return DIV_SYSTEM_QSOUND;
|
||||
}
|
||||
return DIV_SYSTEM_NULL;
|
||||
}
|
||||
|
@ -246,6 +248,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
|
|||
return 0xa7;
|
||||
case DIV_SYSTEM_LYNX:
|
||||
return 0xa8;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return 0xe0;
|
||||
|
||||
case DIV_SYSTEM_NULL:
|
||||
return 0;
|
||||
|
@ -360,6 +364,8 @@ int DivEngine::getChannelCount(DivSystem sys) {
|
|||
return 11;
|
||||
case DIV_SYSTEM_LYNX:
|
||||
return 4;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return 19;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -482,6 +488,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
|
|||
return "Yamaha OPLL with drums";
|
||||
case DIV_SYSTEM_LYNX:
|
||||
return "Atari Lynx";
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return "Capcom QSound";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
@ -599,6 +607,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
|
|||
return "Yamaha YM2413 with drums";
|
||||
case DIV_SYSTEM_LYNX:
|
||||
return "Mikey";
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return "Capcom DL-1425";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
@ -678,7 +688,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
|
|||
sys!=DIV_SYSTEM_YM2151);
|
||||
}
|
||||
|
||||
const char* chanNames[36][24]={
|
||||
const char* chanNames[37][24]={
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3)
|
||||
|
@ -715,9 +725,10 @@ const char* chanNames[36][24]={
|
|||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op
|
||||
{"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums
|
||||
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound
|
||||
};
|
||||
|
||||
const char* chanShortNames[36][24]={
|
||||
const char* chanShortNames[37][24]={
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3)
|
||||
|
@ -754,6 +765,7 @@ const char* chanShortNames[36][24]={
|
|||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, // OPL3 drums
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op
|
||||
{"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound
|
||||
};
|
||||
|
||||
const int chanTypes[37][24]={
|
||||
|
@ -785,7 +797,7 @@ const int chanTypes[37][24]={
|
|||
{0, 0, 0, 1, 1, 1}, // OPN
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
|
||||
{1}, // PC Speaker/Pokémon Mini
|
||||
{3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B
|
||||
|
@ -825,7 +837,7 @@ const DivInstrumentType chanPrefType[43][24]={
|
|||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98
|
||||
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3
|
||||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM
|
||||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound
|
||||
{DIV_INS_STD}, // PC Speaker/Pokémon Mini
|
||||
{DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B
|
||||
|
@ -969,6 +981,9 @@ const char* DivEngine::getChannelName(int chan) {
|
|||
case DIV_SYSTEM_AY8930:
|
||||
return chanNames[17][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return chanNames[36][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
}
|
||||
return "??";
|
||||
}
|
||||
|
@ -1100,6 +1115,9 @@ const char* DivEngine::getChannelShortName(int chan) {
|
|||
case DIV_SYSTEM_AY8930:
|
||||
return chanShortNames[17][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return chanShortNames[36][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
}
|
||||
return "??";
|
||||
}
|
||||
|
@ -1201,6 +1219,7 @@ int DivEngine::getChannelType(int chan) {
|
|||
break;
|
||||
case DIV_SYSTEM_MULTIPCM:
|
||||
case DIV_SYSTEM_SEGAPCM:
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return chanTypes[28][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_PCSPKR:
|
||||
|
@ -1327,6 +1346,7 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
|
|||
break;
|
||||
case DIV_SYSTEM_MULTIPCM:
|
||||
case DIV_SYSTEM_SEGAPCM:
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return chanPrefType[28][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_PCSPKR:
|
||||
|
@ -1395,6 +1415,7 @@ bool DivEngine::isVGMExportable(DivSystem which) {
|
|||
case DIV_SYSTEM_AY8910:
|
||||
case DIV_SYSTEM_AY8930:
|
||||
case DIV_SYSTEM_SAA1099:
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -273,6 +273,28 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(0xff);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
for (int i=0; i<16; i++) {
|
||||
w->writeC(0xc4);
|
||||
w->writeC(0);
|
||||
w->writeC(0);
|
||||
w->writeC(2+(i*8));
|
||||
w->writeC(0xc4);
|
||||
w->writeC(0);
|
||||
w->writeC(0);
|
||||
w->writeC(6+(i*8));
|
||||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
w->writeC(0xc4);
|
||||
w->writeC(0);
|
||||
w->writeC(0);
|
||||
w->writeC(0xcd+(i*4));
|
||||
w->writeC(0xc4);
|
||||
w->writeC(0x00);
|
||||
w->writeC(0x01);
|
||||
w->writeC(0xd6+i);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -395,6 +417,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val&0xff);
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
w->writeC(0xc4);
|
||||
w->writeC((write.val>>8)&0xff);
|
||||
w->writeC(write.val&0xff);
|
||||
w->writeC(write.addr&0xff);
|
||||
break;
|
||||
default:
|
||||
logW("write not handled!\n");
|
||||
break;
|
||||
|
@ -508,6 +536,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
bool writePCESamples=false;
|
||||
bool writeADPCM=false;
|
||||
bool writeSegaPCM=false;
|
||||
bool writeQSound=false;
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
willExport[i]=false;
|
||||
|
@ -690,7 +719,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
case DIV_SYSTEM_LYNX:
|
||||
if (!hasLynx) {
|
||||
hasLynx=disCont[i].dispatch->chipClock;
|
||||
willExport[i] = true;
|
||||
willExport[i]=true;
|
||||
} else if (!(hasLynx&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
|
@ -698,6 +727,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
if (!hasQSound) {
|
||||
// could set chipClock to 4000000 here for compatibility
|
||||
// However I think it it not necessary because old VGM players will still
|
||||
// not be able to handle the 64kb sample bank trick
|
||||
hasQSound=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeQSound=true;
|
||||
} else if (!(hasQSound&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=false;
|
||||
addWarning("dual QSound is not supported by the VGM format");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -931,6 +974,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
w->write(adpcmMem,adpcmMemLen);
|
||||
}
|
||||
|
||||
if (writeQSound && qsoundMemLen>0) {
|
||||
// always write a whole bank
|
||||
unsigned int blockSize=(qsoundMemLen + 0xffff) & ~0xffff;
|
||||
if(blockSize > 0x1000000)
|
||||
blockSize = 0x1000000;
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x8F);
|
||||
w->writeI(blockSize+8);
|
||||
w->writeI(0x1000000);
|
||||
w->writeI(0);
|
||||
w->write(qsoundMem,blockSize);
|
||||
}
|
||||
|
||||
// initialize streams
|
||||
int streamID=0;
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
|
|
@ -1271,11 +1271,11 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
ImGui::Text("Length: %d",sample->length);
|
||||
if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) {
|
||||
if (sample->rate<100) sample->rate=100;
|
||||
if (sample->rate>32000) sample->rate=32000;
|
||||
if (sample->rate>96000) sample->rate=96000;
|
||||
}
|
||||
if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) {
|
||||
if (sample->centerRate<100) sample->centerRate=100;
|
||||
if (sample->centerRate>32000) sample->centerRate=32000;
|
||||
if (sample->centerRate>65535) sample->centerRate=65535;
|
||||
}
|
||||
ImGui::Text("effective rate: %dHz",e->getEffectiveSampleRate(sample->rate));
|
||||
bool doLoop=(sample->loopStart>=0);
|
||||
|
@ -1552,6 +1552,7 @@ const char* aboutLine[]={
|
|||
"puNES by FHorse",
|
||||
"reSID by Dag Lem",
|
||||
"Stella by Stella Team",
|
||||
"QSound emulator by Ian Karlsson and Valley Bell",
|
||||
"",
|
||||
"greetings to:",
|
||||
"Delek",
|
||||
|
@ -1904,12 +1905,16 @@ void FurnaceGUI::drawStats() {
|
|||
if (ImGui::Begin("Statistics",&statsOpen)) {
|
||||
String adpcmUsage=fmt::sprintf("%d/16384KB",e->adpcmMemLen/1024);
|
||||
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
|
||||
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
|
||||
ImGui::Text("ADPCM-A");
|
||||
ImGui::SameLine();
|
||||
ImGui::ProgressBar(((float)e->adpcmMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmUsage.c_str());
|
||||
ImGui::Text("ADPCM-B");
|
||||
ImGui::SameLine();
|
||||
ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str());
|
||||
ImGui::Text("QSound");
|
||||
ImGui::SameLine();
|
||||
ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str());
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
|
||||
ImGui::End();
|
||||
|
@ -2091,7 +2096,9 @@ void FurnaceGUI::drawRegView() {
|
|||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i]));
|
||||
int size=0;
|
||||
unsigned char* regPool=e->getRegisterPool(i,size);
|
||||
int depth=8;
|
||||
unsigned char* regPool=e->getRegisterPool(i,size,depth);
|
||||
unsigned short* regPoolW=(unsigned short*) regPool;
|
||||
if (regPool==NULL) {
|
||||
ImGui::Text("- no register pool available");
|
||||
} else {
|
||||
|
@ -2110,7 +2117,12 @@ void FurnaceGUI::drawRegView() {
|
|||
for (int j=0; j<16; j++) {
|
||||
ImGui::TableNextColumn();
|
||||
if (i*16+j>=size) continue;
|
||||
ImGui::Text("%.2x",regPool[i*16+j]);
|
||||
if(depth == 8)
|
||||
ImGui::Text("%.2x",regPool[i*16+j]);
|
||||
else if(depth == 16)
|
||||
ImGui::Text("%.4x",regPoolW[i*16+j]);
|
||||
else
|
||||
ImGui::Text("??");
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
|
@ -4482,6 +4494,7 @@ bool FurnaceGUI::loop() {
|
|||
sysAddOption(DIV_SYSTEM_SAA1099);
|
||||
sysAddOption(DIV_SYSTEM_AY8930);
|
||||
sysAddOption(DIV_SYSTEM_LYNX);
|
||||
sysAddOption(DIV_SYSTEM_QSOUND);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("configure system...")) {
|
||||
|
@ -4647,6 +4660,23 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_QSOUND: {
|
||||
ImGui::Text("Echo delay:");
|
||||
int echoBufSize=2725 - (flags & 4095);
|
||||
if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) {
|
||||
if (echoBufSize<0) echoBufSize=0;
|
||||
if (echoBufSize>2725) echoBufSize=2725;
|
||||
e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart);
|
||||
}
|
||||
ImGui::Text("Echo feedback:");
|
||||
int echoFeedback=(flags>>12)&255;
|
||||
if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) {
|
||||
if (echoFeedback<0) echoFeedback=0;
|
||||
if (echoFeedback>255) echoFeedback=255;
|
||||
e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_GB:
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_EXT:
|
||||
|
@ -4690,6 +4720,7 @@ bool FurnaceGUI::loop() {
|
|||
sysChangeOption(i,DIV_SYSTEM_SAA1099);
|
||||
sysChangeOption(i,DIV_SYSTEM_AY8930);
|
||||
sysChangeOption(i,DIV_SYSTEM_LYNX);
|
||||
sysChangeOption(i,DIV_SYSTEM_QSOUND);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue