mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-14 00:35:06 +00:00
4daaaa4f2f
issue #1098
273 lines
7.9 KiB
C++
273 lines
7.9 KiB
C++
/**
|
|
* Furnace Tracker - multi-system chiptune tracker
|
|
* Copyright (C) 2021-2023 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 _YM2610SHARED_H
|
|
#define _YM2610SHARED_H
|
|
|
|
#include "fmshared_OPN.h"
|
|
#include "../engine.h"
|
|
#include "../../ta-log.h"
|
|
#include "ay.h"
|
|
#include "sound/ymfm/ymfm.h"
|
|
#include "sound/ymfm/ymfm_opn.h"
|
|
#include <string.h>
|
|
|
|
#define CHIP_FREQBASE fmFreqBase
|
|
#define CHIP_DIVIDER fmDivBase
|
|
|
|
class DivYM2610Interface: public ymfm::ymfm_interface {
|
|
public:
|
|
unsigned char* adpcmAMem;
|
|
unsigned char* adpcmBMem;
|
|
int sampleBank;
|
|
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
|
|
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
|
|
DivYM2610Interface():
|
|
adpcmAMem(NULL),
|
|
adpcmBMem(NULL),
|
|
sampleBank(0) {}
|
|
};
|
|
|
|
class DivPlatformYM2610Base: public DivPlatformOPN {
|
|
protected:
|
|
OPNChannelStereo chan[16];
|
|
DivDispatchOscBuffer* oscBuf[16];
|
|
bool isMuted[16];
|
|
|
|
ym3438_t fm_nuked;
|
|
ymfm::ym2610b* fm;
|
|
ymfm::ym2610b::output_data fmout;
|
|
DivPlatformAY8910* ay;
|
|
|
|
unsigned char* adpcmAMem;
|
|
size_t adpcmAMemLen;
|
|
unsigned char* adpcmBMem;
|
|
size_t adpcmBMemLen;
|
|
DivYM2610Interface iface;
|
|
|
|
unsigned int sampleOffA[256];
|
|
unsigned int sampleOffB[256];
|
|
|
|
unsigned char sampleBank;
|
|
|
|
bool extMode, noExtMacros;
|
|
|
|
bool sampleLoaded[2][256];
|
|
|
|
unsigned char writeADPCMAOff, writeADPCMAOn;
|
|
int globalADPCMAVolume;
|
|
|
|
double NOTE_OPNB(int ch, int note) {
|
|
if (ch>=adpcmBChanOffs) { // ADPCM
|
|
return NOTE_ADPCMB(note);
|
|
} else if (ch>=psgChanOffs) { // PSG
|
|
return NOTE_PERIODIC(note);
|
|
}
|
|
// FM
|
|
return NOTE_FNUM_BLOCK(note,11);
|
|
}
|
|
double NOTE_ADPCMB(int note) {
|
|
if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].sample<parent->song.sampleLen) {
|
|
double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/8363.0;
|
|
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public:
|
|
void reset() {
|
|
writeADPCMAOff=0;
|
|
writeADPCMAOn=0;
|
|
globalADPCMAVolume=0x3f;
|
|
|
|
OPN2_Reset(&fm_nuked);
|
|
OPN2_SetChipType(&fm_nuked,ym3438_mode_opn);
|
|
|
|
ay->reset();
|
|
ay->getRegisterWrites().clear();
|
|
ay->flushWrites();
|
|
}
|
|
|
|
void muteChannel(int ch, bool mute) {
|
|
isMuted[ch]=mute;
|
|
if (ch>=adpcmBChanOffs) { // ADPCM-B
|
|
immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6));
|
|
}
|
|
if (ch>=adpcmAChanOffs) { // ADPCM-A
|
|
immWrite(0x108+(ch-adpcmAChanOffs),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].outVol));
|
|
return;
|
|
}
|
|
if (ch>=psgChanOffs) { // PSG
|
|
ay->muteChannel(ch-psgChanOffs,mute);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int getOutputCount() {
|
|
return 2;
|
|
}
|
|
|
|
const void* getSampleMem(int index) {
|
|
return index == 0 ? adpcmAMem : index == 1 ? adpcmBMem : NULL;
|
|
}
|
|
|
|
size_t getSampleMemCapacity(int index) {
|
|
return index == 0 ? 16777216 : index == 1 ? 16777216 : 0;
|
|
}
|
|
|
|
const char* getSampleMemName(int index=0) {
|
|
return index == 0 ? "ADPCM-A" : index == 1 ? "ADPCM-B" : NULL;
|
|
}
|
|
|
|
size_t getSampleMemUsage(int index) {
|
|
return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0;
|
|
}
|
|
|
|
bool isSampleLoaded(int index, int sample) {
|
|
if (index<0 || index>1) return false;
|
|
if (sample<0 || sample>255) return false;
|
|
return sampleLoaded[index][sample];
|
|
}
|
|
|
|
void renderSamples(int sysID) {
|
|
memset(adpcmAMem,0,getSampleMemCapacity(0));
|
|
memset(sampleOffA,0,256*sizeof(unsigned int));
|
|
memset(sampleOffB,0,256*sizeof(unsigned int));
|
|
memset(sampleLoaded,0,256*2*sizeof(bool));
|
|
|
|
size_t memPos=0;
|
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
|
DivSample* s=parent->song.sample[i];
|
|
if (!s->renderOn[0][sysID]) {
|
|
sampleOffA[i]=0;
|
|
continue;
|
|
}
|
|
|
|
int paddedLen=(s->lengthA+255)&(~0xff);
|
|
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
|
memPos=(memPos+0xfffff)&0xf00000;
|
|
}
|
|
if (memPos>=getSampleMemCapacity(0)) {
|
|
logW("out of ADPCM-A memory for sample %d!",i);
|
|
break;
|
|
}
|
|
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
|
|
memcpy(adpcmAMem+memPos,s->dataA,getSampleMemCapacity(0)-memPos);
|
|
logW("out of ADPCM-A memory for sample %d!",i);
|
|
} else {
|
|
memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
|
|
sampleLoaded[0][i]=true;
|
|
}
|
|
sampleOffA[i]=memPos;
|
|
memPos+=paddedLen;
|
|
}
|
|
adpcmAMemLen=memPos+256;
|
|
|
|
memset(adpcmBMem,0,getSampleMemCapacity(1));
|
|
|
|
memPos=0;
|
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
|
DivSample* s=parent->song.sample[i];
|
|
if (!s->renderOn[1][sysID]) {
|
|
sampleOffB[i]=0;
|
|
continue;
|
|
}
|
|
|
|
int paddedLen=(s->lengthB+255)&(~0xff);
|
|
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
|
memPos=(memPos+0xfffff)&0xf00000;
|
|
}
|
|
if (memPos>=getSampleMemCapacity(1)) {
|
|
logW("out of ADPCM-B memory for sample %d!",i);
|
|
break;
|
|
}
|
|
if (memPos+paddedLen>=getSampleMemCapacity(1)) {
|
|
memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(1)-memPos);
|
|
logW("out of ADPCM-B memory for sample %d!",i);
|
|
} else {
|
|
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
|
|
sampleLoaded[1][i]=true;
|
|
}
|
|
sampleOffB[i]=memPos;
|
|
memPos+=paddedLen;
|
|
}
|
|
adpcmBMemLen=memPos+256;
|
|
}
|
|
|
|
void setFlags(const DivConfig& flags) {
|
|
switch (flags.getInt("clockSel",0)) {
|
|
case 0x01:
|
|
chipClock=24167829/3;
|
|
break;
|
|
default:
|
|
chipClock=8000000.0;
|
|
break;
|
|
}
|
|
CHECK_CUSTOM_CLOCK;
|
|
noExtMacros=flags.getBool("noExtMacros",false);
|
|
fbAllOps=flags.getBool("fbAllOps",false);
|
|
ssgVol=flags.getInt("ssgVol",128);
|
|
fmVol=flags.getInt("fmVol",256);
|
|
rate=fm->sample_rate(chipClock);
|
|
for (int i=0; i<16; i++) {
|
|
oscBuf[i]->rate=rate;
|
|
}
|
|
}
|
|
|
|
int init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
|
parent=p;
|
|
ayFlags.set("chipType",1);
|
|
dumpWrites=false;
|
|
skipRegisterWrites=false;
|
|
for (int i=0; i<16; i++) {
|
|
isMuted[i]=false;
|
|
oscBuf[i]=new DivDispatchOscBuffer;
|
|
}
|
|
adpcmAMem=new unsigned char[getSampleMemCapacity(0)];
|
|
adpcmAMemLen=0;
|
|
adpcmBMem=new unsigned char[getSampleMemCapacity(1)];
|
|
adpcmBMemLen=0;
|
|
iface.adpcmAMem=adpcmAMem;
|
|
iface.adpcmBMem=adpcmBMem;
|
|
iface.sampleBank=0;
|
|
fm=new ymfm::ym2610b(iface);
|
|
fm->set_fidelity(ymfm::OPN_FIDELITY_MED);
|
|
setFlags(flags);
|
|
// YM2149, 2MHz
|
|
ay=new DivPlatformAY8910(true,chipClock,32,144);
|
|
ay->init(p,3,sugRate,ayFlags);
|
|
ay->toggleRegisterDump(true);
|
|
return 0;
|
|
}
|
|
|
|
void quit() {
|
|
for (int i=0; i<16; i++) {
|
|
delete oscBuf[i];
|
|
}
|
|
ay->quit();
|
|
delete ay;
|
|
delete[] adpcmAMem;
|
|
delete[] adpcmBMem;
|
|
}
|
|
|
|
DivPlatformYM2610Base(int ext, int psg, int adpcmA, int adpcmB, int chanCount):
|
|
DivPlatformOPN(ext,psg,adpcmA,adpcmB,chanCount,9440540.0, 72, 32) {}
|
|
};
|
|
|
|
#endif
|