mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-22 12:35:11 +00:00
Add VERA support for Commander X16
This commit is contained in:
parent
98e2c51592
commit
9abf872ff3
13 changed files with 565 additions and 13 deletions
|
@ -303,6 +303,7 @@ src/engine/platform/saa.cpp
|
|||
src/engine/platform/amiga.cpp
|
||||
src/engine/platform/segapcm.cpp
|
||||
src/engine/platform/qsound.cpp
|
||||
src/engine/platform/vera.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
src/engine/platform/lynx.cpp
|
||||
)
|
||||
|
|
|
@ -23,6 +23,7 @@ depending on the instrument type, there are currently 10 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
|
||||
|
||||
|
|
8
papers/doc/4-instrument/vera.md
Normal file
8
papers/doc/4-instrument/vera.md
Normal 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
|
|
@ -39,6 +39,7 @@
|
|||
#include "platform/amiga.h"
|
||||
#include "platform/segapcm.h"
|
||||
#include "platform/qsound.h"
|
||||
#include "platform/vera.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "../ta-log.h"
|
||||
|
@ -226,6 +227,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
dispatch=new DivPlatformSegaPCM;
|
||||
break;
|
||||
case DIV_SYSTEM_VERA:
|
||||
dispatch = new DivPlatformVERA;
|
||||
break;
|
||||
default:
|
||||
logW("this system is not supported yet! using dummy platform.\n");
|
||||
dispatch=new DivPlatformDummy;
|
||||
|
|
|
@ -951,6 +951,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;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ enum DivInstrumentType {
|
|||
DIV_INS_BEEPER=21,
|
||||
DIV_INS_SWAN=22,
|
||||
DIV_INS_MIKEY=23,
|
||||
DIV_INS_VERA=24,
|
||||
};
|
||||
|
||||
// FM operator structure:
|
||||
|
|
404
src/engine/platform/vera.cpp
Normal file
404
src/engine/platform/vera.cpp
Normal file
|
@ -0,0 +1,404 @@
|
|||
/**
|
||||
* 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>
|
||||
|
||||
#define rWrite(c,a,d) {regPool[(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;
|
||||
}
|
||||
|
||||
void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
// taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef
|
||||
const uint8_t volume_lut_psg[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};
|
||||
const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64};
|
||||
for (size_t pos=start; pos<start+len; pos++) {
|
||||
int32_t lout=0;
|
||||
int32_t rout=0;
|
||||
// PSG
|
||||
for (int i=0; i<16; i++) {
|
||||
unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8);
|
||||
unsigned old_accum=chan[i].accum;
|
||||
unsigned new_accum=old_accum+freq;
|
||||
int val=0x20;
|
||||
// TODO actually emulate the LFSR, it's currently unknown publicly
|
||||
// and the official emulator just uses this:
|
||||
if ((old_accum^new_accum)&0x10000) chan[i].noiseval=rand()&63;
|
||||
new_accum&=0x1ffff;
|
||||
chan[i].accum=new_accum;
|
||||
switch (regPool[i*4+3]>>6) {
|
||||
case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break;
|
||||
case 1: val=(new_accum>>11); break;
|
||||
case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break;
|
||||
case 3: val=chan[i].noiseval; break;
|
||||
}
|
||||
val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f];
|
||||
lout+=(regPool[i*4+2]&0x40)?val:0;
|
||||
rout+=(regPool[i*4+2]&0x80)?val:0;
|
||||
}
|
||||
// PCM
|
||||
// simple one-channel sample player, actual hardware is essentially a DAC
|
||||
// with buffering
|
||||
if (chan[16].pcm.sample>=0) {
|
||||
chan[16].accum+=regPool[65];
|
||||
if (chan[16].accum>=128) {
|
||||
DivSample* s=parent->getSample(chan[16].pcm.sample);
|
||||
if (s->samples>0) {
|
||||
// TODO stereo samples once DivSample has a support for it
|
||||
switch (s->depth) {
|
||||
case 8:
|
||||
chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0;
|
||||
chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0;
|
||||
regPool[64]|=0x20; // for register viewer purposes
|
||||
break;
|
||||
case 16:
|
||||
chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0;
|
||||
chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0;
|
||||
regPool[64]&=~0x20;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
chan[16].pcm.sample=-1;
|
||||
}
|
||||
chan[16].accum&=0x7f;
|
||||
chan[16].pcm.pos++;
|
||||
if (chan[16].pcm.pos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
|
||||
chan[16].pcm.pos=s->loopStart;
|
||||
} else {
|
||||
chan[16].pcm.sample=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int pcmvol=volume_lut_pcm[regPool[64]&0x0f];
|
||||
lout+=chan[16].pcm.out_l*pcmvol/64;
|
||||
rout+=chan[16].pcm.out_r*pcmvol/64;
|
||||
|
||||
bufL[pos]=(short)(lout/2);
|
||||
bufR[pos]=(short)(rout/2);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
rWriteHi(i,2,3); // default pan
|
||||
}
|
||||
chan[16].vol=15;
|
||||
}
|
||||
|
||||
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<17; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
if (i<16) {
|
||||
chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0);
|
||||
rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63));
|
||||
} else {
|
||||
// NB this is currently assuming Amiga instrument type with a 0-64
|
||||
// (inclusive) volume range. This envelope is then scaled and added to
|
||||
// the channel volume. Is this a better way to handle this instead of
|
||||
// making another identical Amiga instrument type but with a 0-15
|
||||
// volume range?
|
||||
chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0);
|
||||
rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15));
|
||||
}
|
||||
}
|
||||
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 && i<16) {
|
||||
rWriteLo(i,3,chan[i].std.duty);
|
||||
}
|
||||
if (chan[i].std.hadWave && i<16) {
|
||||
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 (i<16) {
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
rWrite(i,0,chan[i].freq&0xff);
|
||||
rWrite(i,1,(chan[i].freq>>8)&0xff);
|
||||
} else {
|
||||
if (chan[i].freq>128) chan[i].freq=128;
|
||||
rWrite(16,1,chan[i].freq&0xff);
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
// PCM
|
||||
chan[16].std.next();
|
||||
if (chan[16].std.hadVol) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformVERA::dispatch(DivCommand c) {
|
||||
int tmp;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
tmp = isMuted[c.chan]?0:chan[c.chan].vol;
|
||||
if(c.chan<16) {
|
||||
rWriteLo(c.chan,2,tmp)
|
||||
} 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(tmp);
|
||||
}
|
||||
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,(isMuted[c.chan]?0:tmp));
|
||||
} else {
|
||||
tmp=c.value&0x0f;
|
||||
chan[c.chan].vol=tmp;
|
||||
rWriteFIFOVol(isMuted[c.chan]?0: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;
|
||||
if (c.chan<16) {
|
||||
rWriteHi(c.chan,2,tmp);
|
||||
} else {
|
||||
chan[c.chan].pcm.pan = tmp&3;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
chipClock=25000000;
|
||||
rate=chipClock/512;
|
||||
reset();
|
||||
return 17;
|
||||
}
|
||||
|
||||
DivPlatformVERA::~DivPlatformVERA() {
|
||||
}
|
74
src/engine/platform/vera.h
Normal file
74
src/engine/platform/vera.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
class DivPlatformVERA: public DivDispatch {
|
||||
protected:
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
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, pan;
|
||||
PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {}
|
||||
} pcm;
|
||||
Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {}
|
||||
};
|
||||
Channel chan[17];
|
||||
bool isMuted[17];
|
||||
unsigned char regPool[66];
|
||||
|
||||
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);
|
||||
~DivPlatformVERA();
|
||||
|
||||
private:
|
||||
int calcNoteFreq(int ch, int note);
|
||||
};
|
||||
#endif
|
|
@ -258,6 +258,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
|||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
|
|||
return DIV_SYSTEM_LYNX;
|
||||
case 0xa9:
|
||||
return DIV_SYSTEM_SEGAPCM_COMPAT;
|
||||
case 0xaa:
|
||||
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 0xaa;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -4597,6 +4597,7 @@ bool FurnaceGUI::loop() {
|
|||
sysAddOption(DIV_SYSTEM_AY8930);
|
||||
sysAddOption(DIV_SYSTEM_LYNX);
|
||||
sysAddOption(DIV_SYSTEM_QSOUND);
|
||||
sysAddOption(DIV_SYSTEM_VERA);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("configure system...")) {
|
||||
|
@ -4715,7 +4716,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();
|
||||
}
|
||||
|
@ -4913,6 +4914,7 @@ bool FurnaceGUI::loop() {
|
|||
sysChangeOption(i,DIV_SYSTEM_AY8930);
|
||||
sysChangeOption(i,DIV_SYSTEM_LYNX);
|
||||
sysChangeOption(i,DIV_SYSTEM_QSOUND);
|
||||
sysChangeOption(i,DIV_SYSTEM_VERA);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]={
|
||||
|
@ -783,9 +784,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;
|
||||
}
|
||||
|
||||
|
@ -1304,7 +1305,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) {
|
||||
|
@ -1317,6 +1318,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;
|
||||
}
|
||||
|
@ -1330,7 +1337,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) {
|
||||
|
@ -1342,6 +1349,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";
|
||||
}
|
||||
|
@ -1361,9 +1371,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;
|
||||
|
|
Loading…
Reference in a new issue