Prepare for SM8521

This commit is contained in:
cam900 2023-02-11 21:06:17 +09:00
parent c0190b81cb
commit 6c5e806fb2
3 changed files with 741 additions and 0 deletions

View File

@ -0,0 +1,382 @@
/**
* 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.
*/
#include "sm8521.h"
#include "../engine.h"
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 16
const char* regCheatSheetSM8521[]={
"SGC", "40",
"SG0L", "42",
"SG1L", "44",
"SG0TL", "46",
"SG0TH", "47",
"SG1TL", "48",
"SG1TH", "49",
"SG2L", "4A",
"SG2TL", "4C",
"SG2TH", "4D",
"SGDA", "4E",
"SG0Wn", "60+n",
"SG1Wn", "70+n",
NULL
};
const char** DivPlatformSM8521::getRegisterSheet() {
return regCheatSheetSM8521;
}
void DivPlatformSM8521::acquire(short** buf, size_t len) {
while (!writes.empty()) {
QueuedWrite w=writes.front();
sm8521_write(&sm8521,w.addr,w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
}
for (size_t h=0; h<len; h++) {
sm8521_sound_tick(&sm8521,1);
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out;
}
oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out;
}
}
void DivPlatformSM8521::updateWave(int ch) {
if (ch<2) {
const unsigned char temp=regPool[0x40];
rWrite(0x40,temp&~(1<<ch));
for (int i=0; i<16; i++) {
int nibble1=15-chan[ch].ws.output[((i<<1)+chan[ch].antiClickWavePos-1)&31];
int nibble2=15-chan[ch].ws.output[((1+(i<<1))+chan[ch].antiClickWavePos-1)&31];
rWrite(0x60+i+(ch*16),(nibble2<<4)|nibble1);
}
rWrite(0x40,temp|(1<<ch));
chan[ch].antiClickWavePos&=31;
}
}
void DivPlatformSM8521::tick(bool sysTick) {
for (int i=0; i<3; i++) {
// anti-click
if (antiClickEnabled && sysTick && chan[i].freq>0) {
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
chan[i].antiClickPeriodCount%=chan[i].freq;
}
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&31)*MIN(31,chan[i].std.vol.val))>>5;
if (!isMuted[i]) {
chan[i].volumeChanged=true;
}
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].volumeChanged) {
if (isMuted[i]) {
rWrite(volMap[i],0);
} else {
rWrite(volMap[i],chan[i].outVol&0x1f);
}
chan[i].volumeChanged=false;
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>4095) chan[i].freq=4095;
rWrite(freqMap[i][0],chan[i].freq>>8);
rWrite(freqMap[i][1],chan[i].freq&0xff);
const unsigned char temp=regPool[0x40];
if (chan[i].keyOn) {
rWrite(0x40,temp|(1<<i));
}
if (chan[i].keyOff) {
rWrite(0x40,temp&~(1<<i));
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformSM8521::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(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;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:8);
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:8);
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:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(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].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 31;
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);
break;
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformSM8521::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].volumeChanged=true;
}
void DivPlatformSM8521::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
updateWave(i);
}
}
void* DivPlatformSM8521::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformSM8521::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformSM8521::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformSM8521::getRegisterPool() {
return regPool;
}
int DivPlatformSM8521::getRegisterPoolSize() {
return 256;
}
void DivPlatformSM8521::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,256);
for (int i=0; i<3; i++) {
chan[i]=DivPlatformSM8521::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,15,false);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
sm8521_reset(&sm8521);
sm8521_write(&sm8521,0x40,0x80); // initialize SGC
}
int DivPlatformSM8521::getOutputCount() {
return 1;
}
bool DivPlatformSM8521::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformSM8521::notifyWaveChange(int wave) {
for (int i=0; i<2; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}
}
void DivPlatformSM8521::notifyInsDeletion(void* ins) {
for (int i=0; i<3; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformSM8521::setFlags(const DivConfig& flags) {
chipClock=10000000;
CHECK_CUSTOM_CLOCK;
rate=chipClock/4;
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;
}
}
void DivPlatformSM8521::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformSM8521::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformSM8521::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<3; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 6;
}
void DivPlatformSM8521::quit() {
for (int i=0; i<3; i++) {
delete oscBuf[i];
}
}
DivPlatformSM8521::~DivPlatformSM8521() {
}

View File

@ -0,0 +1,86 @@
/**
* 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 _NAMCOWSG_H
#define _NAMCOWSG_H
#include "../dispatch.h"
#include <queue>
#include "../waveSynth.h"
#include "sound/sm8521.h"
class DivPlatformSM8521: public DivDispatch {
const unsigned char volMap[3]={0x42,0x44,0x4a};
const unsigned char freqMap[3][2]={{0x46,0x47},{0x48,0x49},{0x4c,0x4d}};
struct Channel: public SharedChannel<signed char> {
int antiClickPeriodCount, antiClickWavePos;
signed short wave;
bool volumeChanged;
DivWaveSynth ws;
Channel():
SharedChannel<signed char>(15),
antiClickPeriodCount(0),
antiClickWavePos(0),
wave(-1),
volumeChanged(false) {}
};
Channel chan[3];
DivDispatchOscBuffer* oscBuf[3];
bool isMuted[3];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
bool antiClickEnabled;
struct sm8521_t sm8521;
unsigned char regPool[256];
void updateWave(int ch);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
bool keyOffAffectsArp(int ch);
void setFlags(const DivConfig& flags);
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();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformSM8521();
};
#endif

View File

@ -0,0 +1,273 @@
/*
============================================================================
SM8521 sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2022-present cam900 and contributors
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
============================================================================
TODO:
- needs hardware test
*/
#include <stdlib.h>
#ifdef __cplusplus
extern "C"
{
#endif
enum sm8521_sgc
{
SM8521_SGC_SONDOUT = (1 << 7),
SM8521_SGC_DIROUT = (1 << 3),
SM8521_SGC_SG2OUT = (1 << 2),
SM8521_SGC_SG1OUT = (1 << 1),
SM8521_SGC_SG0OUT = (1 << 0)
};
struct sm8521_sg_t
{
unsigned short t; // Time constant register
unsigned char level; // Output level control register
unsigned short out; // output
int counter; // clock counter
};
struct sm8521_wave_t
{
struct sm8521_sg_t base;
unsigned char addr; // waveform address
unsigned char wave[16]; // 4 bit waveform (32 nybbles)
};
struct sm8521_noise_t
{
struct sm8521_sg_t base;
unsigned int lfsr; // LFSR
};
struct sm8521_t
{
struct sm8521_wave_t sg[2];
struct sm8521_noise_t noise;
signed short out; // output
signed char sgda; // D/A direct output register (write only)
unsigned char sgc; // Control register
};
void sm8521_sg_wave_tick(struct sm8521_wave_t *sg, const int cycle)
{
sg->base.counter += cycle;
while (sg->base.counter >= (sg->base.t + 1))
{
sg->addr++;
sg->base.counter -= (sg->base.t + 1);
}
int wave = (sg->wave[(sg->addr >> 1) & 0xf] >> ((sg->addr & 1) << 2)) & 0xf;
if (wave & 0x8)
{
wave = -(0x8 - (wave & 0x7));
}
sg->base.out = (wave * sg->base.level) >> 1; // scale out to 8bit
}
void sm8521_noise_tick(struct sm8521_noise_t *noise, const int cycle)
{
noise->base.counter += cycle;
while (noise->base.counter >= (noise->base.t + 1))
{
noise->lfsr = rand() & 0x1; // unknown algorithm
noise->base.counter -= (noise->base.t + 1);
}
noise->base.out = (((noise->lfsr & 0x1) ? 7 : -8) * noise->base.level) >> 1; // scale out to 8bit
}
void sm8521_sound_tick(struct sm8521_t *sm8521, const int cycle)
{
int out = 0;
if (sm8521->sgc & SM8521_SGC_SONDOUT)
{
if (sm8521->sgc & SM8521_SGC_DIROUT)
{
out = sm8521->sgda - 0x80;
}
else
{
if (sm8521->sgc & SM8521_SGC_SG0OUT)
{
sm8521_sg_wave_tick(&sm8521->sg[0], cycle);
out += sm8521->sg[0].base.out;
}
if (sm8521->sgc & SM8521_SGC_SG1OUT)
{
sm8521_sg_wave_tick(&sm8521->sg[1], cycle);
out += sm8521->sg[1].base.out;
}
if (sm8521->sgc & SM8521_SGC_SG2OUT)
{
sm8521_noise_tick(&sm8521->noise, cycle);
out += sm8521->noise.base.out;
}
out = (out < -0x80) ? -0x80 : ((out > 0x7f) ? 0x7f : out); // clamp
}
}
sm8521->out = out;
}
void sm8521_reset(struct sm8521_t *sm8521)
{
for (int i = 0; i < 2; i++)
{
sm8521->sg[i].base.t = 0;
sm8521->sg[i].base.level = 0;
sm8521->sg[i].base.out = 0;
sm8521->sg[i].base.counter = 0;
sm8521->sg[i].addr = 0;
for (int j = 0; j < 16; j++)
{
sm8521->sg[i].wave[j] = 0;
}
}
sm8521->noise.base.t = 0;
sm8521->noise.base.level = 0;
sm8521->noise.base.out = 0;
sm8521->noise.base.counter = 0;
sm8521->noise.lfsr = 0;
sm8521->out = 0;
sm8521->sgda = 0;
sm8521->sgc = 0;
}
unsigned char sm8521_read(struct sm8521_t *sm8521, const unsigned char a)
{
if ((a & 0xe0) == 0x60)
{
if ((a & 0x10) && (!(sm8521->sgc & SM8521_SGC_SG1OUT))) // 0x70-0x7f SG1W0-15
{
return sm8521->sg[1].wave[a & 0xf];
}
else if ((!(a & 0x10)) && (!(sm8521->sgc & SM8521_SGC_SG0OUT))) // 0x60-0x6f SG0W0-15
{
return sm8521->sg[0].wave[a & 0xf];
}
return 0;
}
switch (a)
{
case 0x40: // SGC
return sm8521->sgc;
break;
case 0x42: // SG0L
return sm8521->sg[0].base.level & 0x1f;
break;
case 0x44: // SG1L
return sm8521->sg[1].base.level & 0x1f;
break;
case 0x46: // SG0TH
return (sm8521->sg[0].base.t >> 8) & 0xf;
break;
case 0x47: // SG0TL
return sm8521->sg[0].base.t & 0xff;
break;
case 0x48: // SG1TH
return (sm8521->sg[1].base.t >> 8) & 0xf;
break;
case 0x49: // SG1TL
return sm8521->sg[1].base.t & 0x0ff;
break;
case 0x4a: // SG2L
return sm8521->noise.base.level & 0x1f;
break;
case 0x4c: // SG2TH
return (sm8521->noise.base.t >> 8) & 0xf;
break;
case 0x4d: // SG2TL
return sm8521->noise.base.t & 0xff;
break;
default:
return 0;
break;
}
}
void sm8521_write(struct sm8521_t *sm8521, const unsigned char a, const unsigned char d)
{
if ((a & 0xe0) == 0x60)
{
if ((a & 0x10) && (!(sm8521->sgc & SM8521_SGC_SG1OUT))) // 0x70-0x7f SG1W0-15
{
sm8521->sg[1].wave[a & 0xf] = d;
}
else if ((!(a & 0x10)) && (!(sm8521->sgc & SM8521_SGC_SG0OUT))) // 0x60-0x6f SG0W0-15
{
sm8521->sg[0].wave[a & 0xf] = d;
}
return;
}
switch (a)
{
case 0x40: // SGC
sm8521->sgc = d;
break;
case 0x42: // SG0L
sm8521->sg[0].base.level = d & 0x1f;
break;
case 0x44: // SG1L
sm8521->sg[1].base.level = d & 0x1f;
break;
case 0x46: // SG0TH
sm8521->sg[0].base.t = (sm8521->sg[0].base.t & 0x0ff) | ((d << 8) & 0xf00);
break;
case 0x47: // SG0TL
sm8521->sg[0].base.t = (sm8521->sg[0].base.t & 0xf00) | (d & 0x0ff);
break;
case 0x48: // SG1TH
sm8521->sg[1].base.t = (sm8521->sg[1].base.t & 0x0ff) | ((d << 8) & 0xf00);
break;
case 0x49: // SG1TL
sm8521->sg[1].base.t = (sm8521->sg[1].base.t & 0xf00) | (d & 0x0ff);
break;
case 0x4a: // SG2L
sm8521->noise.base.level = d & 0x1f;
break;
case 0x4c: // SG2TH
sm8521->noise.base.t = (sm8521->noise.base.t & 0x0ff) | ((d << 8) & 0xf00);
break;
case 0x4d: // SG2TL
sm8521->noise.base.t = (sm8521->noise.base.t & 0xf00) | (d & 0x0ff);
break;
case 0x4e: // SGDA
sm8521->sgda = d;
break;
}
}
#ifdef __cplusplus
} // extern "C"
#endif