Merge branch 'tildearrow:master' into vrc6

This commit is contained in:
cam900 2022-03-28 12:39:00 +09:00 committed by GitHub
commit 5060c0c140
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1275 additions and 24 deletions

View file

@ -126,6 +126,19 @@ enum DivDispatchCmds {
DIV_CMD_WS_SWEEP_TIME,
DIV_CMD_WS_SWEEP_AMOUNT,
DIV_CMD_N163_WAVE_POSITION,
DIV_CMD_N163_WAVE_LENGTH,
DIV_CMD_N163_WAVE_MODE,
DIV_CMD_N163_WAVE_LOAD,
DIV_CMD_N163_WAVE_LOADPOS,
DIV_CMD_N163_WAVE_LOADLEN,
DIV_CMD_N163_WAVE_LOADMODE,
DIV_CMD_N163_CHANNEL_LIMIT,
DIV_CMD_N163_GLOBAL_WAVE_LOAD,
DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,
DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,
DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,
DIV_ALWAYS_SET_VOLUME,
DIV_CMD_MAX

View file

@ -46,6 +46,7 @@
#include "platform/swan.h"
#include "platform/lynx.h"
#include "platform/bubsyswsg.h"
#include "platform/n163.h"
#include "platform/pet.h"
#include "platform/vic20.h"
#include "platform/vrc6.h"
@ -278,6 +279,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_BUBSYS_WSG:
dispatch=new DivPlatformBubSysWSG;
break;
case DIV_SYSTEM_N163:
dispatch=new DivPlatformN163;
break;
case DIV_SYSTEM_PET:
dispatch=new DivPlatformPET;
break;

View file

@ -42,8 +42,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev72"
#define DIV_ENGINE_VERSION 72
#define DIV_VERSION "dev73"
#define DIV_ENGINE_VERSION 73
// for imports
#define DIV_VERSION_MOD 0xff01

View file

@ -385,6 +385,13 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->write(amiga.noteFreq,120*sizeof(unsigned int));
w->write(amiga.noteMap,120*sizeof(short));
}
// N163
w->writeI(n163.wave);
w->writeC(n163.wavePos);
w->writeC(n163.waveLen);
w->writeC(n163.waveMode);
w->writeC(0); // reserved
}
DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
@ -740,6 +747,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
}
}
// N163
if (version>=73) {
n163.wave=reader.readI();
n163.wavePos=(unsigned char)reader.readC();
n163.waveLen=(unsigned char)reader.readC();
n163.waveMode=(unsigned char)reader.readC();
reader.readC(); // reserved
}
return DIV_DATA_SUCCESS;
}

View file

@ -385,6 +385,17 @@ struct DivInstrumentAmiga {
}
};
struct DivInstrumentN163 {
int wave, wavePos, waveLen;
unsigned char waveMode;
DivInstrumentN163():
wave(-1),
wavePos(0),
waveLen(32),
waveMode(3) {}
};
struct DivInstrument {
String name;
bool mode;
@ -394,6 +405,7 @@ struct DivInstrument {
DivInstrumentGB gb;
DivInstrumentC64 c64;
DivInstrumentAmiga amiga;
DivInstrumentN163 n163;
/**
* save the instrument to a SafeWriter.

View file

@ -0,0 +1,656 @@
/**
* 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 "n163.h"
#include "../engine.h"
#include <math.h>
#define rRead(a,v) n163->addr_w(a); n163->data_r(v);
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWriteMask(a,v,m) if (!skipRegisterWrites) {writes.emplace(a,v,m); if (dumpWrites) {addWrite(a,v);} }
#define chWrite(c,a,v) \
if (c<=chanMax) { \
rWrite(0x78-(c<<3)+(a&7),v) \
}
#define chWriteMask(c,a,v,m) \
if (c<=chanMax) { \
rWriteMask(0x78-(c<<3)+(a&7),v,m) \
}
#define CHIP_FREQBASE (15*32768)
const char* regCheatSheetN163[]={
"FreqL7", "40",
"AccL7", "41",
"FreqM7", "42",
"AccM7", "43",
"WavLen_FreqH7", "44",
"AccH7", "45",
"WavPos7", "46",
"Vol7", "47",
"FreqL6", "48",
"AccL6", "49",
"FreqM6", "4A",
"AccM6", "4B",
"WavLen_FreqH6", "4C",
"AccH6", "4D",
"WavPos6", "4E",
"Vol6", "4F",
"FreqL5", "50",
"AccL5", "51",
"FreqM5", "52",
"AccM5", "53",
"WavLen_FreqH5", "54",
"AccH5", "55",
"WavPos5", "56",
"Vol5", "57",
"FreqL4", "58",
"AccL4", "59",
"FreqM4", "5A",
"AccM4", "5B",
"WavLen_FreqH4", "5C",
"AccH4", "5D",
"WavPos4", "5E",
"Vol4", "5F",
"FreqL3", "60",
"AccL3", "61",
"FreqM3", "62",
"AccM3", "63",
"WavLen_FreqH3", "64",
"AccH3", "65",
"WavPos3", "66",
"Vol3", "67",
"FreqL2", "68",
"AccL2", "69",
"FreqM2", "6A",
"AccM2", "6B",
"WavLen_FreqH2", "6C",
"AccH2", "6D",
"WavPos2", "6E",
"Vol2", "6F",
"FreqL1", "70",
"AccL1", "71",
"FreqM1", "72",
"AccM1", "73",
"WavLen_FreqH1", "74",
"AccH1", "75",
"WavPos1", "76",
"Vol1", "77",
"FreqL0", "78",
"AccL0", "79",
"FreqM0", "7A",
"AccM0", "7B",
"WavLen_FreqH0", "7C",
"AccH0", "7D",
"WavPos0", "7E",
"ChanMax_Vol0", "7F",
NULL
};
const char** DivPlatformN163::getRegisterSheet() {
return regCheatSheetN163;
}
const char* DivPlatformN163::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Select waveform";
break;
case 0x11:
return "11xx: Set waveform position in RAM (single nibble unit)";
break;
case 0x12:
return "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)";
break;
case 0x13:
return "130x: Change waveform update mode (0: off, bit 0: update now, bit 1: update when every waveform changes)";
break;
case 0x14:
return "14xx: Select waveform for load to RAM";
break;
case 0x15:
return "15xx: Set waveform position for load to RAM (single nibble unit)";
break;
case 0x16:
return "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)";
break;
case 0x17:
return "170x: Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)";
break;
case 0x18:
return "180x: Change channel limits (0 to 7, x + 1)";
break;
case 0x20:
return "20xx: (Global) Select waveform for load to RAM";
break;
case 0x21:
return "21xx: (Global) Set waveform position for load to RAM (single nibble unit)";
break;
case 0x22:
return "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)";
break;
case 0x23:
return "230x: (Global) Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)";
break;
}
return NULL;
}
void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t i=start; i<start+len; i++) {
n163->tick();
int out=(n163->out()<<6)*(chanMax+1); // scale to 16 bit
if (out>32767) out=32767;
if (out<-32768) out=-32768;
bufL[i]=bufR[i]=out;
// command queue
while (!writes.empty()) {
QueuedWrite w=writes.front();
n163->addr_w(w.addr);
n163->data_w((n163->data_r()&~w.mask)|(w.val&w.mask));
writes.pop();
}
}
}
void DivPlatformN163::updateWave(int wave, int pos, int len) {
len&=0xfc; // 4 nibble boundary
DivWavetable* wt=parent->getWave(wave);
for (int i=0; i<len; i++) {
unsigned char addr=(pos+i); // address (nibble each)
if (addr>=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area
break;
}
unsigned char mask=(addr&1)?0xf0:0x0f;
if (wt->max<1 || wt->len<1) {
rWriteMask(addr>>1,0,mask);
} else {
int data=wt->data[i*wt->len/len]*15/wt->max;
if (data<0) data=0;
if (data>15) data=15;
rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask);
}
}
}
void DivPlatformN163::updateWaveCh(int ch) {
if (ch<=chanMax) {
updateWave(chan[ch].wave,chan[ch].wavePos,chan[ch].waveLen);
if (chan[ch].active) {
chan[ch].volumeChanged=true;
}
}
}
void DivPlatformN163::tick() {
for (int i=0; i<=chanMax; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
chan[i].outVol=(MIN(15,chan[i].std.vol)*(chan[i].vol&15))/15;
if (chan[i].outVol<0) chan[i].outVol=0;
if (chan[i].outVol>15) chan[i].outVol=15;
if (chan[i].resVol!=chan[i].outVol) {
chan[i].resVol=chan[i].outVol;
if (!isMuted[i]) {
chan[i].volumeChanged=true;
}
}
}
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.hadDuty) {
if (chan[i].wavePos!=chan[i].std.duty) {
chan[i].wavePos=chan[i].std.duty;
if (chan[i].waveMode&0x2) {
chan[i].waveUpdated=true;
}
chan[i].waveChanged=true;
}
}
if (chan[i].std.hadWave) {
if (chan[i].wave!=chan[i].std.wave) {
chan[i].wave=chan[i].std.wave;
if (chan[i].waveMode&0x2) {
chan[i].waveUpdated=true;
}
}
}
if (chan[i].std.hadEx1) {
if (chan[i].waveLen!=(chan[i].std.ex1&0xfc)) {
chan[i].waveLen=chan[i].std.ex1&0xfc;
if (chan[i].waveMode&0x2) {
chan[i].waveUpdated=true;
}
chan[i].freqChanged=true;
}
}
if (chan[i].std.hadEx2) {
if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed
chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2);
if (chan[i].waveMode&0x2) {
chan[i].waveUpdated=true;
chan[i].waveChanged=true;
}
}
if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now
chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1);
if (chan[i].waveMode&0x1) { // rising edge
chan[i].waveUpdated=true;
chan[i].waveChanged=true;
}
}
}
if (chan[i].std.hadEx3) {
if (chan[i].loadWave!=chan[i].std.ex3) {
chan[i].loadWave=chan[i].std.ex3;
if (chan[i].loadMode&0x2) {
updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc);
}
}
}
if (chan[i].std.hadAlg) {
if (chan[i].loadPos!=chan[i].std.alg) {
chan[i].loadPos=chan[i].std.alg;
}
}
if (chan[i].std.hadFb) {
if (chan[i].loadLen!=(chan[i].std.fb&0xfc)) {
chan[i].loadLen=chan[i].std.fb&0xfc;
}
}
if (chan[i].std.hadFms) {
if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes
chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2);
}
if ((chan[i].loadMode&0x1)!=(chan[i].std.fms&0x1)) { // load now
chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms&0x1);
if (chan[i].loadMode&0x1) { // rising edge
updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc);
}
}
}
if (chan[i].volumeChanged) {
if ((!chan[i].active) || isMuted[i]) {
chWriteMask(i,0x7,0,0xf);
} else {
chWriteMask(i,0x7,chan[i].resVol&0xf,0xf);
}
chan[i].volumeChanged=false;
}
if (chan[i].waveChanged) {
chWrite(i,0x6,chan[i].wavePos);
chan[i].freqChanged=true;
chan[i].waveChanged=false;
}
if (chan[i].waveUpdated) {
updateWaveCh(i);
if (!chan[i].keyOff) chan[i].keyOn=true;
chan[i].waveUpdated=false;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0);
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
if (chan[i].keyOn) {
if (chan[i].wave<0) {
chan[i].wave=0;
if (chan[i].waveMode&0x2) {
updateWaveCh(i);
}
}
}
if (chan[i].keyOff && !isMuted[i]) {
chWriteMask(i,0x07,0,0xf);
}
chWrite(i,0x0,chan[i].freq&0xff);
chWrite(i,0x2,chan[i].freq>>8);
chWrite(i,0x4,((256-chan[i].waveLen)&0xfc)|((chan[i].freq>>16)&3));
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformN163::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if (chan[c.chan].insChanged) {
chan[c.chan].wave=ins->n163.wave;
chan[c.chan].wavePos=ins->n163.wavePos;
chan[c.chan].waveLen=ins->n163.waveLen;
chan[c.chan].waveMode=ins->n163.waveMode;
chan[c.chan].waveChanged=true;
if (chan[c.chan].waveMode&0x3) {
chan[c.chan].waveUpdated=true;
}
chan[c.chan].insChanged=false;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].resVol=chan[c.chan].vol;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
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].keyOn=false;
//chan[c.chan].std.init(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].std.release();
break;
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].insChanged=true;
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;
chan[c.chan].resVol=chan[c.chan].outVol;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
}
}
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=NOTE_FREQUENCY(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_WAVE:
chan[c.chan].wave=c.value;
if (chan[c.chan].waveMode&0x2) {
chan[c.chan].waveUpdated=true;
}
chan[c.chan].keyOn=true;
break;
case DIV_CMD_N163_WAVE_POSITION:
chan[c.chan].wavePos=c.value;
if (chan[c.chan].waveMode&0x2) {
chan[c.chan].waveUpdated=true;
}
chan[c.chan].waveChanged=true;
break;
case DIV_CMD_N163_WAVE_LENGTH:
chan[c.chan].waveLen=c.value&0xfc;
if (chan[c.chan].waveMode&0x2) {
chan[c.chan].waveUpdated=true;
}
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_N163_WAVE_MODE:
chan[c.chan].waveMode=c.value&0x3;
if (chan[c.chan].waveMode&0x3) { // update now
chan[c.chan].waveUpdated=true;
chan[c.chan].waveChanged=true;
}
break;
case DIV_CMD_N163_WAVE_LOAD:
chan[c.chan].loadWave=c.value;
if (chan[c.chan].loadMode&0x2) { // load when every waveform changes
updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen);
}
break;
case DIV_CMD_N163_WAVE_LOADPOS:
chan[c.chan].loadPos=c.value;
break;
case DIV_CMD_N163_WAVE_LOADLEN:
chan[c.chan].loadLen=c.value&0xfc;
break;
case DIV_CMD_N163_WAVE_LOADMODE:
chan[c.chan].loadMode=c.value&0x3;
if (chan[c.chan].loadMode&0x1) { // load now
updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen);
}
break;
case DIV_CMD_N163_GLOBAL_WAVE_LOAD:
loadWave=c.value;
if (loadMode&0x2) { // load when every waveform changes
updateWave(loadWave,loadPos,loadLen);
}
break;
case DIV_CMD_N163_GLOBAL_WAVE_LOADPOS:
loadPos=c.value;
break;
case DIV_CMD_N163_GLOBAL_WAVE_LOADLEN:
loadLen=c.value&0xfc;
break;
case DIV_CMD_N163_GLOBAL_WAVE_LOADMODE:
loadMode=c.value&0x3;
if (loadMode&0x3) { // load now
updateWave(loadWave,loadPos,loadLen);
}
break;
case DIV_CMD_N163_CHANNEL_LIMIT:
if (chanMax!=(c.value&0x7)) {
chanMax=c.value&0x7;
rWriteMask(0x7f,chanMax<<4,0x70);
forceIns();
}
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(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].keyOn=true;
}
}
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 DivPlatformN163::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].volumeChanged=true;
}
void DivPlatformN163::forceIns() {
for (int i=0; i<=chanMax; i++) {
chan[i].insChanged=true;
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
chan[i].volumeChanged=true;
chan[i].waveChanged=true;
if (chan[i].waveMode&0x2) {
chan[i].waveUpdated=true;
}
}
}
}
void DivPlatformN163::notifyWaveChange(int wave) {
for (int i=0; i<8; i++) {
if (chan[i].wave==wave) {
if (chan[i].waveMode&0x2) {
chan[i].waveUpdated=true;
}
}
}
}
void DivPlatformN163::notifyInsChange(int ins) {
for (int i=0; i<8; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformN163::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void* DivPlatformN163::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformN163::getRegisterPool() {
for (int i=0; i<128; i++) {
regPool[i]=n163->reg(i);
}
return regPool;
}
int DivPlatformN163::getRegisterPoolSize() {
return 128;
}
void DivPlatformN163::reset() {
while (!writes.empty()) writes.pop();
for (int i=0; i<8; i++) {
chan[i]=DivPlatformN163::Channel();
}
n163->reset();
memset(regPool,0,128);
n163->set_disable(false);
rWrite(0x7f,chanMax<<4);
loadWave=-1;
loadPos=0;
loadLen=0;
loadMode=0;
}
void DivPlatformN163::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformN163::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
void DivPlatformN163::setFlags(unsigned int flags) {
switch (flags&0xf) {
case 0x0: // NTSC
rate=COLOR_NTSC/2.0;
break;
case 0x1: // PAL
rate=COLOR_PAL*3.0/8.0;
break;
case 0x2: // Dendy
rate=COLOR_PAL*2.0/5.0;
break;
}
chanMax=(flags>>4)&7;
chipClock=rate;
rate/=15;
}
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
isMuted[i]=false;
}
n163=new n163_core();
setFlags(flags);
reset();
return 8;
}
void DivPlatformN163::quit() {
delete n163;
}
DivPlatformN163::~DivPlatformN163() {
}

107
src/engine/platform/n163.h Normal file
View file

@ -0,0 +1,107 @@
/**
* 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 _N163_H
#define _N163_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "sound/n163/n163.hpp"
class DivPlatformN163: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
short ins, wave, wavePos, waveLen;
unsigned char waveMode;
short loadWave, loadPos, loadLen;
unsigned char loadMode;
bool active, insChanged, freqChanged, volumeChanged, waveChanged, waveUpdated, keyOn, keyOff, inPorta;
signed char vol, outVol, resVol;
DivMacroInt std;
Channel():
freq(0),
baseFreq(0),
pitch(0),
note(0),
ins(-1),
wave(-1),
wavePos(0),
waveLen(0),
waveMode(0),
loadWave(-1),
loadPos(0),
loadLen(0),
loadMode(0),
active(false),
insChanged(true),
freqChanged(false),
volumeChanged(false),
waveChanged(false),
waveUpdated(false),
keyOn(false),
keyOff(false),
inPorta(false),
vol(15),
outVol(15),
resVol(15) {}
};
Channel chan[8];
bool isMuted[8];
struct QueuedWrite {
unsigned char addr;
unsigned char val;
unsigned char mask;
QueuedWrite(unsigned char a, unsigned char v, unsigned char m=~0): addr(a), val(v), mask(m) {}
};
std::queue<QueuedWrite> writes;
unsigned char chanMax;
short loadWave, loadPos, loadLen;
unsigned char loadMode;
n163_core *n163;
unsigned char regPool[128];
void updateWave(int wave, int pos, int len);
void updateWaveCh(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();
void reset();
void forceIns();
void tick();
void muteChannel(int ch, bool mute);
void setFlags(unsigned int flags);
void notifyWaveChange(int wave);
void notifyInsChange(int ins);
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();
~DivPlatformN163();
};
#endif

View file

@ -0,0 +1,137 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Namco 163 Sound emulation core
This chip is one of NES mapper with sound expansion, This one is by Namco.
It has 1 to 8 wavetable channels, All channel registers and waveforms are stored to internal RAM.
4 bit Waveforms are freely allocatable, and its length is variables; its can be stores many short waveforms or few long waveforms in RAM.
But waveforms are needs to squash, reallocate to avoid conflict with channel register area, each channel register size is 8 bytes per channels.
Sound output is time division multiplexed, it's can be captured only single channels output at once. in reason, More activated channels are less sound quality.
Sound register layout
Address Bit Description
7654 3210
78-7f Channel 0
78 xxxx xxxx Channel 0 Pitch input bit 0-7
79 xxxx xxxx Channel 0 Accumulator bit 0-7*
7a xxxx xxxx Channel 0 Pitch input bit 8-15
7b xxxx xxxx Channel 0 Accumulator bit 8-15*
7c xxxx xx-- Channel 0 Waveform length, 256 - (x * 4)
---- --xx Channel 0 Pitch input bit 16-17
7d xxxx xxxx Channel 0 Accumulator bit 16-23*
7e xxxx xxxx Channel 0 Waveform base offset
xxxx xxx- RAM byte (0 to 127)
---- ---x RAM nibble
---- ---0 Low nibble
---- ---1 High nibble
7f ---- xxxx Channel 0 Volume
7f Number of active channels
7f -xxx ---- Number of active channels
-000 ---- Channel 0 activated
-001 ---- Channel 1 activated
-010 ---- Channel 2 activated
...
-110 ---- Channel 6 activated
-111 ---- Channel 7 activated
70-77 Channel 1 (Optional if activated)
68-6f Channel 2 (Optional if activated)
...
48-4f Channel 6 (Optional if activated)
40-47 Channel 7 (Optional if activated)
Rest of RAM area are for 4 bit Waveform and/or scratchpad.
Each waveform byte has 2 nibbles packed, fetches LSB first, MSB next.
---- xxxx 4 bit waveform, LSB
xxxx ---- Same as above, MSB
Waveform address: Waveform base offset + Bit 16 to 23 of Accumulator, 1 LSB of result is nibble select, 7 MSB of result is Byte address in RAM.
Frequency formula:
Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536)
*/
#include "n163.hpp"
void n163_core::tick()
{
m_out = 0;
// 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank 0x8000-0x9fff select.)
if (m_disable)
return;
// tick per each clock
const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | (bitfield<u32>(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency
u32 accum = m_ram[m_voice_cycle + 1] | (u32(m_ram[m_voice_cycle + 3]) << 8) | ( u32(m_ram[m_voice_cycle + 5]) << 16); // 24 bit accumulator
const u16 length = 256 - (m_ram[m_voice_cycle + 4] & 0xfc);
const u8 addr = m_ram[m_voice_cycle + 6] + bitfield(accum, 16, 8);
const s16 wave = (bitfield(m_ram[bitfield(addr, 1, 7)], bitfield(addr, 0) << 2, 4) - 8);
const s16 volume = bitfield(m_ram[m_voice_cycle + 7], 0, 4);
// accumulate address
accum = bitfield(accum + freq, 0, 24);
if (bitfield(accum, 16, 8) >= length)
accum = bitfield(accum, 0, 18);
// writeback to register
m_ram[m_voice_cycle + 1] = bitfield(accum, 0, 8);
m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8);
m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8);
// update voice cycle
m_voice_cycle -= 0x8;
if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3)))
m_voice_cycle = 0x78;
// output 4 bit waveform and volume, multiplexed
m_out = wave * volume;
}
void n163_core::reset()
{
// reset this chip
m_disable = false;
std::fill(std::begin(m_ram), std::end(m_ram), 0);
m_voice_cycle = 0x78;
m_addr_latch.reset();
m_out = 0;
}
// accessor
void n163_core::addr_w(u8 data)
{
// 0xf800-0xffff Sound address, increment
m_addr_latch.addr = bitfield(data, 0, 7);
m_addr_latch.incr = bitfield(data, 7);
}
void n163_core::data_w(u8 data, bool cpu_access)
{
// 0x4800-0x4fff Sound data write
m_ram[m_addr_latch.addr] = data;
// address latch increment
if (cpu_access && m_addr_latch.incr)
m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7);
}
u8 n163_core::data_r(bool cpu_access)
{
// 0x4800-0x4fff Sound data read
const u8 ret = m_ram[m_addr_latch.addr];
// address latch increment
if (cpu_access && m_addr_latch.incr)
m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7);
return ret;
}

View file

@ -0,0 +1,78 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Namco 163 Sound emulation core
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_N163_HPP
#define _VGSOUND_EMU_N163_HPP
#pragma once
namespace n163
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
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 n163;
class n163_core
{
public:
// accessors, getters, setters
void addr_w(u8 data);
void data_w(u8 data, bool cpu_access = false);
u8 data_r(bool cpu_access = false);
void set_disable(bool disable) { m_disable = disable; }
// internal state
void reset();
void tick();
// sound output pin
s16 out() { return m_out; }
// register pool
u8 reg(u8 addr) { return m_ram[addr & 0x7f]; }
private:
// Address latch
struct addr_latch_t
{
addr_latch_t()
: addr(0)
, incr(0)
{ };
void reset()
{
addr = 0;
incr = 0;
}
u8 addr : 7;
u8 incr : 1;
};
bool m_disable = false;
u8 m_ram[0x80] = {0}; // internal 128 byte RAM
u8 m_voice_cycle = 0x78; // Voice cycle for processing
addr_latch_t m_addr_latch; // address latch
s16 m_out = 0; // output
};
#endif

View file

@ -22,8 +22,6 @@
#include <cstddef>
#include <math.h>
#define CHIP_DIVIDER 1 // 16 for pulse, 14 for sawtooth
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define chWrite(c,a,v) rWrite(0x9000+(c<<12)+(a&3),v)
@ -139,6 +137,8 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len
void DivPlatformVRC6::tick() {
for (int i=0; i<3; i++) {
// 16 for pulse; 14 for saw
int CHIP_DIVIDER=(i==2)?14:16;
chan[i].std.next();
if (chan[i].std.hadVol) {
if (i==2) { // sawtooth
@ -180,9 +180,9 @@ void DivPlatformVRC6::tick() {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (i==2) { // sawtooth
chan[i].freq=parent->calcFreq(chan[i].baseFreq/14,chan[i].pitch,true)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
} else { // pulse
chan[i].freq=parent->calcFreq(chan[i].baseFreq/16,chan[i].pitch,true)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
if (chan[i].furnaceDac) {
double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
@ -199,10 +199,6 @@ void DivPlatformVRC6::tick() {
}
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) {
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
//rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
}
if (chan[i].keyOff) {
chWrite(i,2,0);
} else {
@ -217,6 +213,7 @@ void DivPlatformVRC6::tick() {
}
int DivPlatformVRC6::dispatch(DivCommand c) {
int CHIP_DIVIDER=(c.chan==2)?14:16;
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
if (c.chan!=2) { // pulse wave

View file

@ -134,6 +134,18 @@ const char* cmdName[DIV_CMD_MAX]={
"WS_SWEEP_TIME",
"WS_SWEEP_AMOUNT",
"N163_WAVE_POSITION",
"N163_WAVE_LENGTH",
"N163_WAVE_MODE",
"N163_WAVE_LOAD",
"N163_WAVE_LOADPOS",
"N163_WAVE_LOADLEN",
"N163_CHANNEL_LIMIT",
"N163_GLOBAL_WAVE_LOAD",
"N163_GLOBAL_WAVE_LOADPOS",
"N163_GLOBAL_WAVE_LOADLEN",
"N163_GLOBAL_WAVE_LOADMODE",
"ALWAYS_SET_VOLUME"
};
@ -254,6 +266,51 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
return false;
}
break;
case DIV_SYSTEM_N163:
switch (effect) {
case 0x10: // select instrument waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x11: // select instrument waveform position in RAM
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_POSITION,ch,effectVal));
break;
case 0x12: // select instrument waveform length in RAM
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LENGTH,ch,effectVal));
break;
case 0x13: // change instrument waveform update mode
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_MODE,ch,effectVal));
break;
case 0x14: // select waveform for load to RAM
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOAD,ch,effectVal));
break;
case 0x15: // select waveform position for load to RAM
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADPOS,ch,effectVal));
break;
case 0x16: // select waveform length for load to RAM
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADLEN,ch,effectVal));
break;
case 0x17: // change waveform load mode
dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADMODE,ch,effectVal));
break;
case 0x18: // change channel limits
dispatchCmd(DivCommand(DIV_CMD_N163_CHANNEL_LIMIT,ch,effectVal));
break;
case 0x20: // (global) select waveform for load to RAM
dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOAD,ch,effectVal));
break;
case 0x21: // (global) select waveform position for load to RAM
dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,ch,effectVal));
break;
case 0x22: // (global) select waveform length for load to RAM
dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,ch,effectVal));
break;
case 0x23: // (global) change waveform load mode
dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,ch,effectVal));
break;
default:
return false;
}
break;
case DIV_SYSTEM_QSOUND:
switch (effect) {
case 0x10: // echo feedback