diff --git a/CMakeLists.txt b/CMakeLists.txt index 4feccdf8d..c45e87f5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 5592fb4e4..577e18ebc 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -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 and Wavetable portion in Bubble System's sound hardware. # macros diff --git a/papers/doc/4-instrument/pce.md b/papers/doc/4-instrument/pce.md index 7bff37c70..50ac335bf 100644 --- a/papers/doc/4-instrument/pce.md +++ b/papers/doc/4-instrument/pce.md @@ -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 diff --git a/papers/doc/4-instrument/scc.md b/papers/doc/4-instrument/scc.md new file mode 100644 index 000000000..86e8c11bd --- /dev/null +++ b/papers/doc/4-instrument/scc.md @@ -0,0 +1,7 @@ +# Konami SCC/Bubble System instrument editor + +SCC/Bubble System instrument editor consists of only three macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequence +- [Waveform] - spicifies wavetables sequence diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 5e234bf65..f6dd9b94b 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -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 and 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. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index a6648a329..405d0563c 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -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. diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md new file mode 100644 index 000000000..1dc18cab1 --- /dev/null +++ b/papers/doc/7-systems/bubblesystem.md @@ -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. diff --git a/papers/format.md b/papers/format.md index d5b9c67e6..503099b99 100644 --- a/papers/format.md +++ b/papers/format.md @@ -177,6 +177,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xad: Bubble System - 2 channels | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index ca2d99ce2..3dcf17a8f 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -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; diff --git a/src/engine/platform/k005289.cpp b/src/engine/platform/k005289.cpp new file mode 100644 index 000000000..b50a76af2 --- /dev/null +++ b/src/engine/platform/k005289.cpp @@ -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 + +#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; htick(); + + // 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& 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() { +} diff --git a/src/engine/platform/k005289.h b/src/engine/platform/k005289.h new file mode 100644 index 000000000..d8818fe41 --- /dev/null +++ b/src/engine/platform/k005289.h @@ -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 +#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& 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 diff --git a/src/engine/platform/sound/k005289/k005289.cpp b/src/engine/platform/sound/k005289/k005289.cpp new file mode 100644 index 000000000..4763b63f0 --- /dev/null +++ b/src/engine/platform/sound/k005289/k005289.cpp @@ -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; +} diff --git a/src/engine/platform/sound/k005289/k005289.hpp b/src/engine/platform/sound/k005289/k005289.hpp new file mode 100644 index 000000000..fe5d50ac9 --- /dev/null +++ b/src/engine/platform/sound/k005289/k005289.hpp @@ -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 +#include + +#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 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 diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp index d5c429fda..e8f5e3e40 100644 --- a/src/engine/platform/sound/x1_010/x1_010.hpp +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -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 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 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; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 85c21aacd..d94c18085 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -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; } diff --git a/src/engine/song.h b/src/engine/song.h index 7fc6ec7fd..130c9a916 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -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 { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 5e57f6e36..d4aaa353f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -137,6 +137,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_SEGAPCM_COMPAT; case 0xac: return DIV_SYSTEM_VERA; + case 0xad: + return DIV_SYSTEM_K005289; case 0xb0: return DIV_SYSTEM_X1_010; case 0xde: @@ -226,7 +228,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; @@ -264,6 +266,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa9; case DIV_SYSTEM_VERA: return 0xac; + case DIV_SYSTEM_K005289: + return 0xad; case DIV_SYSTEM_X1_010: return 0xb0; case DIV_SYSTEM_YM2610B_EXT: @@ -394,6 +398,8 @@ int DivEngine::getChannelCount(DivSystem sys) { return 19; case DIV_SYSTEM_VERA: return 17; + case DIV_SYSTEM_K005289: + return 2; } return 0; } @@ -542,6 +548,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"; @@ -672,6 +681,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"; } @@ -801,6 +812,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"; } @@ -1011,7 +1024,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) @@ -1058,6 +1071,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) { @@ -1086,6 +1100,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: @@ -1231,6 +1246,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: @@ -1372,6 +1388,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: @@ -1645,6 +1662,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; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 5765788ec..f431d1839 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5535,6 +5535,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...")) { @@ -5866,6 +5867,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: @@ -5927,6 +5929,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(); } } @@ -7682,6 +7685,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"); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 83220d252..7b4cd52a2 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -84,7 +84,7 @@ const char* insTypes[DIV_INS_MAX]={ "FDS", "Virtual Boy", "Namco 163", - "Konami SCC", + "Konami SCC/Bubble System", "FM (OPZ)", "POKEY", "PC Beeper", @@ -92,4 +92,4 @@ const char* insTypes[DIV_INS_MAX]={ "Atari Lynx", "VERA", "X1-010" -}; \ No newline at end of file +}; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 828eced3a..862de6e81 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1389,7 +1389,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) { diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 22bd7e0d1..fc4972e9d 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -39,7 +39,10 @@ void FurnaceGUI::drawOrders() { for (int i=0; igetTotalChannelCount(); i++) { if (e->song.chanShow[i]) displayChans++; } - if (ImGui::BeginTable("OrdersTable",1+displayChans,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { + ImGui::PushFont(patFont); + bool tooSmall=(displayChans>((ImGui::GetWindowSize().x-24.0f*dpiScale)/ImGui::CalcTextSize("AAA").x)); + ImGui::PopFont(); + if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { ImGui::PushFont(patFont); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing); ImGui::TableSetupScrollFreeze(1,1); @@ -246,4 +249,4 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ORDERS; oldOrder1=e->getOrder(); ImGui::End(); -} \ No newline at end of file +}