mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-30 08:23:01 +00:00
Add Bubble System Support
Bubble System 2 channel Wavetable sound generator logic is configuration with K005289, 4 bit PROM and DAC. K005289 controls pitch and 5 bit address generator per channel, Waveform select and Volume control is tied to AY-3-8910 ports. (each port for per channels) these configuration is better known as K005289, the part of logic. furnace emulates this configurations as single system, waveform format is 15 level and 32 width.
This commit is contained in:
parent
bd705d837d
commit
69aeb7dd58
18 changed files with 616 additions and 14 deletions
|
@ -271,6 +271,8 @@ src/engine/platform/sound/x1_010/x1_010.cpp
|
|||
|
||||
src/engine/platform/sound/swan.cpp
|
||||
|
||||
src/engine/platform/sound/k005289/k005289.cpp
|
||||
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
||||
src/engine/blip_buf.c
|
||||
|
@ -316,6 +318,7 @@ src/engine/platform/x1_010.cpp
|
|||
src/engine/platform/lynx.cpp
|
||||
src/engine/platform/swan.cpp
|
||||
src/engine/platform/vera.cpp
|
||||
src/engine/platform/k005289.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
)
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ depending on the instrument type, there are currently 13 different types of an i
|
|||
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
|
||||
- [VERA](vera.md) - for use with Commander X16 VERA.
|
||||
- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010.
|
||||
- [Konami SCC/Bubble System](scc.md) - for use with Konami SCC, Wavetable portion in Bubble System's sound hardware.
|
||||
|
||||
# macros
|
||||
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
PCE instrument editor consists of only three macros, almost like TIA:
|
||||
|
||||
- [Volume] - volume sequence
|
||||
- [Arpeggio] - pitch sequencr
|
||||
- [Arpeggio] - pitch sequence
|
||||
- [Waveform] - spicifies wavetables sequence
|
||||
|
|
7
papers/doc/4-instrument/scc.md
Normal file
7
papers/doc/4-instrument/scc.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Konami SCC instrument editor
|
||||
|
||||
SCC instrument editor consists of only three macros:
|
||||
|
||||
- [Volume] - volume sequence
|
||||
- [Arpeggio] - pitch sequence
|
||||
- [Waveform] - spicifies wavetables sequence
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
|
||||
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system.
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS and Bubble System, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system.
|
||||
|
|
|
@ -20,5 +20,6 @@ this is a list of systems that Furnace supports, including each system's effects
|
|||
- [Microchip AY8930](ay8930.md)
|
||||
- [Seta/Allumer X1-010](x1_010.md)
|
||||
- [WonderSwan](wonderswan.md)
|
||||
- [Bubble System/K005289](bubblesystem.md)
|
||||
|
||||
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all.
|
||||
|
|
13
papers/doc/7-systems/bubblesystem.md
Normal file
13
papers/doc/7-systems/bubblesystem.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Bubble System/K005289
|
||||
|
||||
a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System.
|
||||
|
||||
It's configured with K005289, 4 bit PROM and DAC.
|
||||
|
||||
Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address. Waveform select and Volume control are tied with AY-3-8910 port.
|
||||
|
||||
furnace emulates this configurations as single system, waveform format is 15 level and 32 width.
|
||||
|
||||
# effects
|
||||
|
||||
- `10xx`: change wave.
|
|
@ -45,6 +45,7 @@
|
|||
#include "platform/x1_010.h"
|
||||
#include "platform/swan.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "platform/k005289.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "song.h"
|
||||
|
@ -267,6 +268,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_VERA:
|
||||
dispatch=new DivPlatformVERA;
|
||||
break;
|
||||
case DIV_SYSTEM_K005289:
|
||||
dispatch=new DivPlatformK005289;
|
||||
break;
|
||||
default:
|
||||
logW("this system is not supported yet! using dummy platform.\n");
|
||||
dispatch=new DivPlatformDummy;
|
||||
|
|
334
src/engine/platform/k005289.cpp
Normal file
334
src/engine/platform/k005289.cpp
Normal file
|
@ -0,0 +1,334 @@
|
|||
/**
|
||||
* 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 "k005289.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define CHIP_DIVIDER 32
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
|
||||
|
||||
const char* regCheatSheetK005289[]={
|
||||
// K005289
|
||||
"Freq_A", "0",
|
||||
"Freq_B", "1",
|
||||
// PROM, DAC control from External logic (Connected to AY PSG ports on Bubble System)
|
||||
"WaveVol_A", "2",
|
||||
"WaveVol_B", "3",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformK005289::getRegisterSheet() {
|
||||
return regCheatSheetK005289;
|
||||
}
|
||||
|
||||
const char* DivPlatformK005289::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformK005289::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
signed int out=0;
|
||||
// K005289 part
|
||||
k005289->tick();
|
||||
|
||||
// Wavetable part
|
||||
for (int i=0; i<2; i++) {
|
||||
out+=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf);
|
||||
}
|
||||
|
||||
out<<=6; // scale output to 16 bit
|
||||
|
||||
if (out<-32768) out=-32768;
|
||||
if (out>32767) out=32767;
|
||||
|
||||
//printf("out: %d\n",out);
|
||||
bufL[h]=bufR[h]=out;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformK005289::updateWave(int ch) {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].wave);
|
||||
for (int i=0; i<32; i++) {
|
||||
if (wt->max>0 && wt->len>0) {
|
||||
int data=wt->data[i*wt->len/32]*15/wt->max; // 4 bit PROM at bubble system
|
||||
if (data<0) data=0;
|
||||
if (data>15) data=15;
|
||||
chan[ch].waveROM[i]=data-8; // convert to signed
|
||||
}
|
||||
}
|
||||
if (chan[ch].active) {
|
||||
rWrite(2+ch,(chan[ch].wave<<5)|(isMuted[ch]?0:chan[ch].outVol));
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformK005289::tick() {
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))/15;
|
||||
if (!isMuted[i]) {
|
||||
rWrite(2+i,(chan[i].wave<<5)|chan[i].outVol);
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadWave) {
|
||||
if (chan[i].wave!=chan[i].std.wave) {
|
||||
chan[i].wave=chan[i].std.wave;
|
||||
updateWave(i);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
k005289->load(i,chan[i].freq);
|
||||
rWrite(i,chan[i].freq);
|
||||
k005289->update(i);
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].wave<0) {
|
||||
chan[i].wave=0;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff && (!isMuted[i])) {
|
||||
rWrite(2+i,(chan[i].wave<<5)|0);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformK005289::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
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;
|
||||
if (!isMuted[c.chan]) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol);
|
||||
chan[c.chan].std.init(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(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;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.hasVol) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
if (chan[c.chan].active && !isMuted[c.chan]) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].outVol);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.hasVol) {
|
||||
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;
|
||||
updateWave(c.chan);
|
||||
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;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
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+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(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].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformK005289::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
rWrite(2+ch,(chan[ch].wave<<5)|((chan[ch].active && isMuted[ch])?0:chan[ch].outVol));
|
||||
}
|
||||
|
||||
void DivPlatformK005289::forceIns() {
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformK005289::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformK005289::getRegisterPool() {
|
||||
return (unsigned char*)regPool;
|
||||
}
|
||||
|
||||
int DivPlatformK005289::getRegisterPoolSize() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
int DivPlatformK005289::getRegisterPoolDepth() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
void DivPlatformK005289::reset() {
|
||||
memset(regPool,0,4*2);
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i]=DivPlatformK005289::Channel();
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
k005289->reset();
|
||||
}
|
||||
|
||||
bool DivPlatformK005289::isStereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DivPlatformK005289::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformK005289::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<2; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformK005289::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformK005289::setFlags(unsigned int flags) {
|
||||
chipClock=COLOR_NTSC;
|
||||
rate=chipClock;
|
||||
}
|
||||
|
||||
void DivPlatformK005289::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformK005289::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformK005289::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
for (int i=0; i<2; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
k005289=new k005289_core();
|
||||
reset();
|
||||
return 2;
|
||||
}
|
||||
|
||||
void DivPlatformK005289::quit() {
|
||||
delete k005289;
|
||||
}
|
||||
|
||||
DivPlatformK005289::~DivPlatformK005289() {
|
||||
}
|
84
src/engine/platform/k005289.h
Normal file
84
src/engine/platform/k005289.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* 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 _K005289_H
|
||||
#define _K005289_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "sound/k005289/k005289.hpp"
|
||||
|
||||
class DivPlatformK005289: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system
|
||||
DivMacroInt std;
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1) {}
|
||||
};
|
||||
Channel chan[2];
|
||||
bool isMuted[2];
|
||||
|
||||
k005289_core* k005289;
|
||||
unsigned short regPool[4];
|
||||
void updateWave(int ch);
|
||||
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();
|
||||
int getRegisterPoolDepth();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(unsigned int 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();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformK005289();
|
||||
};
|
||||
|
||||
#endif
|
45
src/engine/platform/sound/k005289/k005289.cpp
Normal file
45
src/engine/platform/sound/k005289/k005289.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Konami K005289 emulation core
|
||||
|
||||
This chip is used at infamous Konami Bubble System, for part of Wavetable sound generator.
|
||||
But seriously, It is just to 2 internal 12 bit timer and address generators, rather than sound generator.
|
||||
|
||||
Everything except for internal counter and address are done by external logic, the chip is only has external address, frequency registers and its update pins.
|
||||
|
||||
Frequency calculation: Input clock / (4096 - Pitch input)
|
||||
*/
|
||||
|
||||
#include "k005289.hpp"
|
||||
|
||||
void k005289_core::tick()
|
||||
{
|
||||
for (auto & elem : m_voice)
|
||||
elem.tick();
|
||||
}
|
||||
|
||||
void k005289_core::reset()
|
||||
{
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
}
|
||||
|
||||
void k005289_core::voice_t::tick()
|
||||
{
|
||||
if (bitfield(++counter, 0, 12) == 0)
|
||||
{
|
||||
addr = bitfield(addr + 1, 0, 5);
|
||||
counter = freq;
|
||||
}
|
||||
}
|
||||
|
||||
void k005289_core::voice_t::reset()
|
||||
{
|
||||
addr = 0;
|
||||
pitch = 0;
|
||||
freq = 0;
|
||||
counter = 0;
|
||||
}
|
67
src/engine/platform/sound/k005289/k005289.hpp
Normal file
67
src/engine/platform/sound/k005289/k005289.hpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Konami K005289 emulation core
|
||||
|
||||
See k005289.cpp for more info.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#ifndef _VGSOUND_EMU_K005289_HPP
|
||||
#define _VGSOUND_EMU_K005289_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace k005289
|
||||
{
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef signed short s16;
|
||||
|
||||
// get bitfield, bitfield(input, position, len)
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
}
|
||||
}
|
||||
|
||||
using namespace k005289;
|
||||
class k005289_core
|
||||
{
|
||||
public:
|
||||
// accessors, getters, setters
|
||||
u8 addr(int voice) { return m_voice[voice & 1].addr; } // 1QA...E/2QA...E pin
|
||||
void load(int voice, u16 addr) { m_voice[voice & 1].load(addr); } // LD1/2 pin, A0...11 pin
|
||||
void update(int voice) { m_voice[voice & 1].update(); } // TG1/2 pin
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
private:
|
||||
// k005289 voice structs
|
||||
struct voice_t
|
||||
{
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
// accessors, getters, setters
|
||||
void load(u16 addr) { pitch = addr; } // Load pitch data (address pin)
|
||||
void update() { freq = pitch; } // Replace current frequency to lastest loaded pitch
|
||||
|
||||
// registers
|
||||
u8 addr = 0; // external address pin
|
||||
u16 pitch = 0; // pitch
|
||||
u16 freq = 0; // current frequency
|
||||
s16 counter = 0; // frequency counter
|
||||
};
|
||||
|
||||
voice_t m_voice[2];
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,23 +16,28 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed char s8;
|
||||
typedef signed int s32;
|
||||
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
namespace x1_010
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed char s8;
|
||||
typedef signed int s32;
|
||||
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
}
|
||||
}
|
||||
|
||||
using namespace x1_010;
|
||||
class x1_010_mem_intf
|
||||
{
|
||||
public:
|
||||
virtual u8 read_byte(u32 address) { return 0; }
|
||||
};
|
||||
|
||||
using namespace x1_010;
|
||||
class x1_010_core
|
||||
{
|
||||
friend class x1_010_mem_intf;
|
||||
|
|
|
@ -309,6 +309,15 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
|||
return false;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_K005289:
|
||||
switch (effect) {
|
||||
case 0x10: // select waveform
|
||||
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,8 @@ enum DivSystem {
|
|||
DIV_SYSTEM_VERA,
|
||||
DIV_SYSTEM_YM2610B_EXT,
|
||||
DIV_SYSTEM_SEGAPCM_COMPAT,
|
||||
DIV_SYSTEM_X1_010
|
||||
DIV_SYSTEM_X1_010,
|
||||
DIV_SYSTEM_K005289
|
||||
};
|
||||
|
||||
struct DivSong {
|
||||
|
|
|
@ -226,7 +226,7 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
|
|||
return 0x96;
|
||||
case DIV_SYSTEM_SAA1099:
|
||||
return 0x97;
|
||||
case DIV_SYSTEM_OPZ:
|
||||
case DIV_SYSTEM_OPZ:
|
||||
return 0x98;
|
||||
case DIV_SYSTEM_POKEMINI:
|
||||
return 0x99;
|
||||
|
@ -394,6 +394,8 @@ int DivEngine::getChannelCount(DivSystem sys) {
|
|||
return 19;
|
||||
case DIV_SYSTEM_VERA:
|
||||
return 17;
|
||||
case DIV_SYSTEM_K005289:
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -539,6 +541,9 @@ const char* DivEngine::getSongSystemName() {
|
|||
}
|
||||
break;
|
||||
case 3:
|
||||
if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_K005289) {
|
||||
return "Konami Bubble System";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return "multi-system";
|
||||
|
@ -669,6 +674,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
|
|||
return "VERA";
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return "Seta/Allumer X1-010";
|
||||
case DIV_SYSTEM_K005289:
|
||||
return "Konami Bubble System Sound";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
@ -798,6 +805,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
|
|||
return "VERA";
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return "Seta/Allumer X1-010";
|
||||
case DIV_SYSTEM_K005289:
|
||||
return "Konami K005289";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
@ -1008,7 +1017,7 @@ const int chanTypes[41][32]={
|
|||
{3, 4, 3, 2}, // Swan
|
||||
};
|
||||
|
||||
const DivInstrumentType chanPrefType[46][28]={
|
||||
const DivInstrumentType chanPrefType[47][28]={
|
||||
{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)
|
||||
|
@ -1055,6 +1064,7 @@ const DivInstrumentType chanPrefType[46][28]={
|
|||
{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)
|
||||
{DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA
|
||||
{DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010
|
||||
{DIV_INS_SCC, DIV_INS_SCC}, // K005289
|
||||
};
|
||||
|
||||
const char* DivEngine::getChannelName(int chan) {
|
||||
|
@ -1083,6 +1093,7 @@ const char* DivEngine::getChannelName(int chan) {
|
|||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
case DIV_SYSTEM_SFX_BEEPER:
|
||||
case DIV_SYSTEM_K005289:
|
||||
return chanNames[5][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
|
@ -1228,6 +1239,7 @@ const char* DivEngine::getChannelShortName(int chan) {
|
|||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
case DIV_SYSTEM_SFX_BEEPER:
|
||||
case DIV_SYSTEM_K005289:
|
||||
return chanShortNames[5][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
|
@ -1369,6 +1381,7 @@ int DivEngine::getChannelType(int chan) {
|
|||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
case DIV_SYSTEM_SFX_BEEPER:
|
||||
case DIV_SYSTEM_K005289:
|
||||
return chanTypes[5][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
|
@ -1642,6 +1655,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
|
|||
case DIV_SYSTEM_X1_010:
|
||||
return chanPrefType[45][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_K005289:
|
||||
return chanPrefType[46][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
}
|
||||
return DIV_INS_FM;
|
||||
}
|
||||
|
|
|
@ -5253,6 +5253,7 @@ bool FurnaceGUI::loop() {
|
|||
sysAddOption(DIV_SYSTEM_X1_010);
|
||||
sysAddOption(DIV_SYSTEM_SWAN);
|
||||
sysAddOption(DIV_SYSTEM_VERA);
|
||||
sysAddOption(DIV_SYSTEM_K005289);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("configure system...")) {
|
||||
|
@ -5580,6 +5581,7 @@ bool FurnaceGUI::loop() {
|
|||
case DIV_SYSTEM_GB:
|
||||
case DIV_SYSTEM_SWAN:
|
||||
case DIV_SYSTEM_VERA:
|
||||
case DIV_SYSTEM_K005289:
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_EXT:
|
||||
case DIV_SYSTEM_YM2610_FULL:
|
||||
|
@ -5641,6 +5643,7 @@ bool FurnaceGUI::loop() {
|
|||
sysChangeOption(i,DIV_SYSTEM_X1_010);
|
||||
sysChangeOption(i,DIV_SYSTEM_SWAN);
|
||||
sysChangeOption(i,DIV_SYSTEM_VERA);
|
||||
sysChangeOption(i,DIV_SYSTEM_K005289);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
@ -7388,6 +7391,15 @@ FurnaceGUI::FurnaceGUI():
|
|||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"Konami Bubble System", {
|
||||
DIV_SYSTEM_AY8910, 64, 0, 0,
|
||||
DIV_SYSTEM_AY8910, 64, 0, 0,
|
||||
DIV_SYSTEM_K005289, 64, 0, 0,
|
||||
// VLM5030 exists but not used for music at all
|
||||
0
|
||||
}
|
||||
));
|
||||
sysCategories.push_back(cat);
|
||||
|
||||
cat=FurnaceGUISysCategory("DefleMask-compatible");
|
||||
|
|
|
@ -1418,7 +1418,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_AY8930) {
|
||||
dutyMax=255;
|
||||
}
|
||||
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA) {
|
||||
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC) {
|
||||
dutyMax=0;
|
||||
}
|
||||
if (ins->type==DIV_INS_PCE) {
|
||||
|
|
Loading…
Reference in a new issue