Merge pull request #969 from tildearrow/sm8521

Add SM8521 chip support
This commit is contained in:
tildearrow 2023-02-11 19:02:17 -05:00 committed by GitHub
commit 7a04c2685c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 877 additions and 4 deletions

View file

@ -455,6 +455,8 @@ src/engine/platform/sound/snes/SPC_DSP.cpp
src/engine/platform/sound/ga20/iremga20.cpp
src/engine/platform/sound/sm8521.c
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp
@ -539,6 +541,7 @@ src/engine/platform/rf5c68.cpp
src/engine/platform/snes.cpp
src/engine/platform/k007232.cpp
src/engine/platform/ga20.cpp
src/engine/platform/sm8521.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp
)

View file

@ -284,6 +284,7 @@ size | description
| - 0xc5: YM2610B CSM - 20 channels
| - 0xc6: K007232 - 2 channels
| - 0xc7: GA20 - 4 channels
| - 0xc8: SM8521 - 3 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - 0xfc: Pong - 1 channel

View file

@ -115,6 +115,7 @@ the following instrument types are available:
- 45: K007232
- 46: GA20
- 47: Pokémon Mini
- 48: SM8521
the following feature codes are recognized:

View file

@ -75,6 +75,7 @@
#include "platform/vb.h"
#include "platform/k007232.h"
#include "platform/ga20.h"
#include "platform/sm8521.h"
#include "platform/pcmdac.h"
#include "platform/dummy.h"
#include "../ta-log.h"
@ -489,6 +490,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_GA20:
dispatch=new DivPlatformGA20;
break;
case DIV_SYSTEM_SM8521:
dispatch=new DivPlatformSM8521;
break;
case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC;
break;

View file

@ -924,6 +924,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
break;
case DIV_INS_POKEMINI:
break;
case DIV_INS_SM8521:
checkForWL=true;
if (ws.enabled) featureWS=true;
break;
case DIV_INS_MAX:
break;

View file

@ -78,6 +78,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_K007232=45,
DIV_INS_GA20=46,
DIV_INS_POKEMINI=47,
DIV_INS_SM8521=48,
DIV_INS_MAX,
DIV_INS_NULL
};

View file

@ -0,0 +1,402 @@
/**
* 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 64
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,8);
buf[0][h]=sm8521.out<<6;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<6;
}
oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<6;
}
}
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=(chan[ch].ws.output[((i<<1)+chan[ch].antiClickWavePos-1)&31]-8)&0xf;
int nibble2=(chan[ch].ws.output[((1+(i<<1))+chan[ch].antiClickWavePos-1)&31]-8)&0xf;
rWrite(0x60+i+(ch*16),(nibble2<<4)|nibble1);
}
if (chan[ch].active) {
rWrite(0x40,temp|(1<<ch));
}
chan[ch].antiClickWavePos&=31;
}
}
void DivPlatformSM8521::tick(bool sysTick) {
unsigned char keyState=0x80;
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].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
chan[i].antiClickWavePos=0;
chan[i].antiClickPeriodCount=0;
}
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);
if (chan[i].keyOn) {
}
if (chan[i].keyOff) {
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
if (!isMuted[i] && chan[i].active) {
keyState|=(1<<i);
} else {
keyState&=~(1<<i);
}
}
if (regPool[0x40]!=keyState) {
rWrite(0x40,keyState);
regPool[0x40]=keyState;
}
}
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;
if (mute) {
rWrite(0x40,regPool[0x40]&~(1<<ch));
} else if (chan[ch].active) {
rWrite(0x40,regPool[0x40]|0x80|(1<<ch));
}
}
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);
rWrite(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) {
antiClickEnabled=!flags.getBool("noAntiClick",false);
chipClock=11059200;
CHECK_CUSTOM_CLOCK;
rate=chipClock/4/8; // CKIN -> fCLK(/2) -> Function blocks (/2)
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 _SM8521_H
#define _SM8521_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>(31),
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,235 @@
/*
============================================================================
SM8521 sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2023-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 "sm8521.h"
#include <stdlib.h>
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)
};
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;
}
}

View file

@ -0,0 +1,92 @@
/*
============================================================================
SM8521 sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2023-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
*/
#ifndef _SM8521_EMU_H
#define _SM8521_EMU_H
#ifdef __cplusplus
extern "C"
{
#endif
struct sm8521_sg_t
{
unsigned short t; // Time constant register
unsigned char level; // Output level control register
signed 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);
void sm8521_noise_tick(struct sm8521_noise_t *noise, const int cycle);
void sm8521_sound_tick(struct sm8521_t *sm8521, const int cycle);
void sm8521_reset(struct sm8521_t *sm8521);
unsigned char sm8521_read(struct sm8521_t *sm8521, const unsigned char a);
void sm8521_write(struct sm8521_t *sm8521, const unsigned char a, const unsigned char d);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _SM8521_EMU_H

View file

@ -124,7 +124,8 @@ enum DivSystem {
DIV_SYSTEM_YM2610_CSM,
DIV_SYSTEM_YM2610B_CSM,
DIV_SYSTEM_YM2203_CSM,
DIV_SYSTEM_YM2608_CSM
DIV_SYSTEM_YM2608_CSM,
DIV_SYSTEM_SM8521
};
struct DivGroovePattern {

View file

@ -1822,6 +1822,17 @@ void DivEngine::registerSystems() {
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}
);
sysDefs[DIV_SYSTEM_SM8521]=new DivSysDef(
"Sharp SM8521", NULL, 0xc8, 0, 3, false, true, 0, false, 0,
"a SoC with wavetable sound hardware.",
{"Channel 1", "Channel 2", "Noise"},
{"CH1", "CH2", "NS"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE},
{DIV_INS_SM8521, DIV_INS_SM8521, DIV_INS_SM8521},
{},
namcoEffectHandlerMap
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0,
"this is a system designed for testing purposes.",

View file

@ -430,6 +430,10 @@ void FurnaceGUI::drawInsList(bool asChild) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEMINI]);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_SM8521:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SM8521]);
name=fmt::sprintf(ICON_FA_PIE_CHART "##_INS%d",i);
break;
default:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]);
name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i);

View file

@ -189,6 +189,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_K007232,
GUI_COLOR_INSTR_GA20,
GUI_COLOR_INSTR_POKEMINI,
GUI_COLOR_INSTR_SM8521,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_BG,

View file

@ -129,6 +129,7 @@ const char* insTypes[DIV_INS_MAX+1]={
"K007232",
"GA20",
"Pokémon Mini",
"SM8521",
NULL
};
@ -818,6 +819,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_INSTR_K007232,"",ImVec4(1.0f,0.8f,0.1f,1.0f)),
D(GUI_COLOR_INSTR_GA20,"",ImVec4(0.1f,1.0f,0.4f,1.0f)),
D(GUI_COLOR_INSTR_POKEMINI,"",ImVec4(1.0f,1.0f,0.3f,1.0f)),
D(GUI_COLOR_INSTR_SM8521,"",ImVec4(1.0f,1.0f,0.0f,1.0f)),
D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)),
D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)),
@ -998,6 +1000,7 @@ const int availableSystems[]={
DIV_SYSTEM_MSM5232,
DIV_SYSTEM_K007232,
DIV_SYSTEM_GA20,
DIV_SYSTEM_SM8521,
DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_PONG,
0 // don't remove this last one!
@ -1084,6 +1087,7 @@ const int chipsSpecial[]={
DIV_SYSTEM_PET,
DIV_SYSTEM_VRC6,
DIV_SYSTEM_MMC5,
DIV_SYSTEM_SM8521,
0 // don't remove this last one!
};

View file

@ -4762,11 +4762,13 @@ void FurnaceGUI::drawInsEdit() {
(ins->type==DIV_INS_VBOY) ||
ins->type==DIV_INS_SCC ||
ins->type==DIV_INS_SNES ||
ins->type==DIV_INS_NAMCO) {
ins->type==DIV_INS_NAMCO ||
ins->type==DIV_INS_SM8521) {
if (ImGui::BeginTabItem("Wavetable")) {
switch (ins->type) {
case DIV_INS_GB:
case DIV_INS_NAMCO:
case DIV_INS_SM8521:
case DIV_INS_SWAN:
wavePreviewLen=32;
wavePreviewHeight=15;
@ -4968,7 +4970,7 @@ void FurnaceGUI::drawInsEdit() {
}
}
}
if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930) {
if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SM8521) {
volMax=31;
}
if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6_SAW) {
@ -5067,7 +5069,8 @@ void FurnaceGUI::drawInsEdit() {
}
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC ||
ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM ||
ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20) {
ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 ||
ins->type==DIV_INS_SM8521) {
dutyMax=0;
}
if (ins->type==DIV_INS_VBOY) {

View file

@ -2571,6 +2571,11 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_SFX_BEEPER, 1.0f, 0, "")
}
);
ENTRY(
"Sharp SM8521", {
CH(DIV_SYSTEM_SM8521, 1.0f, 0, "")
}
);
if (settings.hiddenSystems) {
ENTRY(
"Dummy System", {

View file

@ -1907,6 +1907,7 @@ void FurnaceGUI::drawSettings() {
UI_COLOR_CONFIG(GUI_COLOR_INSTR_K007232,"K007232");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_GA20,"GA20");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEMINI,"Pokémon Mini");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_SM8521,"SM8521");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown");
ImGui::TreePop();
}

View file

@ -1694,6 +1694,20 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
}
break;
}
case DIV_SYSTEM_SM8521: {
bool noAntiClick=flags.getBool("noAntiClick",false);
if (ImGui::Checkbox("Disable anti-click",&noAntiClick)) {
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("noAntiClick",noAntiClick);
});
}
break;
}
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_BUBSYS_WSG:
case DIV_SYSTEM_PET: