Merge pull request #225 from cam900/ym2610b

Prepare to experimental support YM2610B and with Extended channel 3 mode, Some additions
This commit is contained in:
tildearrow 2022-03-01 23:21:46 -05:00 committed by GitHub
commit acc9b0aa57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 2243 additions and 21 deletions

View file

@ -313,6 +313,8 @@ src/engine/platform/c64.cpp
src/engine/platform/arcade.cpp
src/engine/platform/ym2610.cpp
src/engine/platform/ym2610ext.cpp
src/engine/platform/ym2610b.cpp
src/engine/platform/ym2610bext.cpp
src/engine/platform/ay.cpp
src/engine/platform/ay8930.cpp
src/engine/platform/tia.cpp

View file

@ -10,6 +10,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [Commodore 64](c64.md)
- [Arcade (YM2151/PCM)](arcade.md)
- [Neo Geo/YM2610](ym2610.md)
- [Taito Arcade/YM2610B](ym2610b.md)
- [AY-3-8910](ay8910.md)
- [Amiga](amiga.md)
- [Yamaha YM2612 standalone](ym2612.md)

View file

@ -2,7 +2,7 @@
originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it.
its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package!
its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package!
# effects

View file

@ -0,0 +1,57 @@
# Taito Arcade/Yamaha YM2610B
YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares, it's backward compatible with non-B chip.
# effects
- `10xy`: set LFO parameters.
- `x` toggles the LFO.
- `y` sets its speed.
- `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.
- `18xx`: toggle extended channel 2 mode.
- 0 disables it and 1 enables it.
- only in extended channel 2 system.
- `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.
- `20xx`: set SSG channel mode. `xx` may be one of the following:
- `00`: square
- `01`: noise
- `02`: square and noise
- `03`: nothing (apparently)
- `04`: envelope and square
- `05`: envelope and noise
- `06`: envelope and square and noise
- `07`: nothing
- `21xx`: set noise frequency. `xx` is a value between 00 and 1F.
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable SSG auto-envelope mode.
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.

View file

@ -168,6 +168,7 @@ size | description
| - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels
| - 0xa7: OPLL drums (YM2413) - 11 channels
| - 0xa8: Atari Lynx - 4 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - (compound!) means that the system is composed of two or more chips,
| and has to be flattened.

View file

@ -30,6 +30,8 @@
#include "platform/arcade.h"
#include "platform/ym2610.h"
#include "platform/ym2610ext.h"
#include "platform/ym2610b.h"
#include "platform/ym2610bext.h"
#include "platform/ay.h"
#include "platform/ay8930.h"
#include "platform/tia.h"
@ -182,6 +184,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_YM2610_FULL_EXT:
dispatch=new DivPlatformYM2610Ext;
break;
case DIV_SYSTEM_YM2610B:
dispatch=new DivPlatformYM2610B;
break;
case DIV_SYSTEM_YM2610B_EXT:
dispatch=new DivPlatformYM2610BExt;
break;
case DIV_SYSTEM_AMIGA:
dispatch=new DivPlatformAmiga;
break;

View file

@ -947,7 +947,9 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return 1789773/(1789773/rate);
case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT:
return (31250*MIN(255,(rate*255/31250)))/255;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_QSOUND:
return (24038*MIN(65535,(rate*4096/24038)))/4096;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT:
return 18518;
default:
break;

View file

@ -148,7 +148,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
// Neo Geo detune
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) {
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
ds.tuning=443.23;
}
@ -256,7 +258,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) {
ins->type=DIV_INS_C64;
}
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) {
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
if (!ins->mode) {
ins->type=DIV_INS_AY;
}

View file

@ -32,6 +32,218 @@ static unsigned char konOffs[4]={
#define CHIP_DIVIDER 32
const char* regCheatSheetYM2610[]={
// SSG
"SSG_FreqL_A", "000",
"SSG_FreqH_A", "001",
"SSG_FreqL_B", "002",
"SSG_FreqH_B", "003",
"SSG_FreqL_C", "004",
"SSG_FreqH_C", "005",
"SSG_FreqNoise", "006",
"SSG_Enable", "007",
"SSG_Volume_A", "008",
"SSG_Volume_B", "009",
"SSG_Volume_C", "00A",
"SSG_FreqL_Env", "00B",
"SSG_FreqH_Env", "00C",
"SSG_Control_Env", "00D",
// ADPCM-B
"ADPCMB_Control", "010",
"ADPCMB_L_R", "011",
"ADPCMB_StartL", "012",
"ADPCMB_StartH", "013",
"ADPCMB_EndL", "014",
"ADPCMB_EndH", "015",
"ADPCMB_FreqL", "019",
"ADPCMB_FreqH", "01A",
"ADPCMB_Volume", "01B",
"ADPCM_Flag", "01C",
// FM (Common)
"FM_Test", "021",
"FM_LFOFreq", "022",
"ClockA1", "024",
"ClockA2", "025",
"ClockB", "026",
"FM_Control", "027",
"FM_NoteCtl", "028",
// FM (Channel 1-2)
"FM1_Op1_DT_MULT", "031",
"FM2_Op1_DT_MULT", "032",
"FM1_Op2_DT_MULT", "035",
"FM2_Op2_DT_MULT", "036",
"FM1_Op3_DT_MULT", "039",
"FM2_Op3_DT_MULT", "03A",
"FM1_Op4_DT_MULT", "03D",
"FM2_Op4_DT_MULT", "03E",
"FM1_Op1_TL", "041",
"FM2_Op1_TL", "042",
"FM1_Op2_TL", "045",
"FM2_Op2_TL", "046",
"FM1_Op3_TL", "049",
"FM2_Op3_TL", "04A",
"FM1_Op4_TL", "04D",
"FM2_Op4_TL", "04E",
"FM1_Op1_KS_AR", "051",
"FM2_Op1_KS_AR", "052",
"FM1_Op2_KS_AR", "055",
"FM2_Op2_KS_AR", "056",
"FM1_Op3_KS_AR", "059",
"FM2_Op3_KS_AR", "05A",
"FM1_Op4_KS_AR", "05D",
"FM2_Op4_KS_AR", "05E",
"FM1_Op1_AM_DR", "061",
"FM2_Op1_AM_DR", "062",
"FM1_Op2_AM_DR", "065",
"FM2_Op2_AM_DR", "066",
"FM1_Op3_AM_DR", "069",
"FM2_Op3_AM_DR", "06A",
"FM1_Op4_AM_DR", "06D",
"FM2_Op4_AM_DR", "06E",
"FM1_Op1_SR", "071",
"FM2_Op1_SR", "072",
"FM1_Op2_SR", "075",
"FM2_Op2_SR", "076",
"FM1_Op3_SR", "079",
"FM2_Op3_SR", "07A",
"FM1_Op4_SR", "07D",
"FM2_Op4_SR", "07E",
"FM1_Op1_SL_RR", "081",
"FM2_Op1_SL_RR", "082",
"FM1_Op2_SL_RR", "085",
"FM2_Op2_SL_RR", "086",
"FM1_Op3_SL_RR", "089",
"FM2_Op3_SL_RR", "08A",
"FM1_Op4_SL_RR", "08D",
"FM2_Op4_SL_RR", "08E",
"FM1_Op1_SSG_EG", "091",
"FM2_Op1_SSG_EG", "092",
"FM1_Op2_SSG_EG", "095",
"FM2_Op2_SSG_EG", "096",
"FM1_Op3_SSG_EG", "099",
"FM2_Op3_SSG_EG", "09A",
"FM1_Op4_SSG_EG", "09D",
"FM2_Op4_SSG_EG", "09E",
"FM1_FNum1", "0A1",
"FM2_(Op1)FNum1", "0A2",
"FM1_FNum2", "0A5",
"FM2_(Op1)FNum2", "0A6",
"FM2_Op2_FNum1", "0A8",
"FM2_Op3_FNum1", "0A9",
"FM2_Op4_FNum1", "0AA",
"FM2_Op2_FNum2", "0AC",
"FM2_Op3_FNum2", "0AD",
"FM2_Op4_FNum2", "0AE",
"FM1_FB_ALG", "0B1",
"FM2_FB_ALG", "0B2",
"FM1_Pan_LFO", "0B5",
"FM2_Pan_LFO", "0B6",
// ADPCM-A
"ADPCMA_Control", "100",
"ADPCMA_MVol", "101",
"ADPCMA_Test", "102",
"ADPCMA_Ch1_Vol", "108",
"ADPCMA_Ch2_Vol", "109",
"ADPCMA_Ch3_Vol", "10A",
"ADPCMA_Ch4_Vol", "10B",
"ADPCMA_Ch5_Vol", "10C",
"ADPCMA_Ch6_Vol", "10D",
"ADPCMA_Ch1_StL", "110",
"ADPCMA_Ch2_StL", "111",
"ADPCMA_Ch3_StL", "112",
"ADPCMA_Ch4_StL", "113",
"ADPCMA_Ch5_StL", "114",
"ADPCMA_Ch6_StL", "115",
"ADPCMA_Ch1_StH", "118",
"ADPCMA_Ch2_StH", "119",
"ADPCMA_Ch3_StH", "11A",
"ADPCMA_Ch4_StH", "11B",
"ADPCMA_Ch5_StH", "11C",
"ADPCMA_Ch6_StH", "11D",
"ADPCMA_Ch1_EdL", "120",
"ADPCMA_Ch2_EdL", "121",
"ADPCMA_Ch3_EdL", "122",
"ADPCMA_Ch4_EdL", "123",
"ADPCMA_Ch5_EdL", "124",
"ADPCMA_Ch6_EdL", "125",
"ADPCMA_Ch1_EdH", "128",
"ADPCMA_Ch2_EdH", "129",
"ADPCMA_Ch3_EdH", "12A",
"ADPCMA_Ch4_EdH", "12B",
"ADPCMA_Ch5_EdH", "12C",
"ADPCMA_Ch6_EdH", "12D",
// FM (Channel 3-4)
"FM3_Op1_DT_MULT", "131",
"FM4_Op1_DT_MULT", "132",
"FM3_Op2_DT_MULT", "135",
"FM4_Op2_DT_MULT", "136",
"FM3_Op3_DT_MULT", "139",
"FM4_Op3_DT_MULT", "13A",
"FM3_Op4_DT_MULT", "13D",
"FM4_Op4_DT_MULT", "13E",
"FM3_Op1_TL", "141",
"FM4_Op1_TL", "142",
"FM3_Op2_TL", "145",
"FM4_Op2_TL", "146",
"FM3_Op3_TL", "149",
"FM4_Op3_TL", "14A",
"FM3_Op4_TL", "14D",
"FM4_Op4_TL", "14E",
"FM3_Op1_KS_AR", "151",
"FM4_Op1_KS_AR", "152",
"FM3_Op2_KS_AR", "155",
"FM4_Op2_KS_AR", "156",
"FM3_Op3_KS_AR", "159",
"FM4_Op3_KS_AR", "15A",
"FM3_Op4_KS_AR", "15D",
"FM4_Op4_KS_AR", "15E",
"FM3_Op1_AM_DR", "161",
"FM4_Op1_AM_DR", "162",
"FM3_Op2_AM_DR", "165",
"FM4_Op2_AM_DR", "166",
"FM3_Op3_AM_DR", "169",
"FM4_Op3_AM_DR", "16A",
"FM3_Op4_AM_DR", "16D",
"FM4_Op4_AM_DR", "16E",
"FM3_Op1_SR", "171",
"FM4_Op1_SR", "172",
"FM3_Op2_SR", "175",
"FM4_Op2_SR", "176",
"FM3_Op3_SR", "179",
"FM4_Op3_SR", "17A",
"FM3_Op4_SR", "17D",
"FM4_Op4_SR", "17E",
"FM3_Op1_SL_RR", "181",
"FM4_Op1_SL_RR", "182",
"FM3_Op2_SL_RR", "185",
"FM4_Op2_SL_RR", "186",
"FM3_Op3_SL_RR", "189",
"FM4_Op3_SL_RR", "18A",
"FM3_Op4_SL_RR", "18D",
"FM4_Op4_SL_RR", "18E",
"FM3_Op1_SSG_EG", "191",
"FM4_Op1_SSG_EG", "192",
"FM3_Op2_SSG_EG", "195",
"FM4_Op2_SSG_EG", "196",
"FM3_Op3_SSG_EG", "199",
"FM4_Op3_SSG_EG", "19A",
"FM3_Op4_SSG_EG", "19D",
"FM4_Op4_SSG_EG", "19E",
"FM3_FNum1", "1A1",
"FM4_FNum1", "1A2",
"FM3_FNum2", "1A5",
"FM4_FNum2", "1A6",
"FM3_FB_ALG", "1B1",
"FM4_FB_ALG", "1B2",
"FM3_Pan_LFO", "1B5",
"FM4_Pan_LFO", "1B6",
NULL
};
const char** DivPlatformYM2610::getRegisterSheet() {
return regCheatSheetYM2610;
}
const char* DivPlatformYM2610::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
@ -486,7 +698,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,0x80); // start
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
@ -512,7 +724,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,0x80); // start
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
}

View file

@ -35,6 +35,10 @@ class DivYM2610Interface: public ymfm::ymfm_interface {
class DivPlatformYM2610: public DivDispatch {
protected:
const unsigned short chanOffs[4]={
0x01, 0x02, 0x101, 0x102
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
@ -123,6 +127,7 @@ class DivPlatformYM2610: public DivDispatch {
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();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,129 @@
/**
* 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 _YM2610B_H
#define _YM2610B_H
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ym2610.h"
class DivPlatformYM2610B: public DivDispatch {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins, psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM;
int vol, outVol;
unsigned char pan;
DivMacroInt std;
Channel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
note(0),
ins(-1),
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
inPorta(false),
furnacePCM(false),
vol(0),
outVol(15),
pan(3) {}
};
Channel chan[16];
bool isMuted[16];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2610b* fm;
ymfm::ym2610b::output_data fmout;
DivYM2610Interface iface;
unsigned char regPool[512];
unsigned char lastBusy;
bool dacMode;
int dacPeriod;
int dacRate;
int dacPos;
int dacSample;
int ayNoiseFreq;
unsigned char sampleBank;
int delay;
bool extMode;
short oldWrites[512];
short pendingWrites[512];
unsigned char ayEnvMode;
unsigned short ayEnvPeriod;
short ayEnvSlideLow;
short ayEnvSlide;
int octave(int freq);
int toFreq(int freq);
double NOTE_ADPCMB(int note);
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();
void reset();
void forceIns();
void tick();
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
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();
~DivPlatformYM2610B();
};
#endif

View file

@ -0,0 +1,337 @@
/**
* 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 "ym2610bext.h"
#include "../engine.h"
#include <math.h>
#include "ym2610shared.h"
int DivPlatformYM2610BExt::dispatch(DivCommand c) {
if (c.chan<2) {
return DivPlatformYM2610B::dispatch(c);
}
if (c.chan>5) {
c.chan-=3;
return DivPlatformYM2610B::dispatch(c);
}
int ch=c.chan-2;
int ordch=orderedOps[ch];
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
}
}
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
opChan[ch].active=true;
break;
}
case DIV_CMD_NOTE_OFF:
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
}
break;
}
case DIV_CMD_GET_VOLUME: {
return opChan[ch].vol;
break;
}
case DIV_CMD_INSTRUMENT:
if (opChan[ch].ins!=c.value || c.value2==1) {
opChan[ch].insChanged=true;
}
opChan[ch].ins=c.value;
break;
case DIV_CMD_PANNING: {
switch (c.value) {
case 0x01:
opChan[ch].pan=1;
break;
case 0x10:
opChan[ch].pan=2;
break;
default:
opChan[ch].pan=3;
break;
}
DivInstrument* ins=parent->getIns(opChan[ch].ins);
// TODO: ???
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
opChan[ch].pitch=c.value;
opChan[ch].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
int newFreq;
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
if (!opChan[ch].portaPause) {
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
opChan[ch].portaPause=true;
break;
}
}
opChan[ch].baseFreq=newFreq;
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
if (return2) return 2;
break;
}
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].freqChanged=true;
break;
}
case DIV_CMD_FM_MULT: { // TODO
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,c.value2);
}
break;
}
case DIV_CMD_FM_AR: {
DivInstrument* ins=parent->getIns(opChan[ch].ins);
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator op=ins->fm.op[i];
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6));
}
} else {
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6));
}
break;
}
case DIV_CMD_GET_VOLMAX:
return 127;
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
case DIV_CMD_PRE_PORTA:
break;
default:
//printf("WARNING: unimplemented command %d\n",c.cmd);
break;
}
return 1;
}
static int opChanOffsL[4]={
0xa9, 0xaa, 0xa8, 0xa2
};
static int opChanOffsH[4]={
0xad, 0xae, 0xac, 0xa6
};
void DivPlatformYM2610BExt::tick() {
if (extMode) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
opChan[i].keyOff=false;
}
}
if (writeSomething) {
immWrite(0x28,writeMask);
}
}
DivPlatformYM2610B::tick();
bool writeNoteOn=false;
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch);
if (opChan[i].freq>262143) opChan[i].freq=262143;
int freqt=toFreq(opChan[i].freq);
opChan[i].freqH=freqt>>8;
opChan[i].freqL=freqt&0xff;
immWrite(opChanOffsH[i],opChan[i].freqH);
immWrite(opChanOffsL[i],opChan[i].freqL);
opChan[i].freqChanged=false;
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
opChan[i].keyOn=false;
}
}
if (writeNoteOn) {
immWrite(0x28,writeMask);
}
}
void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) {
if (ch<2) {
DivPlatformYM2610B::muteChannel(ch,mute);
return;
}
if (ch>5) {
DivPlatformYM2610B::muteChannel(ch-3,mute);
return;
}
isOpMuted[ch-2]=mute;
int ordch=orderedOps[ch-2];
DivInstrument* ins=parent->getIns(opChan[ch-2].ins);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch-2]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
}
void DivPlatformYM2610BExt::forceIns() {
DivPlatformYM2610B::forceIns();
for (int i=0; i<4; i++) {
opChan[i].insChanged=true;
if (opChan[i].active) {
opChan[i].keyOn=true;
opChan[i].freqChanged=true;
}
}
}
void* DivPlatformYM2610BExt::getChanState(int ch) {
if (ch>=6) return &chan[ch-3];
if (ch>=2) return &opChan[ch-2];
return &chan[ch];
}
void DivPlatformYM2610BExt::reset() {
DivPlatformYM2610B::reset();
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformYM2610BExt::OpChannel();
opChan[i].vol=127;
}
// channel 2 mode
immWrite(0x27,0x40);
extMode=true;
}
bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) {
return (ch>8);
}
void DivPlatformYM2610BExt::notifyInsChange(int ins) {
DivPlatformYM2610B::notifyInsChange(ins);
for (int i=0; i<4; i++) {
if (opChan[i].ins==ins) {
opChan[i].insChanged=true;
}
}
}
int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) {
DivPlatformYM2610B::init(parent,channels,sugRate,flags);
for (int i=0; i<4; i++) {
isOpMuted[i]=false;
}
reset();
return 19;
}
void DivPlatformYM2610BExt::quit() {
DivPlatformYM2610B::quit();
}
DivPlatformYM2610BExt::~DivPlatformYM2610BExt() {
}

View file

@ -0,0 +1,51 @@
/**
* 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 "../dispatch.h"
#include "ym2610b.h"
class DivPlatformYM2610BExt: public DivPlatformYM2610B {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch;
unsigned char ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];
friend void putDispatchChan(void*,int,int);
public:
int dispatch(DivCommand c);
void* getChanState(int chan);
void reset();
void forceIns();
void tick();
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2610BExt();
};

View file

@ -147,10 +147,6 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
if (return2) return 2;
break;
}
case DIV_CMD_SAMPLE_MODE: {
// ignored on extended channel 2 mode.
break;
}
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].freqChanged=true;

View file

@ -17,9 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
static unsigned short chanOffs[4]={
0x01, 0x02, 0x101, 0x102
};
static unsigned short opOffs[4]={
0x00, 0x04, 0x08, 0x0c
};
@ -45,4 +42,4 @@ static int orderedOps[4]={
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_FREQBASE 9440540
#define CHIP_FREQBASE 9440540

View file

@ -264,7 +264,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
return true;
}
#define IS_YM2610 (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT)
#define IS_YM2610 (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT)
bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) {
switch (sysOfChan[ch]) {
@ -275,6 +275,8 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
switch (effect) {
case 0x10: // LFO or noise mode
if (sysOfChan[ch]==DIV_SYSTEM_YM2151) {

View file

@ -90,6 +90,7 @@ enum DivSystem {
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_LYNX,
DIV_SYSTEM_QSOUND,
DIV_SYSTEM_YM2610B_EXT,
DIV_SYSTEM_SEGAPCM_COMPAT
};

View file

@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
return DIV_SYSTEM_LYNX;
case 0xa9:
return DIV_SYSTEM_SEGAPCM_COMPAT;
case 0xde:
return DIV_SYSTEM_YM2610B_EXT;
case 0xe0:
return DIV_SYSTEM_QSOUND;
}
@ -256,6 +258,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
return 0xa8;
case DIV_SYSTEM_SEGAPCM_COMPAT:
return 0xa9;
case DIV_SYSTEM_YM2610B_EXT:
return 0xde;
case DIV_SYSTEM_QSOUND:
return 0xe0;
@ -376,6 +380,7 @@ int DivEngine::getChannelCount(DivSystem sys) {
return 4;
case DIV_SYSTEM_SEGAPCM_COMPAT:
return 5;
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_QSOUND:
return 19;
}
@ -629,6 +634,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "Atari Lynx";
case DIV_SYSTEM_SEGAPCM_COMPAT:
return "SegaPCM (compatible 5-channel mode)";
case DIV_SYSTEM_YM2610B_EXT:
return "Taito Arcade Extended Channel 3";
case DIV_SYSTEM_QSOUND:
return "Capcom QSound";
}
@ -752,6 +759,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
return "Mikey";
case DIV_SYSTEM_SEGAPCM_COMPAT:
return "SegaPCM (compatible 5-channel mode)";
case DIV_SYSTEM_YM2610B_EXT:
return "Yamaha YM2610B Extended Channel 3";
case DIV_SYSTEM_QSOUND:
return "Capcom DL-1425";
}
@ -822,6 +831,8 @@ bool DivEngine::isFMSystem(DivSystem sys) {
sys==DIV_SYSTEM_YM2610_EXT ||
sys==DIV_SYSTEM_YM2610_FULL ||
sys==DIV_SYSTEM_YM2610_FULL_EXT ||
sys==DIV_SYSTEM_YM2610B ||
sys==DIV_SYSTEM_YM2610B_EXT ||
sys==DIV_SYSTEM_YMU759 ||
sys==DIV_SYSTEM_YM2151 ||
sys==DIV_SYSTEM_YM2612);
@ -834,7 +845,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
sys!=DIV_SYSTEM_YM2151);
}
const char* chanNames[37][24]={
const char* chanNames[38][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)
@ -872,9 +883,10 @@ const char* chanNames[37][24]={
{"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
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3)
};
const char* chanShortNames[37][24]={
const char* chanShortNames[38][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)
@ -912,9 +924,10 @@ const char* chanShortNames[37][24]={
{"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
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3)
};
const int chanTypes[37][24]={
const int chanTypes[38][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3)
@ -952,9 +965,10 @@ const int chanTypes[37][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op
{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums
{3, 3, 3, 3}, //Lynx
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3)
};
const DivInstrumentType chanPrefType[43][24]={
const DivInstrumentType chanPrefType[44][24]={
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3)
@ -998,6 +1012,7 @@ const DivInstrumentType chanPrefType[43][24]={
{DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, // Swan
{DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z
{DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, 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 (extended channel 3)
};
const char* DivEngine::getChannelName(int chan) {
@ -1115,6 +1130,9 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_YM2610B:
return chanNames[31][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2610B_EXT:
return chanNames[37][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_OPL_DRUMS:
case DIV_SYSTEM_OPL2_DRUMS:
@ -1251,6 +1269,9 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_YM2610B:
return chanShortNames[31][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2610B_EXT:
return chanShortNames[37][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_OPL_DRUMS:
case DIV_SYSTEM_OPL2_DRUMS:
@ -1385,6 +1406,9 @@ int DivEngine::getChannelType(int chan) {
case DIV_SYSTEM_YM2610B:
return chanTypes[31][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2610B_EXT:
return chanTypes[37][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_OPL_DRUMS:
case DIV_SYSTEM_OPL2_DRUMS:
@ -1519,6 +1543,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
case DIV_SYSTEM_YM2610B:
return chanPrefType[31][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2610B_EXT:
return chanPrefType[43][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_OPLL_DRUMS:
return chanPrefType[32][dispatchChanOfChan[chan]];
break;
@ -1564,6 +1591,10 @@ bool DivEngine::isVGMExportable(DivSystem which) {
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
case DIV_SYSTEM_SAA1099:

View file

@ -152,8 +152,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT:
for (int i=0; i<2; i++) { // set SL and RR to highest
w->writeC(isSecond?0xa8:0x58);
w->writeC(0x81+i);
@ -376,8 +378,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(isSecond?0xa8:0x58);
@ -611,8 +615,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT:
if (!hasOPNB) {
hasOPNB=disCont[i].dispatch->chipClock;
willExport[i]=true;
@ -623,6 +629,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
hasOPNB|=0x40000000;
howManyChips++;
}
if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag
hasOPNB|=0x80000000;
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
@ -895,6 +904,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->write(adpcmAMem,adpcmAMemLen);
}
if (writeADPCM && adpcmBMemLen>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x83);
w->writeI(adpcmBMemLen+8);
w->writeI(adpcmBMemLen);
w->writeI(0);
w->write(adpcmBMem,adpcmBMemLen);
}
if (writeQSound && qsoundMemLen>0) {
// always write a whole bank
unsigned int blockSize=(qsoundMemLen+0xffff)&(~0xffff);

View file

@ -29,6 +29,8 @@
#include "../engine/platform/arcade.h"
#include "../engine/platform/ym2610.h"
#include "../engine/platform/ym2610ext.h"
#include "../engine/platform/ym2610b.h"
#include "../engine/platform/ym2610bext.h"
#include "../engine/platform/ay.h"
#include "../engine/platform/ay8930.h"
#include "../engine/platform/tia.h"

View file

@ -1309,18 +1309,25 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("notes:");
if (sample->loopStart>=0) {
considerations=true;
ImGui::Text("- sample won't loop on Neo Geo ADPCM");
ImGui::Text("- sample won't loop on Neo Geo ADPCM-A");
if (sample->loopStart&1) {
ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga");
}
if (sample->loopStart>0) {
ImGui::Text("- sample loop start will be ignored on Neo Geo ADPCM-B");
}
}
if (sample->samples&1) {
considerations=true;
ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga");
}
if (sample->samples&511) {
considerations=true;
ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM.");
}
if (sample->samples>65535) {
considerations=true;
ImGui::Text("- maximum sample length on Sega PCM is 65536 samples");
ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples");
}
if (sample->samples>2097151) {
considerations=true;
@ -4549,6 +4556,8 @@ bool FurnaceGUI::loop() {
sysAddOption(DIV_SYSTEM_YM2610_EXT);
sysAddOption(DIV_SYSTEM_YM2610_FULL);
sysAddOption(DIV_SYSTEM_YM2610_FULL_EXT);
sysAddOption(DIV_SYSTEM_YM2610B);
sysAddOption(DIV_SYSTEM_YM2610B_EXT);
sysAddOption(DIV_SYSTEM_AY8910);
sysAddOption(DIV_SYSTEM_AMIGA);
sysAddOption(DIV_SYSTEM_OPLL);
@ -4828,6 +4837,8 @@ bool FurnaceGUI::loop() {
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YMU759:
ImGui::Text("nothing to configure");
break;
@ -4861,6 +4872,8 @@ bool FurnaceGUI::loop() {
sysChangeOption(i,DIV_SYSTEM_YM2610_EXT);
sysChangeOption(i,DIV_SYSTEM_YM2610_FULL);
sysChangeOption(i,DIV_SYSTEM_YM2610_FULL_EXT);
sysChangeOption(i,DIV_SYSTEM_YM2610B);
sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT);
sysChangeOption(i,DIV_SYSTEM_AY8910);
sysChangeOption(i,DIV_SYSTEM_AMIGA);
sysChangeOption(i,DIV_SYSTEM_OPLL);