Merge branch 'master' of https://github.com/tildearrow/furnace into mod-import

This commit is contained in:
Natt Akuma 2022-03-15 03:08:37 +07:00
commit 91e856c61e
21 changed files with 628 additions and 18 deletions

View file

@ -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
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View 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.

View file

@ -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

View file

@ -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;

View 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() {
}

View 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

View 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;
}

View 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

View file

@ -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;

View file

@ -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;
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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");

View file

@ -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"
};
};

View file

@ -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) {

View file

@ -39,7 +39,10 @@ void FurnaceGUI::drawOrders() {
for (int i=0; i<e->getTotalChannelCount(); 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();
}
}