Merge branch 'x16'

This commit is contained in:
tildearrow 2022-03-10 17:31:32 -05:00
commit cbc8721a00
17 changed files with 820 additions and 15 deletions

View File

@ -234,6 +234,8 @@ src/engine/platform/sound/gb/apu.c
src/engine/platform/sound/gb/timing.c
src/engine/platform/sound/pce_psg.cpp
src/engine/platform/sound/nes/apu.c
src/engine/platform/sound/vera_psg.c
src/engine/platform/sound/vera_pcm.c
src/engine/platform/sound/c64/sid.cc
src/engine/platform/sound/c64/voice.cc
@ -310,6 +312,7 @@ src/engine/platform/segapcm.cpp
src/engine/platform/qsound.cpp
src/engine/platform/lynx.cpp
src/engine/platform/swan.cpp
src/engine/platform/vera.cpp
src/engine/platform/dummy.cpp
)

View File

@ -24,6 +24,7 @@ depending on the instrument type, there are currently 12 different types of an i
- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610.
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
- [VERA](vera.md) - for use with Commander X16 VERA.
# macros

View File

@ -0,0 +1,8 @@
# VERA instrument editor
VERA instrument editor consists of only four macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequence
- [Duty cycle] - pulse duty cycle sequence
- [Waveform] - select the waveform used by instrument

View File

@ -41,9 +41,10 @@
#include "platform/pcspkr.h"
#include "platform/segapcm.h"
#include "platform/qsound.h"
#include "platform/swan.h"
#include "platform/dummy.h"
#include "platform/lynx.h"
#include "platform/swan.h"
#include "platform/vera.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -259,6 +260,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_SWAN:
dispatch=new DivPlatformSwan;
break;
case DIV_SYSTEM_VERA:
dispatch=new DivPlatformVERA;
break;
default:
logW("this system is not supported yet! using dummy platform.\n");
dispatch=new DivPlatformDummy;

View File

@ -954,6 +954,8 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return (24038*MIN(65535,(rate*4096/24038)))/4096;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT:
return 18518;
case DIV_SYSTEM_VERA:
return (48828*MIN(128,(rate*128/48828)))/128;
default:
break;
}

View File

@ -51,6 +51,7 @@ enum DivInstrumentType {
DIV_INS_BEEPER=21,
DIV_INS_SWAN=22,
DIV_INS_MIKEY=23,
DIV_INS_VERA=24,
};
// FM operator structure:

View File

@ -0,0 +1,133 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#include "vera_pcm.h"
#include <stdio.h>
static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64};
static void
fifo_reset(struct VERA_PCM* pcm)
{
pcm->fifo_wridx = 0;
pcm->fifo_rdidx = 0;
pcm->fifo_cnt = 0;
}
void
pcm_reset(struct VERA_PCM* pcm)
{
fifo_reset(pcm);
pcm->ctrl = 0;
pcm->rate = 0;
pcm->cur_l = 0;
pcm->cur_r = 0;
pcm->phase = 0;
}
void
pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val)
{
if (val & 0x80) {
fifo_reset(pcm);
}
pcm->ctrl = val & 0x3F;
}
uint8_t
pcm_read_ctrl(struct VERA_PCM* pcm)
{
uint8_t result = pcm->ctrl;
if (pcm->fifo_cnt == sizeof(pcm->fifo)) {
result |= 0x80;
}
return result;
}
void
pcm_write_rate(struct VERA_PCM* pcm, uint8_t val)
{
pcm->rate = val;
}
uint8_t
pcm_read_rate(struct VERA_PCM* pcm)
{
return pcm->rate;
}
void
pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val)
{
if (pcm->fifo_cnt < sizeof(pcm->fifo)) {
pcm->fifo[pcm->fifo_wridx++] = val;
if (pcm->fifo_wridx == sizeof(pcm->fifo)) {
pcm->fifo_wridx = 0;
}
pcm->fifo_cnt++;
}
}
static uint8_t
read_fifo(struct VERA_PCM* pcm)
{
if (pcm->fifo_cnt == 0) {
return 0;
}
uint8_t result = pcm->fifo[pcm->fifo_rdidx++];
if (pcm->fifo_rdidx == sizeof(pcm->fifo)) {
pcm->fifo_rdidx = 0;
}
pcm->fifo_cnt--;
return result;
}
bool
pcm_is_fifo_almost_empty(struct VERA_PCM* pcm)
{
return pcm->fifo_cnt < 1024;
}
void
pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples)
{
while (num_samples--) {
uint8_t old_phase = pcm->phase;
pcm->phase += pcm->rate;
if ((old_phase & 0x80) != (pcm->phase & 0x80)) {
switch ((pcm->ctrl >> 4) & 3) {
case 0: { // mono 8-bit
pcm->cur_l = (int16_t)read_fifo(pcm) << 8;
pcm->cur_r = pcm->cur_l;
break;
}
case 1: { // stereo 8-bit
pcm->cur_l = read_fifo(pcm) << 8;
pcm->cur_r = read_fifo(pcm) << 8;
break;
}
case 2: { // mono 16-bit
pcm->cur_l = read_fifo(pcm);
pcm->cur_l |= read_fifo(pcm) << 8;
pcm->cur_r = pcm->cur_l;
break;
}
case 3: { // stereo 16-bit
pcm->cur_l = read_fifo(pcm);
pcm->cur_l |= read_fifo(pcm) << 8;
pcm->cur_r = read_fifo(pcm);
pcm->cur_r |= read_fifo(pcm) << 8;
break;
}
}
}
*(buf_l) += ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6;
*(buf_r) += ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6;
buf_l++;
buf_r++;
}
}

View File

@ -0,0 +1,31 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct VERA_PCM {
uint8_t fifo[4096 - 1]; // Actual hardware FIFO is 4kB, but you can only use 4095 bytes.
unsigned fifo_wridx;
unsigned fifo_rdidx;
unsigned fifo_cnt;
uint8_t ctrl;
uint8_t rate;
int16_t cur_l, cur_r;
uint8_t phase;
};
void pcm_reset(struct VERA_PCM* pcm);
void pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val);
uint8_t pcm_read_ctrl(struct VERA_PCM* pcm);
void pcm_write_rate(struct VERA_PCM* pcm, uint8_t val);
uint8_t pcm_read_rate(struct VERA_PCM* pcm);
void pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val);
void pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples);
bool pcm_is_fifo_almost_empty(struct VERA_PCM* pcm);

View File

@ -0,0 +1,106 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#include "vera_psg.h"
#include <stdlib.h>
#include <string.h>
enum waveform {
WF_PULSE = 0,
WF_SAWTOOTH,
WF_TRIANGLE,
WF_NOISE,
};
static uint8_t volume_lut[64] = {0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63};
void
psg_reset(struct VERA_PSG* psg)
{
memset(psg->channels, 0, sizeof(psg->channels));
psg->noiseState=1;
psg->noiseOut=0;
}
void
psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val)
{
reg &= 0x3f;
int ch = reg / 4;
int idx = reg & 3;
switch (idx) {
case 0: psg->channels[ch].freq = (psg->channels[ch].freq & 0xFF00) | val; break;
case 1: psg->channels[ch].freq = (psg->channels[ch].freq & 0x00FF) | (val << 8); break;
case 2: {
psg->channels[ch].right = (val & 0x80) != 0;
psg->channels[ch].left = (val & 0x40) != 0;
psg->channels[ch].volume = volume_lut[val & 0x3F];
break;
}
case 3: {
psg->channels[ch].pw = val & 0x3F;
psg->channels[ch].waveform = val >> 6;
break;
}
}
}
static inline void
render(struct VERA_PSG* psg, int16_t *left, int16_t *right)
{
int l = 0;
int r = 0;
// TODO this is a currently speculated noise generation
// as the hardware and sources for it are not out in the public
// and the official emulator just uses rand()
psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63;
psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1);
for (int i = 0; i < 16; i++) {
struct VERAChannel *ch = &psg->channels[i];
unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF;
if ((ch->phase & 0x10000) != (new_phase & 0x10000)) {
ch->noiseval = psg->noiseOut;
}
ch->phase = new_phase;
uint8_t v = 0;
switch (ch->waveform) {
case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break;
case WF_SAWTOOTH: v = ch->phase >> 11; break;
case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break;
case WF_NOISE: v = ch->noiseval; break;
}
int8_t sv = (v ^ 0x20);
if (sv & 0x20) {
sv |= 0xC0;
}
int val = (int)sv * (int)ch->volume;
if (ch->left) {
l += val;
}
if (ch->right) {
r += val;
}
}
*left = l;
*right = r;
}
void
psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples)
{
while (num_samples--) {
render(psg, bufL, bufR);
bufL++;
bufR++;
}
}

View File

@ -0,0 +1,28 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct VERAChannel {
uint16_t freq;
uint8_t volume;
bool left, right;
uint8_t pw;
uint8_t waveform;
unsigned phase;
uint8_t noiseval;
};
struct VERA_PSG {
unsigned int noiseState, noiseOut;
struct VERAChannel channels[16];
};
void psg_reset(struct VERA_PSG* psg);
void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val);
void psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples);

View File

@ -0,0 +1,345 @@
/**
* 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 "vera.h"
#include "../engine.h"
#include <string.h>
#include <math.h>
extern "C" {
#include "sound/vera_psg.h"
#include "sound/vera_pcm.h"
}
#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));}
#define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f))
#define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0))
#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f))
const char* regCheatSheetVERA[]={
"CHxFreq", "00+x*4",
"CHxVol", "02+x*4",
"CHxWave", "03+x*4",
"AUDIO_CTRL", "40",
"AUDIO_RATE", "41",
NULL
};
const char** DivPlatformVERA::getRegisterSheet() {
return regCheatSheetVERA;
}
const char* DivPlatformVERA::getEffectName(unsigned char effect) {
switch (effect) {
case 0x20:
return "20xx: Change waveform";
break;
case 0x22:
return "22xx: Set duty cycle (0 to 63)";
break;
}
return NULL;
}
// TODO: wire up PCM.
void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
psg_render(psg,bufL+start,bufR+start,len);
pcm_render(pcm,bufL+start,bufR+start,len);
}
void DivPlatformVERA::reset() {
for (int i=0; i<17; i++) {
chan[i]=Channel();
}
memset(regPool,0,66);
for (int i=0; i<16; i++) {
chan[i].vol=63;
chan[i].pan=3;
rWriteHi(i,2,isMuted[i]?0:3);
}
chan[16].vol=15;
chan[16].pan=3;
}
int DivPlatformVERA::calcNoteFreq(int ch, int note) {
if (ch<16) {
return parent->calcBaseFreq(chipClock,2097152,note,false);
} else {
double off=1.0;
if (chan[ch].pcm.sample>=0 && chan[ch].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[ch].pcm.sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=s->centerRate/8363.0;
}
}
return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false));
}
}
void DivPlatformVERA::tick() {
for (int i=0; i<16; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0);
rWriteLo(i,2,chan[i].outVol);
}
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp);
} else {
chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
chan[i].baseFreq=calcNoteFreq(0,chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.hadDuty) {
rWriteLo(i,3,chan[i].std.duty);
}
if (chan[i].std.hadWave) {
rWriteHi(i,3,chan[i].std.wave);
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8);
if (chan[i].freq>65535) chan[i].freq=65535;
rWrite(i,0,chan[i].freq&0xff);
rWrite(i,1,(chan[i].freq>>8)&0xff);
chan[i].freqChanged=false;
}
}
// PCM
chan[16].std.next();
if (chan[16].std.hadVol) {
chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0);
rWriteFIFOVol(chan[16].outVol&15);
}
if (chan[16].std.hadArp) {
if (!chan[16].inPorta) {
if (chan[16].std.arpMode) {
chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp);
} else {
chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp);
}
}
chan[16].freqChanged=true;
} else {
if (chan[16].std.arpMode && chan[16].std.finishedArp) {
chan[16].baseFreq=calcNoteFreq(16,chan[16].note);
chan[16].freqChanged=true;
}
}
if (chan[16].freqChanged) {
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8);
if (chan[16].freq>128) chan[16].freq=128;
rWrite(16,1,chan[16].freq&0xff);
chan[16].freqChanged=false;
}
}
int DivPlatformVERA::dispatch(DivCommand c) {
int tmp;
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
if(c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol)
} else {
chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample;
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
}
chan[16].pcm.pos=0;
rWriteFIFOVol(chan[c.chan].vol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
if(c.chan<16) {
rWriteLo(c.chan,2,0)
} else {
chan[16].pcm.sample=-1;
rWriteFIFOVol(0);
rWrite(16,1,0);
}
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:
chan[c.chan].ins=(unsigned char)c.value;
break;
case DIV_CMD_VOLUME:
if (c.chan<16) {
tmp=c.value&0x3f;
chan[c.chan].vol=tmp;
rWriteLo(c.chan,2,tmp);
} else {
tmp=c.value&0x0f;
chan[c.chan].vol=tmp;
rWriteFIFOVol(tmp);
}
break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=calcNoteFreq(c.chan,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=calcNoteFreq(c.chan,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_STD_NOISE_MODE:
if (c.chan<16) rWriteLo(c.chan,3,c.value);
break;
case DIV_CMD_WAVE:
if (c.chan<16) rWriteHi(c.chan,3,c.value);
break;
case DIV_CMD_PANNING: {
tmp=0;
tmp|=(c.value&0x10)?1:0;
tmp|=(c.value&0x01)?2:0;
chan[c.chan].pan=tmp&3;
if (c.chan<16) {
rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan);
}
break;
}
case DIV_CMD_GET_VOLMAX:
if(c.chan<16) {
return 63;
} else {
return 15;
}
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
default:
break;
}
return 1;
}
void* DivPlatformVERA::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformVERA::getRegisterPool() {
return regPool;
}
int DivPlatformVERA::getRegisterPoolSize() {
return 66;
}
void DivPlatformVERA::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (ch<16) {
rWriteHi(ch,2,mute?0:chan[ch].pan);
}
}
bool DivPlatformVERA::isStereo() {
return true;
}
void DivPlatformVERA::notifyInsDeletion(void* ins) {
for (int i=0; i<2; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformVERA::poke(unsigned int addr, unsigned short val) {
regPool[addr] = (unsigned char)val;
}
void DivPlatformVERA::poke(std::vector<DivRegWrite>& wlist) {
for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val;
}
int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
for (int i=0; i<17; i++) {
isMuted[i]=false;
}
parent=p;
psg=new struct VERA_PSG;
pcm=new struct VERA_PCM;
dumpWrites=false;
skipRegisterWrites=false;
chipClock=25000000;
rate=chipClock/512;
reset();
return 17;
}
void DivPlatformVERA::quit() {
delete psg;
delete pcm;
}
DivPlatformVERA::~DivPlatformVERA() {
}

View File

@ -0,0 +1,78 @@
/**
* 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 _VERA_H
#define _VERA_H
#include "../dispatch.h"
#include "../instrument.h"
#include "../macroInt.h"
struct VERA_PSG;
struct VERA_PCM;
class DivPlatformVERA: public DivDispatch {
protected:
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, pan;
bool active, freqChanged, inPorta;
int vol, outVol;
unsigned accum;
int noiseval;
DivMacroInt std;
struct PCMChannel {
int sample;
int out_l, out_r;
unsigned pos;
unsigned len;
unsigned char freq;
PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {}
} pcm;
Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {}
};
Channel chan[17];
bool isMuted[17];
unsigned char regPool[66];
struct VERA_PSG* psg;
struct VERA_PCM* pcm;
int calcNoteFreq(int ch, int note);
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();
void reset();
void tick();
void muteChannel(int ch, bool mute);
void notifyInsDeletion(void* ins);
bool isStereo();
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();
~DivPlatformVERA();
};
#endif

View File

@ -272,6 +272,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
return false;
}
break;
case DIV_SYSTEM_VERA:
switch (effect) {
case 0x20: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x22: // duty
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
default:
return false;
}
break;
default:
return false;
}

View File

@ -90,8 +90,9 @@ enum DivSystem {
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_LYNX,
DIV_SYSTEM_QSOUND,
DIV_SYSTEM_VERA,
DIV_SYSTEM_YM2610B_EXT,
DIV_SYSTEM_SEGAPCM_COMPAT
DIV_SYSTEM_SEGAPCM_COMPAT,
};
struct DivSong {

View File

@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
return DIV_SYSTEM_LYNX;
case 0xa9:
return DIV_SYSTEM_SEGAPCM_COMPAT;
case 0xac:
return DIV_SYSTEM_VERA;
case 0xde:
return DIV_SYSTEM_YM2610B_EXT;
case 0xe0:
@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
return 0xa8;
case DIV_SYSTEM_SEGAPCM_COMPAT:
return 0xa9;
case DIV_SYSTEM_VERA:
return 0xac;
case DIV_SYSTEM_YM2610B_EXT:
return 0xde;
case DIV_SYSTEM_QSOUND:
@ -383,6 +387,8 @@ int DivEngine::getChannelCount(DivSystem sys) {
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_QSOUND:
return 19;
case DIV_SYSTEM_VERA:
return 17;
}
return 0;
}
@ -522,6 +528,10 @@ const char* DivEngine::getSongSystemName() {
if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) {
return "Bally Midway MCR";
}
if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) {
return "Commander X16";
}
break;
case 3:
break;
@ -650,6 +660,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "Taito Arcade Extended Channel 3";
case DIV_SYSTEM_QSOUND:
return "Capcom QSound";
case DIV_SYSTEM_VERA:
return "VERA";
}
return "Unknown";
}
@ -775,6 +787,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
return "Yamaha YM2610B Extended Channel 3";
case DIV_SYSTEM_QSOUND:
return "Capcom DL-1425";
case DIV_SYSTEM_VERA:
return "VERA";
}
return "Unknown";
}
@ -858,7 +872,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
}
const char* chanNames[38][24]={
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM/VERA
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3)
{"Square 1", "Square 2", "Square 3", "Noise"}, // SMS
@ -899,7 +913,7 @@ const char* chanNames[38][24]={
};
const char* chanShortNames[38][24]={
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759/VERA
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3)
{"S1", "S2", "S3", "NO"}, // SMS
@ -939,7 +953,7 @@ const char* chanShortNames[38][24]={
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3)
};
const int chanTypes[38][24]={
const int chanTypes[39][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3)
@ -978,9 +992,10 @@ const int chanTypes[38][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums
{3, 3, 3, 3}, //Lynx
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3)
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA
};
const DivInstrumentType chanPrefType[44][24]={
const DivInstrumentType chanPrefType[45][24]={
{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)
@ -1025,6 +1040,7 @@ const DivInstrumentType chanPrefType[44][24]={
{DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z
{DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx
{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
};
const char* DivEngine::getChannelName(int chan) {
@ -1162,6 +1178,9 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_QSOUND:
return chanNames[36][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanNames[0][dispatchChanOfChan[chan]];
break;
}
return "??";
}
@ -1301,6 +1320,9 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_QSOUND:
return chanShortNames[36][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanShortNames[0][dispatchChanOfChan[chan]];
break;
}
return "??";
}
@ -1438,6 +1460,9 @@ int DivEngine::getChannelType(int chan) {
case DIV_SYSTEM_LYNX:
return chanTypes[36][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanTypes[38][dispatchChanOfChan[chan]];
break;
}
return 1;
}
@ -1588,6 +1613,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
case DIV_SYSTEM_LYNX:
return chanPrefType[42][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanPrefType[44][dispatchChanOfChan[chan]];
break;
}
return DIV_INS_FM;
}

View File

@ -4739,6 +4739,7 @@ bool FurnaceGUI::loop() {
sysAddOption(DIV_SYSTEM_LYNX);
sysAddOption(DIV_SYSTEM_QSOUND);
sysAddOption(DIV_SYSTEM_SWAN);
sysAddOption(DIV_SYSTEM_VERA);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("configure system...")) {
@ -4857,7 +4858,7 @@ bool FurnaceGUI::loop() {
break;
}
case DIV_SYSTEM_YM2151:
if (ImGui::RadioButton("NTSC (3.58MHz)",flags==0)) {
if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) {
e->setSysFlags(i,0,restart);
updateWindowTitle();
}
@ -5033,6 +5034,7 @@ bool FurnaceGUI::loop() {
}
case DIV_SYSTEM_GB:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA:
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL:
@ -5092,6 +5094,7 @@ bool FurnaceGUI::loop() {
sysChangeOption(i,DIV_SYSTEM_LYNX);
sysChangeOption(i,DIV_SYSTEM_QSOUND);
sysChangeOption(i,DIV_SYSTEM_SWAN);
sysChangeOption(i,DIV_SYSTEM_VERA);
ImGui::EndMenu();
}
}
@ -6636,6 +6639,13 @@ FurnaceGUI::FurnaceGUI():
0
}
));*/
cat.systems.push_back(FurnaceGUISysDef(
"Commander X16", {
DIV_SYSTEM_YM2151, 64, 0, 0,
DIV_SYSTEM_VERA, 64, 0, 0,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Arcade systems");

View File

@ -27,7 +27,7 @@
#include <imgui.h>
#include "plot_nolerp.h"
const char* insTypes[24]={
const char* insTypes[25]={
"Standard",
"FM (4-operator)",
"Game Boy",
@ -51,7 +51,8 @@ const char* insTypes[24]={
"POKEY",
"PC Beeper",
"WonderSwan",
"Atari Lynx"
"Atari Lynx",
"VERA"
};
const char* ssgEnvTypes[8]={
@ -791,9 +792,9 @@ void FurnaceGUI::drawInsEdit() {
} else {
DivInstrument* ins=e->song.ins[curIns];
ImGui::InputText("Name",&ins->name);
if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM;
if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM;
int insType=ins->type;
if (ImGui::Combo("Type",&insType,insTypes,24,24)) {
if (ImGui::Combo("Type",&insType,insTypes,25,24)) {
ins->type=(DivInstrumentType)insType;
}
@ -1355,7 +1356,7 @@ void FurnaceGUI::drawInsEdit() {
float loopIndicator[256];
const char* volumeLabel="Volume";
int volMax=(ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)?31:15;
int volMax=15;
int volMin=0;
if (ins->type==DIV_INS_C64) {
if (ins->c64.volIsCutoff) {
@ -1368,6 +1369,12 @@ void FurnaceGUI::drawInsEdit() {
}
}
}
if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) {
volMax=31;
}
if (ins->type==DIV_INS_VERA) {
volMax=63;
}
if (ins->type==DIV_INS_AMIGA) {
volMax=64;
}
@ -1381,7 +1388,7 @@ void FurnaceGUI::drawInsEdit() {
bool arpMode=ins->std.arpMacroMode;
const char* dutyLabel="Duty/Noise";
int dutyMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?31:3;
int dutyMax=3;
if (ins->type==DIV_INS_C64) {
dutyLabel="Duty";
if (ins->c64.dutyIsAbs) {
@ -1393,6 +1400,9 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_FM) {
dutyMax=32;
}
if ((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)) {
dutyMax=31;
}
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) {
dutyLabel="Noise Freq";
}
@ -1416,9 +1426,13 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) {
dutyMax=0;
}
if (ins->type==DIV_INS_VERA) {
dutyLabel="Duty";
dutyMax=63;
}
bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs);
int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63;
int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63;
bool bitMode=false;
if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) {
bitMode=true;