mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-16 09:45:06 +00:00
a6e4345863
CRASHES EVERYWHERE
322 lines
9 KiB
C++
322 lines
9 KiB
C++
/**
|
|
* 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 _YM2610SHARED_H
|
|
#define _YM2610SHARED_H
|
|
#include "fmshared_OPN.h"
|
|
#include "../macroInt.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) {}
|
|
};
|
|
|
|
template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
|
|
protected:
|
|
struct Channel {
|
|
DivInstrumentFM state;
|
|
unsigned char freqH, freqL;
|
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
|
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
|
signed char konCycles;
|
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset, opMaskChanged;
|
|
int vol, outVol;
|
|
int sample;
|
|
unsigned char pan, opMask;
|
|
int macroVolMul;
|
|
DivMacroInt std;
|
|
void macroInit(DivInstrument* which) {
|
|
std.init(which);
|
|
pitch2=0;
|
|
}
|
|
Channel():
|
|
freqH(0),
|
|
freqL(0),
|
|
freq(0),
|
|
baseFreq(0),
|
|
pitch(0),
|
|
pitch2(0),
|
|
portaPauseFreq(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),
|
|
hardReset(false),
|
|
opMaskChanged(false),
|
|
vol(0),
|
|
outVol(15),
|
|
sample(-1),
|
|
pan(3),
|
|
opMask(15),
|
|
macroVolMul(255) {}
|
|
};
|
|
|
|
struct OpChannel {
|
|
DivMacroInt std;
|
|
unsigned char freqH, freqL;
|
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
|
signed char konCycles;
|
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
|
int vol;
|
|
unsigned char pan;
|
|
// UGLY
|
|
OpChannel():
|
|
freqH(0),
|
|
freqL(0),
|
|
freq(0),
|
|
baseFreq(0),
|
|
pitch(0),
|
|
pitch2(0),
|
|
portaPauseFreq(0),
|
|
ins(-1),
|
|
active(false),
|
|
insChanged(true),
|
|
freqChanged(false),
|
|
keyOn(false),
|
|
keyOff(false),
|
|
portaPause(false),
|
|
inPorta(false),
|
|
mask(true),
|
|
vol(0),
|
|
pan(3) {}
|
|
};
|
|
Channel chan[ChanNum];
|
|
DivDispatchOscBuffer* oscBuf[ChanNum];
|
|
bool isMuted[ChanNum];
|
|
|
|
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;
|
|
|
|
unsigned char writeADPCMAOff, writeADPCMAOn;
|
|
int globalADPCMAVolume;
|
|
|
|
const int extChanOffs, psgChanOffs, adpcmAChanOffs, adpcmBChanOffs;
|
|
const int chanNum=ChanNum;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool isStereo() {
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
size_t getSampleMemUsage(int index) {
|
|
return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0;
|
|
}
|
|
|
|
void renderSamples() {
|
|
memset(adpcmAMem,0,getSampleMemCapacity(0));
|
|
memset(sampleOffA,0,256*sizeof(unsigned int));
|
|
memset(sampleOffB,0,256*sizeof(unsigned int));
|
|
|
|
size_t memPos=0;
|
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
|
DivSample* s=parent->song.sample[i];
|
|
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);
|
|
}
|
|
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];
|
|
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);
|
|
}
|
|
sampleOffB[i]=memPos;
|
|
memPos+=paddedLen;
|
|
}
|
|
adpcmBMemLen=memPos+256;
|
|
}
|
|
|
|
void setFlags(unsigned int flags) {
|
|
switch (flags&0xff) {
|
|
default:
|
|
case 0x00:
|
|
chipClock=8000000.0;
|
|
break;
|
|
case 0x01:
|
|
chipClock=24167829/3;
|
|
break;
|
|
}
|
|
rate=chipClock/16;
|
|
for (int i=0; i<ChanNum; i++) {
|
|
oscBuf[i]->rate=rate;
|
|
}
|
|
}
|
|
|
|
int init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
|
parent=p;
|
|
dumpWrites=false;
|
|
skipRegisterWrites=false;
|
|
for (int i=0; i<ChanNum; 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_MAX);
|
|
setFlags(flags);
|
|
// YM2149, 2MHz
|
|
ay=new DivPlatformAY8910(true,chipClock,32);
|
|
ay->init(p,3,sugRate,16);
|
|
ay->toggleRegisterDump(true);
|
|
return 0;
|
|
}
|
|
|
|
void quit() {
|
|
for (int i=0; i<ChanNum; i++) {
|
|
delete oscBuf[i];
|
|
}
|
|
ay->quit();
|
|
delete ay;
|
|
delete[] adpcmAMem;
|
|
delete[] adpcmBMem;
|
|
}
|
|
|
|
DivPlatformYM2610Base(int ext, int psg, int adpcmA, int adpcmB):
|
|
DivPlatformOPN(9440540.0, 72, 32),
|
|
extChanOffs(ext),
|
|
psgChanOffs(psg),
|
|
adpcmAChanOffs(adpcmA),
|
|
adpcmBChanOffs(adpcmB) {}
|
|
};
|
|
|
|
#endif
|