Merge branch 'master' of https://github.com/tildearrow/furnace into ymf289b

This commit is contained in:
cam900 2023-02-23 21:15:49 +09:00
commit e694687fff
415 changed files with 152066 additions and 7336 deletions

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -175,7 +175,7 @@ String DivConfig::getString(String key, String fallback) const {
return fallback;
}
bool DivConfig::has(String key) {
bool DivConfig::has(String key) const {
try {
String test=conf.at(key);
} catch (std::out_of_range& e) {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -46,7 +46,7 @@ class DivConfig {
String getString(String key, String fallback) const;
// check for existence
bool has(String key);
bool has(String key) const;
// set a config value
void set(String key, bool value);

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -161,3 +161,7 @@ void DivEngine::setConf(String key, const char* value) {
void DivEngine::setConf(String key, String value) {
conf.set(key,value);
}
bool DivEngine::hasConf(String key) {
return conf.has(key);
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -24,6 +24,7 @@
#define DIV_MAX_CHIPS 32
#define DIV_MAX_CHANS 128
#define DIV_MAX_PATTERNS 256
#define DIV_MAX_CHIP_DEFS 256
// in-pattern
#define DIV_MAX_ROWS 256

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -103,9 +103,8 @@ enum DivDispatchCmds {
DIV_CMD_FM_AM_DEPTH, // (depth)
DIV_CMD_FM_PM_DEPTH, // (depth)
DIV_CMD_GENESIS_LFO, // unused?
DIV_CMD_ARCADE_LFO, // unused?
DIV_CMD_FM_LFO2, // (speed)
DIV_CMD_FM_LFO2_WAVE, // (waveform)
DIV_CMD_STD_NOISE_FREQ, // (freq)
DIV_CMD_STD_NOISE_MODE, // (mode)
@ -215,6 +214,21 @@ enum DivDispatchCmds {
DIV_CMD_SURROUND_PANNING, // (out, val)
DIV_CMD_FM_AM2_DEPTH, // (depth)
DIV_CMD_FM_PM2_DEPTH, // (depth)
DIV_CMD_ES5506_FILTER_MODE, // (value)
DIV_CMD_ES5506_FILTER_K1, // (value, mask)
DIV_CMD_ES5506_FILTER_K2, // (value, mask)
DIV_CMD_ES5506_FILTER_K1_SLIDE, // (value, negative)
DIV_CMD_ES5506_FILTER_K2_SLIDE, // (value, negative)
DIV_CMD_ES5506_ENVELOPE_COUNT, // (count)
DIV_CMD_ES5506_ENVELOPE_LVRAMP, // (ramp)
DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp)
DIV_CMD_ES5506_ENVELOPE_K1RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_PAUSE, // (value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX
@ -632,8 +646,8 @@ class DivDispatch {
#define COLOR_PAL (283.75*15625.0+25.0)
#define CLAMP_VAR(x,xMin,xMax) \
if (x<xMin) x=xMin; \
if (x>xMax) x=xMax;
if ((x)<(xMin)) (x)=(xMin); \
if ((x)>(xMax)) (x)=(xMax);
#define NEW_ARP_STRAT (parent->song.linearPitch==2 && !parent->song.oldArpStrategy)
#define HACKY_LEGATO_MESS chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode && !NEW_ARP_STRAT

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -67,6 +67,7 @@
#include "platform/vrc6.h"
#include "platform/fds.h"
#include "platform/mmc5.h"
#include "platform/es5506.h"
#include "platform/scc.h"
#include "platform/ymz280b.h"
#include "platform/rf5c68.h"
@ -74,6 +75,7 @@
#include "platform/vb.h"
#include "platform/k007232.h"
#include "platform/ga20.h"
#include "platform/sm8521.h"
#include "platform/pcmdac.h"
#include "platform/dummy.h"
#include "../ta-log.h"
@ -436,6 +438,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_MMC5:
dispatch=new DivPlatformMMC5;
break;
case DIV_SYSTEM_ES5506:
dispatch=new DivPlatformES5506;
break;
case DIV_SYSTEM_SCC:
dispatch=new DivPlatformSCC;
((DivPlatformSCC*)dispatch)->setChipModel(false);
@ -485,6 +490,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_GA20:
dispatch=new DivPlatformGA20;
break;
case DIV_SYSTEM_SM8521:
dispatch=new DivPlatformSM8521;
break;
case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC;
break;

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -60,7 +60,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
case 0x08:
return "08xy: Set panning (x: left; y: right)";
case 0x09:
return "09xx: Set speed 1";
return "09xx: Set groove pattern (speed 1 if no grooves exist)";
case 0x0a:
return "0Axy: Volume slide (0y: down; x0: up)";
case 0x0b:
@ -70,7 +70,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
case 0x0d:
return "0Dxx: Jump to next pattern";
case 0x0f:
return "0Fxx: Set speed 2";
return "0Fxx: Set speed (speed 2 if no grooves exist)";
case 0x80:
return "80xx: Set panning (00: left; 80: center; FF: right)";
case 0x81:
@ -110,8 +110,6 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
return "EDxx: Note delay";
case 0xee:
return "EExx: Send external command";
case 0xef:
return "EFxx: Set global tuning (quirky!)";
case 0xf0:
return "F0xx: Set tick rate (bpm)";
case 0xf1:
@ -1471,6 +1469,42 @@ void DivEngine::createNew(const char* description, String sysName, bool inBase64
BUSY_END;
}
void DivEngine::createNewFromDefaults() {
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
song.unload();
song=DivSong();
changeSong(0);
String preset=getConfString("initialSys2","");
bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135;
if (preset.empty()) {
// try loading old preset
preset=decodeSysDesc(getConfString("initialSys",""));
oldVol=false;
}
logD("preset size %ld",preset.size());
if (preset.size()>0 && (preset.size()&3)==0) {
initSongWithDesc(preset.c_str(),true,oldVol);
}
String sysName=getConfString("initialSysName","");
if (sysName=="") {
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
} else {
song.systemName=sysName;
}
recalcChans();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
void DivEngine::swapChannels(int src, int dest) {
logV("swapping channel %d with %d",src,dest);
if (src==dest) {
@ -1961,14 +1995,12 @@ String DivEngine::getPlaybackDebugInfo() {
"cmdsPerSecond: %d\n"
"globalPitch: %d\n"
"extValue: %d\n"
"speed1: %d\n"
"speed2: %d\n"
"tempoAccum: %d\n"
"totalProcessed: %d\n"
"bufferPos: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
(int)extValue,(int)speed1,(int)speed2,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
(int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
);
}
@ -2093,7 +2125,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
lastLoopPos=-1;
}
endOfSong=false;
speedAB=false;
// whaaaaa?
curSpeed=0;
playing=true;
skipping=true;
memset(walked,0,8192);
@ -2441,15 +2474,14 @@ void DivEngine::reset() {
}
extValue=0;
extValuePresent=0;
speed1=curSubSong->speed1;
speed2=curSubSong->speed2;
speeds=curSubSong->speeds;
firstTick=false;
shallStop=false;
shallStopSched=false;
pendingMetroTick=0;
elapsedBars=0;
elapsedBeats=0;
nextSpeed=speed1;
nextSpeed=speeds.val[0];
divider=60;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
@ -2519,6 +2551,8 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return (48828*MIN(128,(rate*128/48828)))/128;
case DIV_SYSTEM_X1_010:
return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case
case DIV_SYSTEM_ES5506:
return (31250*MIN(131071,(rate*2048/31250)))/2048; // TODO: support variable clock, channel limit case
default:
break;
}
@ -2649,12 +2683,8 @@ size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex;
}
unsigned char DivEngine::getSpeed1() {
return speed1;
}
unsigned char DivEngine::getSpeed2() {
return speed2;
const DivGroovePattern& DivEngine::getSpeeds() {
return speeds;
}
float DivEngine::getHz() {
@ -3552,14 +3582,14 @@ void DivEngine::delSample(int index) {
BUSY_END;
}
void DivEngine::addOrder(bool duplicate, bool where) {
void DivEngine::addOrder(int pos, bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return;
memset(order,0,DIV_MAX_CHANS);
BUSY_BEGIN_SOFT;
if (duplicate) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
order[i]=curOrders->ord[i][curOrder];
order[i]=curOrders->ord[i][pos];
}
} else {
bool used[DIV_MAX_PATTERNS];
@ -3587,14 +3617,14 @@ void DivEngine::addOrder(bool duplicate, bool where) {
} else { // after current order
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
for (int j=curSubSong->ordersLen; j>pos; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
curOrders->ord[i][curOrder+1]=order[i];
curOrders->ord[i][pos+1]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (pos<=curOrder) curOrder++;
if (playing && !freelance) {
playSub(false);
}
@ -3602,7 +3632,7 @@ void DivEngine::addOrder(bool duplicate, bool where) {
BUSY_END;
}
void DivEngine::deepCloneOrder(bool where) {
void DivEngine::deepCloneOrder(int pos, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return;
warnings="";
@ -3610,7 +3640,7 @@ void DivEngine::deepCloneOrder(bool where) {
for (int i=0; i<chans; i++) {
bool didNotFind=true;
logD("channel %d",i);
order[i]=curOrders->ord[i][curOrder];
order[i]=curOrders->ord[i][pos];
// find free slot
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
logD("finding free slot in %d...",j);
@ -3639,14 +3669,14 @@ void DivEngine::deepCloneOrder(bool where) {
} else { // after current order
saveLock.lock();
for (int i=0; i<chans; i++) {
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
for (int j=curSubSong->ordersLen; j>pos; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
curOrders->ord[i][curOrder+1]=order[i];
curOrders->ord[i][pos+1]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (pos<=curOrder) curOrder++;
if (playing && !freelance) {
playSub(false);
}
@ -3654,17 +3684,18 @@ void DivEngine::deepCloneOrder(bool where) {
BUSY_END;
}
void DivEngine::deleteOrder() {
void DivEngine::deleteOrder(int pos) {
if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT;
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curOrder; j<curSubSong->ordersLen; j++) {
for (int j=pos; j<curSubSong->ordersLen; j++) {
curOrders->ord[i][j]=curOrders->ord[i][j+1];
}
}
curSubSong->ordersLen--;
saveLock.unlock();
if (curOrder>pos) curOrder--;
if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
if (playing && !freelance) {
playSub(false);
@ -3672,40 +3703,46 @@ void DivEngine::deleteOrder() {
BUSY_END;
}
void DivEngine::moveOrderUp() {
void DivEngine::moveOrderUp(int& pos) {
BUSY_BEGIN_SOFT;
if (curOrder<1) {
if (pos<1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][pos]^=curOrders->ord[i][pos-1];
curOrders->ord[i][pos-1]^=curOrders->ord[i][pos];
curOrders->ord[i][pos]^=curOrders->ord[i][pos-1];
}
saveLock.unlock();
curOrder--;
if (curOrder==pos) {
curOrder--;
}
pos--;
if (playing && !freelance) {
playSub(false);
}
BUSY_END;
}
void DivEngine::moveOrderDown() {
void DivEngine::moveOrderDown(int& pos) {
BUSY_BEGIN_SOFT;
if (curOrder>=curSubSong->ordersLen-1) {
if (pos>=curSubSong->ordersLen-1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][pos]^=curOrders->ord[i][pos+1];
curOrders->ord[i][pos+1]^=curOrders->ord[i][pos];
curOrders->ord[i][pos]^=curOrders->ord[i][pos+1];
}
saveLock.unlock();
curOrder++;
if (curOrder==pos) {
curOrder++;
}
pos++;
if (playing && !freelance) {
playSub(false);
}
@ -4236,7 +4273,7 @@ void DivEngine::quitDispatch() {
clockDrift=0;
chans=0;
playing=false;
speedAB=false;
curSpeed=0;
endOfSong=false;
ticks=0;
tempoAccum=0;
@ -4415,6 +4452,8 @@ void DivEngine::preInit() {
String logPath=configPath+DIR_SEPARATOR_STR+"furnace.log";
startLogFile(logPath.c_str());
logI("Furnace version " DIV_VERSION ".");
loadConf();
}
@ -4472,6 +4511,9 @@ bool DivEngine::init() {
for (int i=0; i<64; i++) {
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
}
for (int i=0; i<128; i++) {
tremTable[i]=255*0.5*(1.0-cos(((double)i/128.0)*(2*M_PI)));
}
for (int i=0; i<4096; i++) {
reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0)));
pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0)));

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev136"
#define DIV_ENGINE_VERSION 136
#define DIV_VERSION "dev142"
#define DIV_ENGINE_VERSION 142
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -100,6 +100,7 @@ struct DivChannelState {
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
bool wentThroughNote, goneThroughNote;
int midiNote, curMidiNote, midiPitch;
size_t midiAge;
@ -152,6 +153,8 @@ struct DivChannelState {
wasShorthandPorta(false),
noteOnInhibit(false),
resetArp(false),
wentThroughNote(false),
goneThroughNote(false),
midiNote(-1),
curMidiNote(-1),
midiPitch(-1),
@ -337,7 +340,6 @@ class DivEngine {
bool playing;
bool freelance;
bool shallStop, shallStopSched;
bool speedAB;
bool endOfSong;
bool consoleMode;
bool extValuePresent;
@ -359,7 +361,7 @@ class DivEngine {
bool midiOutClock;
int midiOutMode;
int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
size_t curSubSongIndex;
size_t bufferPos;
double divider;
@ -368,7 +370,7 @@ class DivEngine {
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
unsigned char extValue, pendingMetroTick;
unsigned char speed1, speed2;
DivGroovePattern speeds;
short tempoAccum;
DivStatusView view;
DivHaltPositions haltOn;
@ -391,9 +393,9 @@ class DivEngine {
std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream;
std::vector<DivInstrumentType> possibleInsTypes;
static DivSysDef* sysDefs[256];
static DivSystem sysFileMapFur[256];
static DivSystem sysFileMapDMF[256];
static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
struct SamplePreview {
double rate;
@ -413,6 +415,7 @@ class DivEngine {
} sPreview;
short vibTable[64];
short tremTable[128];
int reversePitchTable[4096];
int pitchTable[4096];
char c163NameCS[1024];
@ -513,6 +516,7 @@ class DivEngine {
String decodeSysDesc(String desc);
// start fresh
void createNew(const char* description, String sysName, bool inBase64=true);
void createNewFromDefaults();
// load a file.
bool load(unsigned char* f, size_t length);
// save as .dmf.
@ -524,7 +528,12 @@ class DivEngine {
// specify system to build ROM for.
SafeWriter* buildROM(int sys);
// dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false);
// set trailingTicks to:
// - 0 to add one tick of trailing
// - x to add x+1 ticks of trailing
// - -1 to auto-determine trailing
// - -2 to add a whole loop of trailing
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1);
// dump to ZSM.
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
// dump command stream.
@ -581,6 +590,9 @@ class DivEngine {
void setConf(String key, const char* value);
void setConf(String key, String value);
// get whether config value exists
bool hasConf(String key);
// calculate base frequency/period
double calcBaseFreq(double clock, double divider, int note, bool period);
@ -729,11 +741,8 @@ class DivEngine {
// get current subsong
size_t getCurrentSubSong();
// get speed 1
unsigned char getSpeed1();
// get speed 2
unsigned char getSpeed2();
// get speeds
const DivGroovePattern& getSpeeds();
// get Hz
float getHz();
@ -813,19 +822,19 @@ class DivEngine {
void delSample(int index);
// add order
void addOrder(bool duplicate, bool where);
void addOrder(int pos, bool duplicate, bool where);
// deep clone orders
void deepCloneOrder(bool where);
void deepCloneOrder(int pos, bool where);
// delete order
void deleteOrder();
void deleteOrder(int pos);
// move order up
void moveOrderUp();
void moveOrderUp(int& pos);
// move order down
void moveOrderDown();
void moveOrderDown(int& pos);
// move thing up
bool moveInsUp(int which);
@ -1064,7 +1073,6 @@ class DivEngine {
freelance(false),
shallStop(false),
shallStopSched(false),
speedAB(false),
endOfSong(false),
consoleMode(false),
extValuePresent(false),
@ -1098,6 +1106,7 @@ class DivEngine {
nextSpeed(3),
elapsedBars(0),
elapsedBeats(0),
curSpeed(0),
curSubSongIndex(0),
bufferPos(0),
divider(60),
@ -1115,8 +1124,6 @@ class DivEngine {
globalPitch(0),
extValue(0),
pendingMetroTick(0),
speed1(3),
speed2(3),
tempoAccum(0),
view(DIV_STATUS_NOTHING),
haltOn(DIV_HALT_NONE),
@ -1158,13 +1165,14 @@ class DivEngine {
memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int));
memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int));
memset(vibTable,0,64*sizeof(short));
memset(tremTable,0,128*sizeof(short));
memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int));
memset(sysDefs,0,256*sizeof(void*));
memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
memset(walked,0,8192);
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));
for (int i=0; i<256; i++) {
for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL;
sysFileMapDMF[i]=DIV_SYSTEM_NULL;
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -83,7 +83,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
ds.version=(unsigned char)reader.readC();
logI("module version %d (0x%.2x)",ds.version,ds.version);
if (ds.version>0x1a) {
if (ds.version>0x1b) {
logE("this version is not supported by Furnace yet!");
lastError="this version is not supported by Furnace yet";
delete[] file;
@ -219,14 +219,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
ds.subsong[0]->timeBase=reader.readC();
ds.subsong[0]->speed1=reader.readC();
ds.subsong[0]->speeds.len=2;
ds.subsong[0]->speeds.val[0]=reader.readC();
if (ds.version>0x07) {
ds.subsong[0]->speed2=reader.readC();
ds.subsong[0]->speeds.val[1]=reader.readC();
ds.subsong[0]->pal=reader.readC();
ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
ds.subsong[0]->customTempo=reader.readC();
} else {
ds.subsong[0]->speed2=ds.subsong[0]->speed1;
ds.subsong[0]->speeds.len=1;
}
if (ds.version>0x0a) {
String hz=reader.readString(3);
@ -827,6 +828,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
for (int i=0; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample;
int length=reader.readI();
int cutStart=0;
int cutEnd=length;
int pitch=5;
int vol=50;
short* data;
@ -866,6 +869,29 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM;
}
}
if (ds.version>=0x1b) {
// what the hell man...
cutStart=reader.readI();
cutEnd=reader.readI();
if (cutStart<0 || cutStart>length) {
logE("cutStart is out of range! (%d)",cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<0 || cutEnd>length) {
logE("cutEnd is out of range! (%d)",cutEnd);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
}
if (length>0) {
if (ds.version>0x08) {
if (ds.version<0x0b) {
@ -876,6 +902,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
data=new short[length];
reader.read(data,length*2);
}
if (ds.version>0x1b) {
if (cutStart!=0 || cutEnd!=length) {
// cut data
short* newData=new short[cutEnd-cutStart];
memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short));
delete[] data;
data=newData;
length=cutEnd-cutStart;
cutStart=0;
cutEnd=length;
}
}
#ifdef TA_BIG_ENDIAN
// convert to big-endian
@ -1716,6 +1755,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<130) {
ds.oldArpStrategy=true;
}
if (ds.version<138) {
ds.brokenPortaLegato=true;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1739,8 +1781,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.readI();
subSong->timeBase=reader.readC();
subSong->speed1=reader.readC();
subSong->speed2=reader.readC();
subSong->speeds.len=2;
subSong->speeds.val[0]=reader.readC();
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
@ -2221,6 +2264,32 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=136) song.patchbayAuto=reader.readC();
if (ds.version>=138) {
ds.brokenPortaLegato=reader.readC();
for (int i=0; i<7; i++) {
reader.readC();
}
}
if (ds.version>=139) {
subSong->speeds.len=reader.readC();
for (int i=0; i<16; i++) {
subSong->speeds.val[i]=reader.readC();
}
// grooves
unsigned char grooveCount=reader.readC();
for (int i=0; i<grooveCount; i++) {
DivGroovePattern gp;
gp.len=reader.readC();
for (int j=0; j<16; j++) {
gp.val[j]=reader.readC();
}
ds.grooves.push_back(gp);
}
}
// read system flags
if (ds.version>=119) {
logD("reading chip flags...");
@ -2279,8 +2348,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
subSong=ds.subsong[i+1];
subSong->timeBase=reader.readC();
subSong->speed1=reader.readC();
subSong->speed2=reader.readC();
subSong->speeds.len=2;
subSong->speeds.val[0]=reader.readC();
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
@ -2328,6 +2398,13 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
for (int i=0; i<tchans; i++) {
subSong->chanShortName[i]=reader.readString();
}
if (ds.version>=139) {
subSong->speeds.len=reader.readC();
for (int i=0; i<16; i++) {
subSong->speeds.val[i]=reader.readC();
}
}
}
}
@ -2574,6 +2651,32 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// new YM2612/SN/X1-010 volumes
if (ds.version<137) {
for (int i=0; i<ds.systemLen; i++) {
switch (ds.system[i]) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
case DIV_SYSTEM_YM2612_CSM:
ds.systemVol[i]/=2.0;
break;
case DIV_SYSTEM_SMS:
case DIV_SYSTEM_T6W28:
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
ds.systemVol[i]/=1.5;
break;
case DIV_SYSTEM_X1_010:
ds.systemVol[i]/=4.0;
break;
default:
break;
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
@ -2920,7 +3023,6 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
if (fxVal>0x20 && ds.name!="klisje paa klisje") {
writeFxCol(0xf0,fxVal);
} else {
writeFxCol(0x09,fxVal);
writeFxCol(0x0f,fxVal);
}
break;
@ -3399,8 +3501,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->pal=true;
ds.subsong[0]->customTempo=true;
ds.subsong[0]->pat[3].effectCols=3;
ds.subsong[0]->speed1=3;
ds.subsong[0]->speed2=3;
ds.subsong[0]->speeds.val[0]=3;
ds.subsong[0]->speeds.len=1;
int lastIns[4];
int lastNote[4];
@ -3417,10 +3519,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->orders.ord[j][i]=i;
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
if (j==3 && seq[i].speed) {
p->data[0][6]=0x09;
p->data[0][6]=0x0f;
p->data[0][7]=seq[i].speed;
p->data[0][8]=0x0f;
p->data[0][9]=seq[i].speed;
}
bool ignoreNext=false;
@ -4307,8 +4407,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(0);
w->writeC(subSong->timeBase);
w->writeC(subSong->speed1);
w->writeC(subSong->speed2);
// these are for compatibility
w->writeC(subSong->speeds.val[0]);
w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]);
w->writeC(subSong->arpLen);
w->writeF(subSong->hz);
w->writeS(subSong->patLen);
@ -4489,6 +4590,27 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
}
w->writeC(song.patchbayAuto);
// even more compat flags
w->writeC(song.brokenPortaLegato);
for (int i=0; i<7; i++) {
w->writeC(0);
}
// speeds of first song
w->writeC(subSong->speeds.len);
for (int i=0; i<16; i++) {
w->writeC(subSong->speeds.val[i]);
}
// groove list
w->writeC((unsigned char)song.grooves.size());
for (const DivGroovePattern& i: song.grooves) {
w->writeC(i.len);
for (int j=0; j<16; j++) {
w->writeC(i.val[j]);
}
}
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
@ -4503,8 +4625,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(0);
w->writeC(subSong->timeBase);
w->writeC(subSong->speed1);
w->writeC(subSong->speed2);
w->writeC(subSong->speeds.val[0]);
w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]);
w->writeC(subSong->arpLen);
w->writeF(subSong->hz);
w->writeS(subSong->patLen);
@ -4543,6 +4665,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeString(subSong->chanShortName[i],false);
}
// speeds
w->writeC(subSong->speeds.len);
for (int i=0; i<16; i++) {
w->writeC(subSong->speeds.val[i]);
}
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
@ -4798,8 +4926,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(curSubSong->hilightB);
w->writeC(curSubSong->timeBase);
w->writeC(curSubSong->speed1);
w->writeC(curSubSong->speed2);
w->writeC(curSubSong->speeds.val[0]);
w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]);
w->writeC(curSubSong->pal);
w->writeC(curSubSong->customTempo);
char customHz[4];
@ -4823,6 +4951,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
addWarning("only the currently selected subsong will be saved");
}
if (!song.grooves.empty()) {
addWarning("grooves will not be saved");
}
if (curSubSong->speeds.len>2) {
addWarning("only the first two speeds will be effective");
}
if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) {
addWarning(".dmf format does not support virtual tempo");
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -924,6 +924,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
break;
case DIV_INS_POKEMINI:
break;
case DIV_INS_SM8521:
checkForWL=true;
if (ws.enabled) featureWS=true;
break;
case DIV_INS_MAX:
break;

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -78,6 +78,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_K007232=45,
DIV_INS_GA20=46,
DIV_INS_POKEMINI=47,
DIV_INS_SM8521=48,
DIV_INS_MAX,
DIV_INS_NULL
};

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -56,7 +56,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
}
if (delay>0) {
delay--;
had=false;
if (!linger) had=false;
return;
}
if (began && source.delay>0) {
@ -523,4 +523,4 @@ DivMacroStruct* DivMacroInt::structByName(const String& name) {
return NULL;
}
#undef CONSIDER
#undef CONSIDER

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -18,6 +18,7 @@
*/
#include "../dispatch.h"
#include "../../ta-log.h"
void DivDispatch::acquire(short** buf, size_t len) {
}
@ -121,7 +122,8 @@ void DivDispatch::notifyWaveChange(int ins) {
}
void DivDispatch::notifyInsDeletion(void* ins) {
logE("notifyInsDeletion NOT implemented!");
abort();
}
void DivDispatch::notifyPlaybackStop() {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -89,7 +89,7 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
chan[i].audSub-=AMIGA_DIVIDER;
if (chan[i].audSub<0) {
if (chan[i].useWave) {
writeAudDat(chan[i].ws.output[chan[i].audPos++]^0x80);
writeAudDat(chan[i].ws.output[(chan[i].audPos++)&255]^0x80);
if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) {
chan[i].audPos=0;
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -152,7 +152,9 @@ void DivPlatformArcade::tick(bool sysTick) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (KVS(i,j)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -235,7 +237,7 @@ void DivPlatformArcade::tick(bool sysTick) {
if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
if (isMuted[i] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
@ -296,7 +298,9 @@ void DivPlatformArcade::tick(bool sysTick) {
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (KVS(i,j)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -319,27 +323,6 @@ void DivPlatformArcade::tick(bool sysTick) {
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
}
}
if (chan[i].keyOn || chan[i].keyOff) {
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
immWrite(baseAddr+ADDR_SL_RR,0x0f);
immWrite(baseAddr+ADDR_TL,0x7f);
oldWrites[baseAddr+ADDR_SL_RR]=-1;
oldWrites[baseAddr+ADDR_TL]=-1;
}
}
immWrite(0x08,i);
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
for (int k=0; k<9; k++) {
immWrite(baseAddr+ADDR_SL_RR,0x0f);
}
}
}
chan[i].keyOff=false;
}
}
for (int i=0; i<256; i++) {
@ -349,6 +332,24 @@ void DivPlatformArcade::tick(bool sysTick) {
}
}
int hardResetElapsed=0;
bool mustHardReset=false;
for (int i=0; i<8; i++) {
if (chan[i].keyOn || chan[i].keyOff) {
immWrite(0x08,i);
if (chan[i].hardReset && chan[i].keyOn) {
mustHardReset=true;
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
immWrite(baseAddr+ADDR_SL_RR,0x0f);
hardResetElapsed++;
}
}
chan[i].keyOff=false;
}
}
for (int i=0; i<8; i++) {
if (chan[i].freqChanged) {
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2;
@ -363,14 +364,37 @@ void DivPlatformArcade::tick(bool sysTick) {
if (chan[i].freq>=(95<<6)) chan[i].freq=(95<<6)-1;
immWrite(i+0x28,hScale(chan[i].freq>>6));
immWrite(i+0x30,chan[i].freq<<2);
hardResetElapsed+=2;
chan[i].freqChanged=false;
}
if (chan[i].keyOn || chan[i].opMaskChanged) {
if ((chan[i].keyOn || chan[i].opMaskChanged) && !chan[i].hardReset) {
immWrite(0x08,(chan[i].opMask<<3)|i);
hardResetElapsed++;
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
// hard reset handling
if (mustHardReset) {
for (unsigned int i=hardResetElapsed; i<hardResetCycles; i++) {
immWrite(0x1f,i&0xff);
}
for (int i=0; i<8; i++) {
if ((chan[i].keyOn || chan[i].opMaskChanged) && chan[i].hardReset) {
// restore SL/RR
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
immWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
immWrite(0x08,(chan[i].opMask<<3)|i);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
}
}
void DivPlatformArcade::muteChannel(int ch, bool mute) {
@ -382,53 +406,59 @@ void DivPlatformArcade::muteChannel(int ch, bool mute) {
}
}
void DivPlatformArcade::commitState(int ch, DivInstrument* ins) {
if (chan[ch].insChanged) {
chan[ch].state=ins->fm;
chan[ch].opMask=
(chan[ch].state.op[0].enable?1:0)|
(chan[ch].state.op[2].enable?2:0)|
(chan[ch].state.op[1].enable?4:0)|
(chan[ch].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[i];
DivInstrumentFM::Operator op=chan[ch].state.op[i];
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(ch,i)) {
if (!chan[ch].active || chan[ch].insChanged) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[ch].outVol&0x7f,127));
}
} else {
if (chan[ch].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
if (chan[ch].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
}
if (chan[ch].insChanged) {
if (isMuted[ch]) {
rWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3));
} else {
rWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7));
}
rWrite(chanOffs[ch]+ADDR_FMS_AMS,((chan[ch].state.fms&7)<<4)|(chan[ch].state.ams&3));
}
}
int DivPlatformArcade::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator op=chan[c.chan].state.op[i];
if (KVS(c.chan,i)) {
if (!chan[c.chan].active || chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
}
} else {
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
}
if (chan[c.chan].insChanged) {
if (isMuted[c.chan]) {
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
} else {
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7));
}
rWrite(chanOffs[c.chan]+ADDR_FMS_AMS,((chan[c.chan].state.fms&7)<<4)|(chan[c.chan].state.ams&3));
}
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
@ -462,7 +492,9 @@ int DivPlatformArcade::dispatch(DivCommand c) {
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
if (KVS(c.chan,i)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(c.chan,i)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -521,6 +553,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPM);
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
}
chan[c.chan].baseFreq=NOTE_LINEAR(c.value);
chan[c.chan].freqChanged=true;
break;
@ -559,7 +596,9 @@ int DivPlatformArcade::dispatch(DivCommand c) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (KVS(c.chan,c.value)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(c.chan,c.value)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -766,7 +805,9 @@ void DivPlatformArcade::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator op=chan[i].state.op[j];
if (KVS(i,j)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -800,6 +841,12 @@ void DivPlatformArcade::notifyInsChange(int ins) {
}
}
void DivPlatformArcade::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void* DivPlatformArcade::getChanState(int ch) {
return &chan[ch];
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -57,6 +57,7 @@ class DivPlatformArcade: public DivPlatformOPM {
int octave(int freq);
int toFreq(int freq);
void commitState(int ch, DivInstrument* ins);
void acquire_nuked(short** buf, size_t len);
void acquire_ymfm(short** buf, size_t len);
@ -76,6 +77,7 @@ class DivPlatformArcade: public DivPlatformOPM {
void muteChannel(int ch, bool mute);
DivMacroInt* getChanMacroInt(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void setFlags(const DivConfig& flags);
int getOutputCount();
void setYMFM(bool use);

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -44,7 +44,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
signed int out=0;
// K005289 part
k005289.tick();
k005289.tick(8);
// Wavetable part
for (int i=0; i<2; i++) {
@ -60,7 +60,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) {
}
}
if (++writeOscBuf>=64) writeOscBuf=0;
if (++writeOscBuf>=8) writeOscBuf=0;
out<<=6; // scale output to 16 bit
@ -332,9 +332,9 @@ void DivPlatformBubSysWSG::notifyInsDeletion(void* ins) {
void DivPlatformBubSysWSG::setFlags(const DivConfig& flags) {
chipClock=COLOR_NTSC;
CHECK_CUSTOM_CLOCK;
rate=chipClock;
rate=chipClock/8;
for (int i=0; i<2; i++) {
oscBuf[i]->rate=rate/64;
oscBuf[i]->rate=rate/8;
}
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -131,6 +131,10 @@ int DivPlatformDummy::dispatch(DivCommand c) {
return 1;
}
void DivPlatformDummy::notifyInsDeletion(void* ins) {
// nothing
}
void DivPlatformDummy::reset() {
for (int i=0; i<chans; i++) {
chan[i]=DivPlatformDummy::Channel();

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -40,6 +40,7 @@ class DivPlatformDummy: public DivDispatch {
void acquire(short** buf, size_t len);
void muteChannel(int ch, bool mute);
int dispatch(DivCommand c);
void notifyInsDeletion(void* ins);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
void reset();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,327 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 _ES5506_H
#define _ES5506_H
#include "../dispatch.h"
#include "../engine.h"
#include <queue>
#include "../macroInt.h"
#include "../sample.h"
#include "vgsound_emu/src/es550x/es5506.hpp"
class DivPlatformES5506: public DivDispatch, public es550x_intf {
struct Channel : public SharedChannel<int> {
struct PCM {
bool isNoteMap;
int index, next;
int note;
double freqOffs;
double nextFreqOffs;
bool pause, direction;
unsigned int bank;
unsigned int start;
unsigned int end;
unsigned int length;
unsigned int loopStart;
unsigned int loopEnd;
unsigned int nextPos;
bool setPos;
DivSampleLoopMode loopMode;
PCM():
isNoteMap(false),
index(-1),
next(-1),
note(0),
freqOffs(1.0),
nextFreqOffs(1.0),
pause(false),
direction(false),
bank(0),
start(0),
end(0),
length(0),
loopStart(0),
loopEnd(0),
nextPos(0),
setPos(false),
loopMode(DIV_SAMPLE_LOOP_MAX) {}
} pcm;
int nextFreq, nextNote, currNote, wave;
int volMacroMax, panMacroMax;
bool useWave, isReverseLoop;
unsigned int cr;
struct NoteChanged { // Note changed flags
union { // pack flag bits in single byte
struct { // flag bits
unsigned char offs: 1; // frequency offset
unsigned char note: 1; // note
unsigned char freq: 1; // base frequency
unsigned char dummy: 5; // dummy for bit padding
};
unsigned char changed; // Packed flags are stored here
};
NoteChanged() :
changed(0) {}
} noteChanged;
struct VolChanged { // Volume changed flags
union { // pack flag bits in single byte
struct { // flag bits
unsigned char lVol: 1; // left volume
unsigned char rVol: 1; // right volume
unsigned char ca: 1; // Channel assignment
unsigned char dummy: 5; // dummy for bit padding
};
unsigned char changed; // Packed flags are stored here
};
VolChanged() :
changed(0) {}
} volChanged;
struct FilterChanged { // Filter changed flags
union { // pack flag bits in single byte
struct { // flag bits
unsigned char mode: 1; // Filter mode
unsigned char k1: 1; // K1
unsigned char k2: 1; // K2
unsigned char dummy: 5; // dummy for bit padding
};
unsigned char changed; // Packed flags are stored here
};
FilterChanged():
changed(0) {}
} filterChanged;
struct EnvChanged { // Envelope changed flags
union { // pack flag bits in single byte
struct { // flag bits
unsigned char ecount: 1; // Envelope count
unsigned char lVRamp: 1; // Left volume Ramp
unsigned char rVRamp: 1; // Right volume Ramp
unsigned char k1Ramp: 1; // K1 Ramp w/Slow flag
unsigned char k2Ramp: 1; // K2 Ramp w/Slow flag
unsigned char dummy: 3; // dummy for bit padding
};
unsigned char changed; // Packed flags are stored here
};
EnvChanged():
changed(0) {}
} envChanged;
struct PCMChanged {
union {
struct {
unsigned char index: 1; // sample index
unsigned char slice: 1; // transwave slice
unsigned char position: 1; // sample position in memory
unsigned char loopBank: 1; // Loop mode and Bank
unsigned char dummy: 4; // dummy for bit padding
};
unsigned char changed;
};
PCMChanged():
changed(0) {}
} pcmChanged;
struct Overwrite {
DivInstrumentES5506::Filter filter;
DivInstrumentES5506::Envelope envelope;
struct State {
// overwrited flag
union {
struct {
unsigned char mode: 1; // filter mode
unsigned char k1: 1; // k1
unsigned char k2: 1; // k2
unsigned char ecount: 1; // envelope count
unsigned char lVRamp: 1; // left volume ramp
unsigned char rVRamp: 1; // right volume ramp
unsigned char k1Ramp: 1; // k1 ramp
unsigned char k2Ramp: 1; // k2 ramp
};
unsigned char overwrited;
};
State():
overwrited(0) {}
} state;
Overwrite():
filter(DivInstrumentES5506::Filter()),
envelope(DivInstrumentES5506::Envelope()),
state(State()) {}
} overwrite;
unsigned char ca;
signed int k1Offs, k2Offs;
signed int k1Slide, k2Slide;
signed int k1Prev, k2Prev;
int lVol, rVol;
int outLVol, outRVol;
int resLVol, resRVol;
signed int oscOut;
DivInstrumentES5506::Filter filter;
DivInstrumentES5506::Envelope envelope;
Channel():
SharedChannel<int>(0xff),
pcm(PCM()),
nextFreq(0),
nextNote(0),
currNote(0),
wave(-1),
volMacroMax(0xfff),
panMacroMax(0xfff),
useWave(false),
isReverseLoop(false),
cr(0),
noteChanged(NoteChanged()),
volChanged(VolChanged()),
filterChanged(FilterChanged()),
envChanged(EnvChanged()),
pcmChanged(PCMChanged()),
overwrite(Overwrite()),
ca(0),
k1Offs(0),
k2Offs(0),
k1Slide(0),
k2Slide(0),
k1Prev(0xffff),
k2Prev(0xffff),
lVol(0xff),
rVol(0xff),
outLVol(0xfff),
outRVol(0xfff),
resLVol(0xfff),
resRVol(0xfff),
oscOut(0),
filter(DivInstrumentES5506::Filter()),
envelope(DivInstrumentES5506::Envelope()) {
outVol=0xfff;
}
};
Channel chan[32];
DivDispatchOscBuffer* oscBuf[32];
bool isMuted[32];
signed short* sampleMem; // ES5506 uses 16 bit data bus for samples
size_t sampleMemLen;
unsigned int sampleOffES5506[256];
bool sampleLoaded[256];
struct QueuedHostIntf {
unsigned char state;
unsigned char step;
unsigned char addr;
unsigned int val;
unsigned int mask;
unsigned int* read;
unsigned short delay;
bool isRead;
QueuedHostIntf(unsigned char s, unsigned char a, unsigned int v, unsigned int m=(unsigned int)(~0), unsigned short d=0):
state(0),
step(s),
addr(a),
val(v),
mask(m),
read(NULL),
delay(0),
isRead(false) {}
QueuedHostIntf(unsigned char st, unsigned char s, unsigned char a, unsigned int* r, unsigned int m=(unsigned int)(~0), unsigned short d=0):
state(st),
step(s),
addr(a),
val(0),
mask(m),
read(r),
delay(d),
isRead(true) {}
};
struct QueuedReadState {
unsigned int* read;
unsigned char state;
QueuedReadState(unsigned int* r, unsigned char s):
read(r),
state(s) {}
};
std::queue<QueuedHostIntf> hostIntf32;
std::queue<QueuedHostIntf> hostIntf8;
std::queue<unsigned char> queuedRead;
std::queue<QueuedReadState> queuedReadState;
int cycle, curPage, volScale;
unsigned char maskedVal;
unsigned int irqv;
bool isMasked, isReaded;
bool irqTrigger;
unsigned int curCR;
unsigned char initChanMax, chanMax;
es5506_core es5506;
unsigned char regPool[4*16*128]; // 7 bit page x 16 registers per page x 32 bit per registers
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
virtual void e_pin(bool state) override; // E output
virtual void irqb(bool state) override; // IRQB output
virtual s16 read_sample(u8 bank, u32 address) override {
if (sampleMem==NULL) return 0;
return sampleMem[((bank&3)<<21)|(address&0x1fffff)];
}
virtual void acquire(short** buf, size_t len) override;
virtual int dispatch(DivCommand c) override;
virtual void* getChanState(int chan) override;
virtual DivMacroInt* getChanMacroInt(int ch) override;
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
virtual unsigned char* getRegisterPool() override;
virtual int getRegisterPoolSize() override;
virtual void reset() override;
virtual void forceIns() override;
virtual void tick(bool sysTick=true) override;
virtual void muteChannel(int ch, bool mute) override;
virtual int getOutputCount() override;
virtual bool keyOffAffectsArp(int ch) override;
virtual void setFlags(const DivConfig& flags) override;
virtual void notifyInsChange(int ins) override;
virtual void notifyWaveChange(int wave) override;
virtual void notifyInsDeletion(void* ins) override;
virtual void poke(unsigned int addr, unsigned short val) override;
virtual void poke(std::vector<DivRegWrite>& wlist) override;
virtual const void* getSampleMem(int index = 0) override;
virtual size_t getSampleMemCapacity(int index = 0) override;
virtual size_t getSampleMemUsage(int index = 0) override;
virtual bool isSampleLoaded(int index, int sample) override;
virtual void renderSamples(int sysID) override;
virtual const char** getRegisterSheet() override;
virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override;
virtual void quit() override;
DivPlatformES5506():
DivDispatch(),
es550x_intf(),
es5506(*this) {}
};
#endif

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -41,8 +41,14 @@ class DivPlatformOPM: public DivPlatformFMBase {
0x00, 0x08, 0x10, 0x18
};
unsigned char lfoValue, lfoValue2, lfoShape, lfoShape2;
DivPlatformOPM():
DivPlatformFMBase() {}
DivPlatformFMBase(),
lfoValue(0),
lfoValue2(0),
lfoShape(0),
lfoShape2(0) {}
};
#endif

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -154,6 +154,8 @@ class DivPlatformOPN: public DivPlatformFMBase {
unsigned int ayDiv;
unsigned char csmChan;
unsigned char lfoValue;
unsigned short ssgVol;
unsigned short fmVol;
bool extSys, useCombo, fbAllOps;
DivConfig ayFlags;
@ -172,6 +174,8 @@ class DivPlatformOPN: public DivPlatformFMBase {
ayDiv(a),
csmChan(cc),
lfoValue(0),
ssgVol(128),
fmVol(256),
extSys(isExtSys),
useCombo(false),
fbAllOps(false) {}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -47,6 +47,8 @@ class DivPlatformFMBase: public DivDispatch {
0,2,1,3
};
const unsigned int hardResetCycles=127;
struct FMChannel: public SharedChannel<int> {
DivInstrumentFM state;
unsigned char freqH, freqL;
@ -83,6 +85,7 @@ class DivPlatformFMBase: public DivDispatch {
unsigned char lastBusy;
int delay;
bool flushFirst;
unsigned char regPool[512];
short oldWrites[512];
@ -102,7 +105,7 @@ class DivPlatformFMBase: public DivDispatch {
}
}
inline void urgentWrite(unsigned short a, unsigned char v) {
if (!skipRegisterWrites) {
if (!skipRegisterWrites && !flushFirst) {
if (writes.empty()) {
writes.push_back(QueuedWrite(a,v));
} else if (writes.size()>16 || writes.front().addrOrVal) {
@ -118,9 +121,11 @@ class DivPlatformFMBase: public DivDispatch {
friend void putDispatchChan(void*,int,int);
DivPlatformFMBase():DivDispatch(),
lastBusy(0),
delay(0) {}
DivPlatformFMBase():
DivDispatch(),
lastBusy(0),
delay(0),
flushFirst(false) {}
};
#endif

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -92,7 +92,7 @@ void DivPlatformGA20::tick(bool sysTick) {
chan[i].std.next();
if (chan[i].std.vol.had) {
const signed char macroVol=VOL_SCALE_LOG((chan[i].vol&0xff),(0xff*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul,0xff);
if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) {
if (macroVol!=chan[i].outVol) {
chan[i].outVol=macroVol;
chan[i].volumeChanged=true;
}
@ -121,7 +121,7 @@ void DivPlatformGA20::tick(bool sysTick) {
}
}
if (chan[i].volumeChanged) {
chan[i].resVol=(chan[i].active && isMuted[i])?0:chan[i].outVol&0xff;
chan[i].resVol=chan[i].outVol&0xff;
chWrite(i,0x5,chan[i].resVol);
chan[i].volumeChanged=false;
}
@ -175,9 +175,7 @@ void DivPlatformGA20::tick(bool sysTick) {
chWrite(i,6,2);
if (!chan[i].std.vol.had) {
chan[i].outVol=chan[i].vol;
if (!isMuted[i]) {
chan[i].volumeChanged=true;
}
chan[i].volumeChanged=true;
}
chan[i].keyOn=false;
}
@ -218,9 +216,7 @@ int DivPlatformGA20::dispatch(DivCommand c) {
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
chan[c.chan].volumeChanged=true;
}
break;
}
@ -244,9 +240,7 @@ int DivPlatformGA20::dispatch(DivCommand c) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
chan[c.chan].volumeChanged=true;
}
}
break;
@ -320,6 +314,7 @@ int DivPlatformGA20::dispatch(DivCommand c) {
void DivPlatformGA20::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
ga20.set_mute(ch,mute);
chan[ch].volumeChanged=true;
}
@ -358,6 +353,7 @@ void DivPlatformGA20::reset() {
// keyoff all channels
chWrite(i,5,0);
chWrite(i,6,0);
if (isMuted[i]) ga20.set_mute(i,true);
}
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -30,17 +30,26 @@
void DivYM2612Interface::ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) {
if (tnum==1) {
countB=duration_in_clocks;
setB=duration_in_clocks;
} else if (tnum==0) {
countA=duration_in_clocks;
setA=duration_in_clocks;
}
logV("ymfm_set_timer(%d,%d)",tnum,duration_in_clocks);
}
void DivYM2612Interface::clock() {
if (countA>=0) {
if (setA>=0) {
countA-=144;
if (countA<0) m_engine->engine_timer_expired(0);
if (countA<0) {
m_engine->engine_timer_expired(0);
countA+=setA;
}
}
if (setB>=0) {
countB-=144;
if (countB<0) {
m_engine->engine_timer_expired(1);
countB+=setB;
}
}
}
@ -141,23 +150,26 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
os[0]=0; os[1]=0;
for (int i=0; i<6; i++) {
if (!writes.empty() && --delay<0) {
delay=0;
QueuedWrite& w=writes.front();
if (w.addrOrVal) {
OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val);
//printf("write: %x = %.2x\n",w.addr,w.val);
lastBusy=0;
regPool[w.addr&0x1ff]=w.val;
writes.pop_front();
} else {
lastBusy++;
if (fm.write_busy==0) {
//printf("busycounter: %d\n",lastBusy);
OPN2_Write(&fm,0x0+((w.addr>>8)<<1),w.addr);
w.addrOrVal=true;
if (!writes.empty()) {
if (--delay<0) {
delay=0;
QueuedWrite& w=writes.front();
if (w.addrOrVal) {
//logV("%.3x = %.2x",w.addr,w.val);
OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val);
lastBusy=0;
regPool[w.addr&0x1ff]=w.val;
writes.pop_front();
} else {
lastBusy++;
if (fm.write_busy==0) {
OPN2_Write(&fm,0x0+((w.addr>>8)<<1),w.addr);
w.addrOrVal=true;
}
}
}
} else {
flushFirst=false;
}
OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1];
@ -171,18 +183,18 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<7;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<(chipType==2?0:7);
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<(chipType==2?0:7);
}
}
os[0]=(os[0]<<5);
if (chipType!=2) os[0]=(os[0]<<5);
if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767;
os[1]=(os[1]<<5);
if (chipType!=2) os[1]=(os[1]<<5);
if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767;
@ -207,9 +219,11 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
regPool[w.addr&0x1ff]=w.val;
writes.pop_front();
lastBusy=1;
} else {
flushFirst=false;
}
if (ladder) {
if (chipType==1) {
fm_ymfm->generate(&out_ymfm);
} else {
((ymfm::ym3438*)fm_ymfm)->generate(&out_ymfm);
@ -220,6 +234,9 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
//OPN2_Write(&fm,0,0);
for (int i=0; i<6; i++) {
int chOut=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6;
if (chOut<-32768) chOut=-32768;
if (chOut>32767) chOut=32767;
if (i==5) {
if (fm_ymfm->debug_dac_enable()) {
if (softPCM) {
@ -229,10 +246,10 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<7;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6;
oscBuf[i]->data[oscBuf[i]->needle++]=chOut;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6;
oscBuf[i]->data[oscBuf[i]->needle++]=chOut;
}
}
@ -283,7 +300,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (i<6) for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
if (isMuted[i] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
@ -366,7 +383,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
if (isMuted[i] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
@ -389,6 +406,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
chan[i].state.ams=chan[i].std.ams.val;
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].std.ex3.had) {
lfoValue=(chan[i].std.ex3.val>7)?0:(8|(chan[i].std.ex3.val&7));
rWrite(0x22,lfoValue);
}
if (chan[i].std.ex4.had && chan[i].active) {
chan[i].opMask=chan[i].std.ex4.val&15;
chan[i].opMaskChanged=true;
@ -423,7 +444,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isMuted[i]) {
if (isMuted[i] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
@ -454,31 +475,28 @@ void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<512; i++) {
if (pendingWrites[i]!=oldWrites[i]) {
if (i==0x2b && pendingWrites[i]!=0 && !parent->song.brokenDACMode) {
if (chan[5].keyOn) chan[5].keyOn=false;
chan[5].keyOff=true;
}
immWrite(i,pendingWrites[i]&0xff);
oldWrites[i]=pendingWrites[i];
}
}
int hardResetElapsed=0;
bool mustHardReset=false;
for (int i=0; i<6; i++) {
if (i==2 && extMode) continue;
if (chan[i].keyOn || chan[i].keyOff) {
immWrite(0x28,0x00|konOffs[i]);
if (chan[i].hardReset && chan[i].keyOn) {
mustHardReset=true;
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
immWrite(baseAddr+ADDR_SL_RR,0x0f);
immWrite(baseAddr+ADDR_TL,0x7f);
oldWrites[baseAddr+ADDR_SL_RR]=-1;
oldWrites[baseAddr+ADDR_TL]=-1;
//rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
}
immWrite(0x28,0x00|konOffs[i]);
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
for (int k=0; k<5; k++) {
immWrite(baseAddr+ADDR_SL_RR,0x0f);
}
hardResetElapsed++;
}
}
chan[i].keyOff=false;
@ -507,6 +525,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (i<6) {
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
hardResetElapsed+=2;
}
if (chan[i].furnaceDac && chan[i].dacMode) {
double off=1.0;
@ -525,12 +544,38 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
chan[i].freqChanged=false;
}
if (chan[i].keyOn || chan[i].opMaskChanged) {
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
if ((chan[i].keyOn || chan[i].opMaskChanged) && !chan[i].hardReset) {
if (i<6) {
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
hardResetElapsed++;
}
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
// hard reset handling
if (mustHardReset) {
for (unsigned int i=hardResetElapsed; i<hardResetCycles; i++) {
immWrite(0xf0,i&0xff);
}
for (int i=0; i<csmChan; i++) {
if (i==2 && extMode) continue;
if ((chan[i].keyOn || chan[i].opMaskChanged) && chan[i].hardReset) {
if (i<6) {
// restore SL/RR
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
immWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
}
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
}
}
void DivPlatformGenesis::muteChannel(int ch, bool mute) {
@ -540,7 +585,7 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[j];
DivInstrumentFM::Operator& op=chan[ch].state.op[j];
if (isMuted[ch]) {
if (isMuted[ch] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(ch,j)) {
@ -556,6 +601,47 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) {
rWrite(chanOffs[ch]+ADDR_LRAF,(IS_REALLY_MUTED(ch)?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4));
}
void DivPlatformGenesis::commitState(int ch, DivInstrument* ins) {
if (chan[ch].insChanged) {
chan[ch].state=ins->fm;
chan[ch].opMask=
(chan[ch].state.op[0].enable?1:0)|
(chan[ch].state.op[2].enable?2:0)|
(chan[ch].state.op[1].enable?4:0)|
(chan[ch].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[i];
DivInstrumentFM::Operator& op=chan[ch].state.op[i];
if (isMuted[ch] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(ch,i)) {
if (!chan[ch].active || chan[ch].insChanged) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[ch].outVol&0x7f,127));
}
} else {
if (chan[ch].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
if (chan[ch].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
if (chan[ch].insChanged) {
rWrite(chanOffs[ch]+ADDR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3));
rWrite(chanOffs[ch]+ADDR_LRAF,(IS_REALLY_MUTED(ch)?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4));
}
}
int DivPlatformGenesis::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
@ -595,7 +681,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
} else {
rWrite(0x2b,1<<7);
if (dumpWrites) addWrite(0xffff0000,chan[c.chan].dacSample);
if (dumpWrites) {
addWrite(0xffff0000,chan[c.chan].dacSample);
addWrite(0xffff0003,chan[c.chan].dacDirection);
}
}
chan[c.chan].dacPos=0;
chan[c.chan].dacPeriod=0;
@ -638,49 +727,12 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
if (c.chan>=6) break;
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
if (isMuted[c.chan]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(c.chan,i)) {
if (!chan[c.chan].active || chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
}
} else {
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
if (chan[c.chan].insChanged) {
rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
rWrite(chanOffs[c.chan]+ADDR_LRAF,(IS_REALLY_MUTED(c.chan)?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
}
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
@ -728,7 +780,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
if (isMuted[c.chan]) {
if (isMuted[c.chan] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(c.chan,i)) {
@ -863,6 +915,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
} else if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
} else {
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
}
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
}
chan[c.chan].note=c.value;
@ -901,7 +958,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isMuted[c.chan]) {
if (isMuted[c.chan] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(c.chan,c.value)) {
@ -1098,7 +1155,7 @@ void DivPlatformGenesis::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
if (isMuted[i] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
@ -1117,16 +1174,20 @@ void DivPlatformGenesis::forceIns() {
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
if (i<5 || !chan[i].dacMode) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
}
}
}
immWrite(0x2b,0x00);
//rWrite(0x2a,0x00);
if (chan[5].dacMode) {
chan[5].dacSample=-1;
chan[6].dacSample=-1;
rWrite(0x2b,0x80);
}
immWrite(0x22,lfoValue);
flushFirst=true;
}
void DivPlatformGenesis::toggleRegisterDump(bool enable) {
@ -1153,6 +1214,10 @@ int DivPlatformGenesis::getRegisterPoolSize() {
return 512;
}
float DivPlatformGenesis::getPostAmp() {
return 2.0f;
}
void DivPlatformGenesis::reset() {
while (!writes.empty()) writes.pop_front();
memset(regPool,0,512);
@ -1160,7 +1225,17 @@ void DivPlatformGenesis::reset() {
fm_ymfm->reset();
}
OPN2_Reset(&fm);
OPN2_SetChipType(&fm,ladder?ym3438_mode_ym2612:0);
switch (chipType) {
case 1: // YM2612
OPN2_SetChipType(&fm,ym3438_mode_ym2612);
break;
case 2: // YMF276
OPN2_SetChipType(&fm,ym3438_mode_opn);
break;
default: // YM3438
OPN2_SetChipType(&fm,0);
break;
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
@ -1180,6 +1255,7 @@ void DivPlatformGenesis::reset() {
lfoValue=8;
softPCMTimer=0;
extMode=false;
flushFirst=false;
if (softPCM) {
chan[5].dacMode=true;
@ -1216,6 +1292,9 @@ void DivPlatformGenesis::notifyInsChange(int ins) {
}
void DivPlatformGenesis::notifyInsDeletion(void* ins) {
for (int i=0; i<10; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformGenesis::poke(unsigned int addr, unsigned short val) {
@ -1256,14 +1335,28 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) {
chipClock=COLOR_NTSC*15.0/7.0;
break;
}
ladder=flags.getBool("ladderEffect",false);
if (flags.has("chipType")) {
chipType=flags.getInt("chipType",0);
} else {
chipType=flags.getBool("ladderEffect",false)?1:0;
}
noExtMacros=flags.getBool("noExtMacros",false);
fbAllOps=flags.getBool("fbAllOps",false);
OPN2_SetChipType(&fm,ladder?ym3438_mode_ym2612:0);
switch (chipType) {
case 1: // YM2612
OPN2_SetChipType(&fm,ym3438_mode_ym2612);
break;
case 2: // YMF276
OPN2_SetChipType(&fm,ym3438_mode_opn);
break;
default: // YM3438
OPN2_SetChipType(&fm,0);
break;
}
CHECK_CUSTOM_CLOCK;
if (useYMFM) {
if (fm_ymfm!=NULL) delete fm_ymfm;
if (ladder) {
if (chipType==1) {
fm_ymfm=new ymfm::ym2612(iface);
} else {
fm_ymfm=new ymfm::ym3438(iface);
@ -1280,8 +1373,9 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) {
int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
ladder=false;
chipType=0;
skipRegisterWrites=false;
flushFirst=false;
for (int i=0; i<10; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -25,6 +25,7 @@
class DivYM2612Interface: public ymfm::ymfm_interface {
int setA, setB;
int countA, countB;
public:
@ -32,8 +33,8 @@ class DivYM2612Interface: public ymfm::ymfm_interface {
void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks);
DivYM2612Interface():
ymfm::ymfm_interface(),
countA(-1),
countB(-1) {}
countA(0),
countB(0) {}
};
class DivPlatformGenesis: public DivPlatformOPN {
@ -84,7 +85,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
int softPCMTimer;
bool extMode, softPCM, noExtMacros, useYMFM;
bool ladder;
unsigned char chipType;
unsigned char dacVolTable[128];
@ -92,6 +93,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
friend void putDispatchChan(void*,int,int);
inline void processDAC(int iRate);
inline void commitState(int ch, DivInstrument* ins);
void acquire_nuked(short** buf, size_t len);
void acquire_ymfm(short** buf, size_t len);
@ -114,10 +116,11 @@ class DivPlatformGenesis: public DivPlatformOPN {
void setYMFM(bool use);
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
float getPostAmp();
void toggleRegisterDump(bool enable);
void setFlags(const DivConfig& flags);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
virtual void notifyInsDeletion(void* ins);
void setSoftPCM(bool value);
int getPortaFloor(int ch);
void poke(unsigned int addr, unsigned short val);

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -26,6 +26,44 @@
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
void DivPlatformGenesisExt::commitStateExt(int ch, DivInstrument* ins) {
int ordch=orderedOps[ch];
if (opChan[ch].insChanged) {
chan[2].state.alg=ins->fm.alg;
if (ch==0 || fbAllOps) {
chan[2].state.fb=ins->fm.fb;
}
chan[2].state.fms=ins->fm.fms;
chan[2].state.ams=ins->fm.ams;
chan[2].state.op[ordch]=ins->fm.op[ordch];
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
}
}
int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.chan<2) {
return DivPlatformGenesis::dispatch(c);
@ -44,16 +82,6 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (opChan[ch].insChanged) {
chan[2].state.alg=ins->fm.alg;
if (ch==0 || fbAllOps) {
chan[2].state.fb=ins->fm.fb;
}
chan[2].state.fms=ins->fm.fms;
chan[2].state.ams=ins->fm.ams;
chan[2].state.op[ordch]=ins->fm.op[ordch];
}
if (noExtMacros) {
opChan[ch].macroInit(NULL);
} else {
@ -62,30 +90,8 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (!opChan[ch].std.vol.will) {
opChan[ch].outVol=opChan[ch].vol;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
}
commitStateExt(ch,ins);
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
@ -121,7 +127,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
if (isOpMuted[ch]) {
if (isOpMuted[ch] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
@ -202,6 +208,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
break;
case DIV_CMD_LEGATO: {
if (opChan[ch].insChanged) {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
commitStateExt(ch,ins);
opChan[ch].insChanged=false;
}
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].freqChanged=true;
break;
@ -232,7 +243,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isOpMuted[ch]) {
if (isOpMuted[ch] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(2,c.value)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
@ -420,7 +431,7 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
int ordch=orderedOps[ch-2];
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2]) {
if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
@ -578,6 +589,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
opChan[i].freqChanged=false;
}
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) {
@ -640,10 +652,10 @@ void DivPlatformGenesisExt::forceIns() {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (i==2 && extMode) { // extended channel
if (isOpMuted[j]) {
if (isOpMuted[orderedOps[j]] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
@ -746,6 +758,13 @@ void DivPlatformGenesisExt::notifyInsChange(int ins) {
}
}
void DivPlatformGenesisExt::notifyInsDeletion(void* ins) {
DivPlatformGenesis::notifyInsDeletion(ins);
for (int i=0; i<4; i++) {
opChan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
int DivPlatformGenesisExt::getPortaFloor(int ch) {
return (ch>8)?12:0;
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -29,6 +29,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
bool isOpMuted[4];
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
inline void commitStateExt(int ch, DivInstrument* ins);
public:
int dispatch(DivCommand c);
void* getChanState(int chan);
@ -41,6 +42,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
int getPortaFloor(int ch);
void setCSMChannel(unsigned char ch);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -353,6 +353,9 @@ void DivPlatformMSM6258::notifyInsChange(int ins) {
}
void DivPlatformMSM6258::notifyInsDeletion(void* ins) {
for (int i=0; i<1; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformMSM6258::setFlags(const DivConfig& flags) {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -326,6 +326,9 @@ void DivPlatformMSM6295::notifyInsChange(int ins) {
}
void DivPlatformMSM6295::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
const void* DivPlatformMSM6295::getSampleMem(int index) {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -455,35 +455,34 @@ void DivPlatformOPL::tick(bool sysTick) {
}
}
}
}
if (i<melodicChans) {
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<ops; j++) {
unsigned char slot=slots[j][i];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[i].state.op[(ops==4)?orderedOpsL[j]:j];
immWrite(baseAddr+ADDR_SL_RR,0x0f);
immWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
oldWrites[baseAddr+ADDR_SL_RR]=-1;
oldWrites[baseAddr+ADDR_KSL_TL]=-1;
}
}
if (chan[i].keyOn || chan[i].keyOff) {
immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31));
chan[i].keyOff=false;
}
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<ops; j++) {
unsigned char slot=slots[j][i];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[i].state.op[(ops==4)?orderedOpsL[j]:j];
for (int k=0; k<5; k++) {
immWrite(baseAddr+ADDR_SL_RR,0x0f);
immWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
}
int hardResetElapsed=0;
bool mustHardReset=false;
bool weWillWriteRRLater[64];
memset(weWillWriteRRLater,0,64*sizeof(bool));
for (int i=0; i<melodicChans; i++) {
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
if (chan[i].keyOn || chan[i].keyOff) {
immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31));
chan[i].keyOff=false;
}
if (chan[i].hardReset && chan[i].keyOn) {
mustHardReset=true;
for (int j=0; j<ops; j++) {
unsigned char slot=slots[j][i];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
if (baseAddr>0x100) {
weWillWriteRRLater[(baseAddr&0xff)|32]=true;
} else {
weWillWriteRRLater[(baseAddr&0xff)]=true;
}
immWrite(baseAddr+ADDR_SL_RR,0x0f);
hardResetElapsed++;
}
}
}
@ -571,6 +570,11 @@ void DivPlatformOPL::tick(bool sysTick) {
for (int i=0; i<512; i++) {
if (pendingWrites[i]!=oldWrites[i]) {
if ((i>=0x80 && i<0xa0)) {
if (weWillWriteRRLater[i-0x80]) continue;
} else if ((i>=0x180 && i<0x1a0)) {
if (weWillWriteRRLater[32|(i-0x180)]) continue;
}
immWrite(i,pendingWrites[i]&0xff);
oldWrites[i]=pendingWrites[i];
}
@ -589,11 +593,15 @@ void DivPlatformOPL::tick(bool sysTick) {
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
}
if (i<melodicChans) {
if (chan[i].keyOn) {
if (chan[i].keyOn && !chan[i].hardReset) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20));
chan[i].keyOn=false;
} else if (chan[i].freqChanged) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
if (chan[i].keyOn && chan[i].hardReset) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|0);
} else {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
}
}
} else {
if (chan[i].keyOn) {
@ -611,6 +619,35 @@ void DivPlatformOPL::tick(bool sysTick) {
if (updateDrums) {
immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState);
}
// hard reset handling
if (mustHardReset) {
for (unsigned int i=hardResetElapsed; i<128; i++) {
immWrite(0x3f,i&0xff);
}
for (int i=0x80; i<0xa0; i++) {
if (weWillWriteRRLater[i-0x80]) {
immWrite(i,pendingWrites[i]&0xff);
oldWrites[i]=pendingWrites[i];
}
}
for (int i=0x180; i<0x1a0; i++) {
if (weWillWriteRRLater[32|(i-0x180)]) {
immWrite(i,pendingWrites[i]&0xff);
oldWrites[i]=pendingWrites[i];
}
}
for (int i=0; i<melodicChans; i++) {
if (chan[i].hardReset) {
if (chan[i].keyOn) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20));
chan[i].keyOn=false;
} else if (chan[i].freqChanged) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
}
}
}
}
}
#define OPLL_C_NUM 686
@ -698,6 +735,112 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) {
}
}
void DivPlatformOPL::commitState(int ch, DivInstrument* ins) {
if (chan[ch].insChanged) {
if (ch>melodicChans && ins->type==DIV_INS_OPL_DRUMS) {
for (int i=0; i<4; i++) {
chan[melodicChans+i+1].state.alg=ins->fm.alg;
chan[melodicChans+i+1].state.fb=ins->fm.fb;
chan[melodicChans+i+1].state.opllPreset=ins->fm.opllPreset;
chan[melodicChans+i+1].state.fixedDrums=ins->fm.fixedDrums;
chan[melodicChans+i+1].state.kickFreq=ins->fm.kickFreq;
chan[melodicChans+i+1].state.snareHatFreq=ins->fm.snareHatFreq;
chan[melodicChans+i+1].state.tomTopFreq=ins->fm.tomTopFreq;
chan[melodicChans+i+1].state.op[0]=ins->fm.op[i];
}
} else {
chan[ch].state=ins->fm;
}
}
if (chan[ch].insChanged) {
if (ch>melodicChans && ins->type==DIV_INS_OPL_DRUMS) {
for (int i=0; i<4; i++) {
int ch=melodicChans+1+i;
unsigned char slot=slots[0][ch];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[ch].state.op[0];
chan[ch].fourOp=false;
if (isMuted[ch]) {
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[ch].outVol&0x3f,63))|(op.ksl<<6));
}
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
if (oplType>1) {
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
}
if (isMuted[ch]) {
oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1));
} else {
oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&15)<<4));
}
}
} else {
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
chan[ch].fourOp=(ops==4);
if (chan[ch].fourOp) {
/*
if (chan[ch+1].active) {
chan[ch+1].keyOff=true;
chan[ch+1].keyOn=false;
chan[ch+1].active=false;
}*/
chan[ch+1].insChanged=true;
chan[ch+1].macroInit(NULL);
}
update4OpMask=true;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][ch];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[ch].state.op[(ops==4)?orderedOpsL[i]:i];
if (isMuted[ch]) {
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
if (KVSL(ch,i) || ch>melodicChans) {
rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[ch].outVol&0x3f,63))|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
}
}
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
if (oplType>1) {
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
}
}
if (isMuted[ch]) {
oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1));
if (ops==4) {
oldWrites[chanMap[ch+1]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1));
}
} else {
oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&15)<<4));
if (ops==4) {
oldWrites[chanMap[ch+1]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&15)<<4));
}
}
}
}
}
int DivPlatformOPL::dispatch(DivCommand c) {
if (c.chan>=totalChans && c.chan!=adpcmChan) return 0;
// ineffective in 4-op mode
@ -780,114 +923,12 @@ int DivPlatformOPL::dispatch(DivCommand c) {
}
DivInstrument* ins=parent->getIns(chan[c.chan].ins,c.chan>melodicChans?DIV_INS_OPL_DRUMS:DIV_INS_OPL);
if (chan[c.chan].insChanged) {
if (c.chan>melodicChans && ins->type==DIV_INS_OPL_DRUMS) {
for (int i=0; i<4; i++) {
chan[melodicChans+i+1].state.alg=ins->fm.alg;
chan[melodicChans+i+1].state.fb=ins->fm.fb;
chan[melodicChans+i+1].state.opllPreset=ins->fm.opllPreset;
chan[melodicChans+i+1].state.fixedDrums=ins->fm.fixedDrums;
chan[melodicChans+i+1].state.kickFreq=ins->fm.kickFreq;
chan[melodicChans+i+1].state.snareHatFreq=ins->fm.snareHatFreq;
chan[melodicChans+i+1].state.tomTopFreq=ins->fm.tomTopFreq;
chan[melodicChans+i+1].state.op[0]=ins->fm.op[i];
}
} else {
chan[c.chan].state=ins->fm;
}
}
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (chan[c.chan].insChanged) {
if (c.chan>melodicChans && ins->type==DIV_INS_OPL_DRUMS) {
for (int i=0; i<4; i++) {
int ch=melodicChans+1+i;
unsigned char slot=slots[0][ch];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[ch].state.op[0];
chan[ch].fourOp=false;
if (isMuted[ch]) {
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[ch].outVol&0x3f,63))|(op.ksl<<6));
}
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
if (oplType>1) {
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
}
if (isMuted[ch]) {
oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1));
} else {
oldWrites[chanMap[ch]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&15)<<4));
}
}
} else {
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
chan[c.chan].fourOp=(ops==4);
if (chan[c.chan].fourOp) {
/*
if (chan[c.chan+1].active) {
chan[c.chan+1].keyOff=true;
chan[c.chan+1].keyOn=false;
chan[c.chan+1].active=false;
}*/
chan[c.chan+1].insChanged=true;
chan[c.chan+1].macroInit(NULL);
}
update4OpMask=true;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][c.chan];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
if (isMuted[c.chan]) {
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
if (KVSL(c.chan,i) || c.chan>melodicChans) {
rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[c.chan].outVol&0x3f,63))|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
}
}
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
if (oplType>1) {
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
}
}
if (isMuted[c.chan]) {
oldWrites[chanMap[c.chan]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1));
if (ops==4) {
oldWrites[chanMap[c.chan+1]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1));
}
} else {
oldWrites[chanMap[c.chan]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&15)<<4));
if (ops==4) {
oldWrites[chanMap[c.chan+1]+ADDR_LR_FB_ALG]=-1;
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&15)<<4));
}
}
}
}
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
@ -1084,6 +1125,11 @@ int DivPlatformOPL::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
}
chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value));
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
@ -1518,7 +1564,11 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) {
}
DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) {
if (ch>=totalChans) return NULL;
if (oplType==759) {
if (ch>=totalChans+1) return NULL;
} else {
if (ch>=totalChans) return NULL;
}
if (oplType==3 && ch<12) {
if (chan[ch&(~1)].fourOp) {
if (ch&1) {
@ -1649,6 +1699,9 @@ void DivPlatformOPL::notifyInsChange(int ins) {
}
void DivPlatformOPL::notifyInsDeletion(void* ins) {
for (int i=0; i<totalChans; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformOPL::poke(unsigned int addr, unsigned short val) {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -100,6 +100,7 @@ class DivPlatformOPL: public DivDispatch {
int octave(int freq);
int toFreq(int freq);
double NOTE_ADPCMB(int note);
void commitState(int ch, DivInstrument* ins);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -19,6 +19,7 @@
#include "opll.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <string.h>
#include <math.h>
@ -331,6 +332,55 @@ void DivPlatformOPLL::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
}
void DivPlatformOPLL::commitState(int ch, DivInstrument* ins) {
if (chan[ch].insChanged) {
chan[ch].state=ins->fm;
}
if (chan[ch].insChanged) {
// update custom preset
if (chan[ch].state.opllPreset==0) {
DivInstrumentFM::Operator& mod=chan[ch].state.op[0];
DivInstrumentFM::Operator& car=chan[ch].state.op[1];
rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult));
rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult));
rWrite(0x02,(mod.ksl<<6)|(mod.tl&63));
rWrite(0x03,(car.ksl<<6)|((chan[ch].state.fms&1)<<4)|((chan[ch].state.ams&1)<<3)|chan[ch].state.fb);
rWrite(0x04,(mod.ar<<4)|(mod.dr));
rWrite(0x05,(car.ar<<4)|(car.dr));
rWrite(0x06,(mod.sl<<4)|(mod.rr));
rWrite(0x07,(car.sl<<4)|(car.rr));
lastCustomMemory=ch;
}
if (chan[ch].state.opllPreset==16) { // compatible drums mode
if (ch>=6) {
drums=true;
immWrite(0x16,0x20);
immWrite(0x26,0x05);
immWrite(0x16,0x20);
immWrite(0x26,0x05);
immWrite(0x17,0x50);
immWrite(0x27,0x05);
immWrite(0x17,0x50);
immWrite(0x27,0x05);
immWrite(0x18,0xC0);
immWrite(0x28,0x01);
}
} else {
if (ch>=6) {
if (drums) {
drums=false;
immWrite(0x0e,0);
drumState=0;
}
}
if (ch<9) {
rWrite(0x30+ch,((15-VOL_SCALE_LOG_BROKEN(chan[ch].outVol,15-chan[ch].state.op[1].tl,15))&15)|(chan[ch].state.opllPreset<<4));
}
}
}
}
int DivPlatformOPLL::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
@ -375,49 +425,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
break;
}
if (chan[c.chan].insChanged) {
// update custom preset
if (chan[c.chan].state.opllPreset==0) {
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult));
rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult));
rWrite(0x02,(mod.ksl<<6)|(mod.tl&63));
rWrite(0x03,(car.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb);
rWrite(0x04,(mod.ar<<4)|(mod.dr));
rWrite(0x05,(car.ar<<4)|(car.dr));
rWrite(0x06,(mod.sl<<4)|(mod.rr));
rWrite(0x07,(car.sl<<4)|(car.rr));
lastCustomMemory=c.chan;
}
if (chan[c.chan].state.opllPreset==16) { // compatible drums mode
if (c.chan>=6) {
drums=true;
immWrite(0x16,0x20);
immWrite(0x26,0x05);
immWrite(0x16,0x20);
immWrite(0x26,0x05);
immWrite(0x17,0x50);
immWrite(0x27,0x05);
immWrite(0x17,0x50);
immWrite(0x27,0x05);
immWrite(0x18,0xC0);
immWrite(0x28,0x01);
}
} else {
if (c.chan>=6) {
if (drums) {
drums=false;
immWrite(0x0e,0);
drumState=0;
}
}
if (c.chan<9) {
rWrite(0x30+c.chan,((15-VOL_SCALE_LOG_BROKEN(chan[c.chan].outVol,15-chan[c.chan].state.op[1].tl,15))&15)|(chan[c.chan].state.opllPreset<<4));
}
}
}
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
@ -541,6 +549,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
}
case DIV_CMD_LEGATO: {
if (c.chan>=9 && !properDrums) return 0;
if (c.chan<6 || (!drums && !properDrums)) {
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPLL);
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
}
}
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
@ -940,6 +955,9 @@ void DivPlatformOPLL::notifyInsChange(int ins) {
}
void DivPlatformOPLL::notifyInsDeletion(void* ins) {
for (int i=0; i<11; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformOPLL::poke(unsigned int addr, unsigned short val) {
@ -958,6 +976,10 @@ void DivPlatformOPLL::setYMFM(bool use) {
useYMFM=use;
}
float DivPlatformOPLL::getPostAmp() {
return 1.5f;
}
void DivPlatformOPLL::setFlags(const DivConfig& flags) {
int clockSel=flags.getInt("clockSel",0);
if (clockSel==3) {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -73,6 +73,7 @@ class DivPlatformOPLL: public DivDispatch {
int octave(int freq);
int toFreq(int freq);
void commitState(int ch, DivInstrument* ins);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
@ -95,6 +96,7 @@ class DivPlatformOPLL: public DivDispatch {
void setYMFM(bool use);
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
float getPostAmp();
void toggleRegisterDump(bool enable);
void setVRC7(bool vrc);
void setProperDrums(bool pd);

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -163,21 +163,15 @@ void DivPlatformPCE::tick(bool sysTick) {
if (chan[i].std.duty.had && i>=4) {
chan[i].noise=chan[i].std.duty.val;
chan[i].freqChanged=true;
int noiseSeek=chan[i].note;
if (noiseSeek<0) noiseSeek=0;
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
int noiseSeek=chan[i].fixedArp?chan[i].baseNoteOverride:(chan[i].note+chan[i].arpOff);
if (noiseSeek<0) noiseSeek=0;
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val);
chan[i].baseFreq=NOTE_PERIODIC(noiseSeek);
if (noiseSeek<0) noiseSeek=0;
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
chan[i].noiseSeek=noiseSeek;
}
chan[i].freqChanged=true;
}
@ -246,6 +240,15 @@ void DivPlatformPCE::tick(bool sysTick) {
if (chan[i].freq>4095) chan[i].freq=4095;
chWrite(i,0x02,chan[i].freq&0xff);
chWrite(i,0x03,chan[i].freq>>8);
if (i>=4) {
int noiseSeek=(chan[i].fixedArp?chan[i].baseNoteOverride:(chan[i].note+chan[i].arpOff))+chan[i].pitch2;
if (!parent->song.properNoiseLayout && noiseSeek<0) noiseSeek=0;
if (!NEW_ARP_STRAT) {
noiseSeek=chan[i].noiseSeek;
}
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
}
if (chan[i].keyOn) {
//rWrite(16+i*5,0x80);
//chWrite(i,0x04,0x80|chan[i].vol);
@ -331,9 +334,8 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
int noiseSeek=chan[c.chan].note;
if (noiseSeek<0) noiseSeek=0;
chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
chan[c.chan].noiseSeek=c.value;
if (chan[c.chan].noiseSeek<0) chan[c.chan].noiseSeek=0;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
@ -431,7 +433,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
}
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].noise=c.value;
chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|chan[c.chan].note):0);
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_SAMPLE_MODE:
chan[c.chan].pcm=c.value;

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -34,7 +34,7 @@ class DivPlatformPCE: public DivDispatch {
unsigned char pan;
bool noise, pcm, furnaceDac, deferredWaveUpdate;
signed short wave;
int macroVolMul;
int macroVolMul, noiseSeek;
DivWaveSynth ws;
Channel():
SharedChannel<signed char>(31),
@ -51,7 +51,8 @@ class DivPlatformPCE: public DivDispatch {
furnaceDac(false),
deferredWaveUpdate(false),
wave(-1),
macroVolMul(31) {}
macroVolMul(31),
noiseSeek(0) {}
};
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -37,13 +37,12 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
continue;
}
if (chan[0].useWave || (chan[0].sample>=0 && chan[0].sample<parent->song.sampleLen)) {
chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-(chan[0].freq>>16):(chan[0].freq>>16);
chan[0].audSub+=(chan[0].freq&0xffff);
if (chan[0].audSub>=0x10000) {
chan[0].audSub-=0x10000;
chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1;
}
chan[0].audSub+=chan[0].freq;
if (chan[0].useWave) {
while (chan[0].audSub>=0x10000) {
chan[0].audSub-=0x10000;
chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1;
}
if (chan[0].audPos>=(int)chan[0].audLen) {
chan[0].audPos%=chan[0].audLen;
chan[0].audDir=false;
@ -52,105 +51,123 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
} else {
DivSample* s=parent->getSample(chan[0].sample);
if (s->samples>0) {
if (chan[0].audDir) {
if (s->isLoopable()) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOP_FORWARD:
case DIV_SAMPLE_LOOP_PINGPONG:
if (chan[0].audPos<s->loopStart) {
chan[0].audPos=s->loopStart+(s->loopStart-chan[0].audPos);
chan[0].audDir=false;
}
break;
case DIV_SAMPLE_LOOP_BACKWARD:
if (chan[0].audPos<s->loopStart) {
chan[0].audPos=s->loopEnd-1-(s->loopStart-chan[0].audPos);
chan[0].audDir=true;
}
break;
default:
if (chan[0].audPos<0) {
chan[0].sample=-1;
}
break;
while (chan[0].audSub>=0x10000) {
chan[0].audSub-=0x10000;
chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1;
if (chan[0].audDir) {
if (s->isLoopable()) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOP_FORWARD:
case DIV_SAMPLE_LOOP_PINGPONG:
if (chan[0].audPos<s->loopStart) {
chan[0].audPos=s->loopStart+(s->loopStart-chan[0].audPos);
chan[0].audDir=false;
}
break;
case DIV_SAMPLE_LOOP_BACKWARD:
if (chan[0].audPos<s->loopStart) {
chan[0].audPos=s->loopEnd-1-(s->loopStart-chan[0].audPos);
chan[0].audDir=true;
}
break;
default:
if (chan[0].audPos<0) {
chan[0].sample=-1;
}
break;
}
} else if (chan[0].audPos>=(int)s->samples) {
chan[0].sample=-1;
}
} else {
if (s->isLoopable()) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOP_FORWARD:
if (chan[0].audPos>=s->loopEnd) {
chan[0].audPos=(chan[0].audPos+s->loopStart)-s->loopEnd;
chan[0].audDir=false;
}
break;
case DIV_SAMPLE_LOOP_BACKWARD:
case DIV_SAMPLE_LOOP_PINGPONG:
if (chan[0].audPos>=s->loopEnd) {
chan[0].audPos=s->loopEnd-1-(s->loopEnd-1-chan[0].audPos);
chan[0].audDir=true;
}
break;
default:
if (chan[0].audPos>=(int)s->samples) {
chan[0].sample=-1;
}
break;
}
} else if (chan[0].audPos>=(int)s->samples) {
chan[0].sample=-1;
}
} else if (chan[0].audPos>=(int)s->samples) {
chan[0].sample=-1;
}
} else {
if (s->isLoopable()) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOP_FORWARD:
if (chan[0].audPos>=s->loopEnd) {
chan[0].audPos=(chan[0].audPos+s->loopStart)-s->loopEnd;
chan[0].audDir=false;
}
break;
case DIV_SAMPLE_LOOP_BACKWARD:
case DIV_SAMPLE_LOOP_PINGPONG:
if (chan[0].audPos>=s->loopEnd) {
chan[0].audPos=s->loopEnd-1-(s->loopEnd-1-chan[0].audPos);
chan[0].audDir=true;
}
break;
default:
if (chan[0].audPos>=(int)s->samples) {
chan[0].sample=-1;
}
break;
}
} else if (chan[0].audPos>=(int)s->samples) {
chan[0].sample=-1;
}
}
if (chan[0].audPos>=0 && chan[0].audPos<(int)s->samples) {
int s_4=((chan[0].audPos-4)>=0)?s->data16[chan[0].audPos-4]:0;
int s_3=((chan[0].audPos-3)>=0)?s->data16[chan[0].audPos-3]:0;
int s_2=((chan[0].audPos-2)>=0)?s->data16[chan[0].audPos-2]:0;
int s_1=((chan[0].audPos-1)>=0)?s->data16[chan[0].audPos-1]:0;
int s0=s->data16[chan[0].audPos];
int s1=((chan[0].audPos+1)<(int)s->samples)?s->data16[chan[0].audPos+1]:0;
int s2=((chan[0].audPos+2)<(int)s->samples)?s->data16[chan[0].audPos+2]:0;
int s3=((chan[0].audPos+3)<(int)s->samples)?s->data16[chan[0].audPos+3]:0;
switch (interp) {
case 1: // linear
output=s0+((s1-s0)*(chan[0].audSub&0xffff)>>16);
break;
case 2: { // cubic
float* cubicTable=DivFilterTables::getCubicTable();
float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2];
float result=(float)s_1*t[0]+(float)s0*t[1]+(float)s1*t[2]+(float)s2*t[3];
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
case 3: { // sinc
float* sincTable=DivFilterTables::getSincTable8();
float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2];
float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2];
float result=(
s_4*t2[3]+
s_3*t2[2]+
s_2*t2[1]+
s_1*t2[0]+
s0*t1[0]+
s1*t1[1]+
s2*t1[2]+
s3*t1[3]
);
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
default: // none
output=s0;
break;
chan[0].audDat[0]=chan[0].audDat[1];
chan[0].audDat[1]=chan[0].audDat[2];
chan[0].audDat[2]=chan[0].audDat[3];
chan[0].audDat[3]=chan[0].audDat[4];
chan[0].audDat[4]=chan[0].audDat[5];
chan[0].audDat[5]=chan[0].audDat[6];
chan[0].audDat[6]=chan[0].audDat[7];
if (chan[0].audPos>=0 && chan[0].audPos<(int)s->samples) {
chan[0].audDat[7]=s->data16[chan[0].audPos];
} else {
chan[0].audDat[7]=0;
}
}
} else {
chan[0].sample=-1;
chan[0].audSub=0;
chan[0].audPos=0;
}
const short s0=chan[0].audDat[0];
const short s1=chan[0].audDat[1];
const short s2=chan[0].audDat[2];
const short s3=chan[0].audDat[3];
const short s4=chan[0].audDat[4];
const short s5=chan[0].audDat[5];
const short s6=chan[0].audDat[6];
const short s7=chan[0].audDat[7];
switch (interp) {
case 1: // linear
output=s6+((s7-s6)*(chan[0].audSub&0xffff)>>16);
break;
case 2: { // cubic
float* cubicTable=DivFilterTables::getCubicTable();
float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2];
float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3];
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
case 3: { // sinc
float* sincTable=DivFilterTables::getSincTable8();
float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2];
float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2];
float result=(
s0*t2[3]+
s1*t2[2]+
s2*t2[1]+
s3*t2[0]+
s4*t1[0]+
s5*t1[1]+
s6*t1[2]+
s7*t1[3]
);
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
default: // none
output=s7;
break;
}
}
}
@ -266,6 +283,7 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
chan[0].audPos=0;
}
chan[0].audSub=0;
memset(chan[0].audDat,0,8*sizeof(short));
if (c.value!=DIV_NOTE_NULL) {
chan[0].freqChanged=true;
chan[0].note=c.value;
@ -408,6 +426,7 @@ void DivPlatformPCMDAC::reset() {
chan[0].std.setEngine(parent);
chan[0].ws.setEngine(parent);
chan[0].ws.init(NULL,32,255);
memset(chan[0].audDat,0,8*sizeof(short));
}
int DivPlatformPCMDAC::getOutputCount() {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -29,6 +29,7 @@ class DivPlatformPCMDAC: public DivDispatch {
bool audDir;
unsigned int audLoc;
unsigned short audLen;
short audDat[8];
int audPos;
int audSub;
int sample, wave;
@ -41,6 +42,7 @@ class DivPlatformPCMDAC: public DivDispatch {
audDir(false),
audLoc(0),
audLen(0),
audDat{0,0,0,0,0,0,0,0},
audPos(0),
audSub(0),
sample(-1),

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -606,7 +606,7 @@ void DivPlatformQSound::forceIns() {
for (int i=0; i<19; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
//chan[i].sample=-1;
}
}
@ -645,7 +645,7 @@ bool DivPlatformQSound::keyOffAffectsArp(int ch) {
}
void DivPlatformQSound::notifyInsChange(int ins) {
for (int i=0; i<4; i++) {
for (int i=0; i<19; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
@ -657,7 +657,7 @@ void DivPlatformQSound::notifyWaveChange(int wave) {
}
void DivPlatformQSound::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
for (int i=0; i<19; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -73,8 +73,8 @@ void DivPlatformSAA1099::acquire_saaSound(short** buf, size_t len) {
saa_saaSound->GenerateMany((unsigned char*)saaBuf[0],len,oscBuf);
#ifdef TA_BIG_ENDIAN
for (size_t i=0; i<len; i++) {
buf[0][i]=(short)((((unsigned short)saaBuf[0][1+(i<<1)])<<8)|(((unsigned short)saaBuf[0][1+(i<<1)])>>8));
buf[1][i]=(short)((((unsigned short)saaBuf[0][i<<1])<<8)|(((unsigned short)saaBuf[0][i<<1])>>8));
buf[0][i]=(short)((((unsigned short)saaBuf[0][i<<1])<<8)|(((unsigned short)saaBuf[0][i<<1])>>8));
buf[1][i]=(short)((((unsigned short)saaBuf[0][1+(i<<1)])<<8)|(((unsigned short)saaBuf[0][1+(i<<1)])>>8));
}
#else
for (size_t i=0; i<len; i++) {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -82,9 +82,7 @@ const char** DivPlatformSCC::getRegisterSheet() {
void DivPlatformSCC::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
for (int i=0; i<16; i++) {
scc->tick();
}
scc->tick(16);
short out=(short)scc->out()<<5;
buf[0][h]=out;

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -23,52 +23,34 @@
#include <string.h>
#include <math.h>
//#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
//#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define chWrite(c,a,v) rWrite(((c)<<3)+(a),v)
void DivPlatformSegaPCM::acquire(short** buf, size_t len) {
static int os[2];
for (size_t h=0; h<len; h++) {
os[0]=0; os[1]=0;
// do a PCM cycle
pcmL=0; pcmR=0;
for (int i=0; i<16; i++) {
if (chan[i].pcm.sample>=0 && chan[i].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].pcm.sample);
if (s->samples<=0) {
chan[i].pcm.sample=-1;
oscBuf[i]->data[oscBuf[i]->needle++]=0;
continue;
}
if (!isMuted[i]) {
oscBuf[i]->data[oscBuf[i]->needle++]=s->data8[chan[i].pcm.pos>>8]*(chan[i].chVolL+chan[i].chVolR)>>1;
pcmL+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolL);
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
}
chan[i].pcm.pos+=chan[i].pcm.freq;
if (s->isLoopable() && chan[i].pcm.pos>=((unsigned int)s->loopEnd<<8)) {
chan[i].pcm.pos=s->loopStart<<8;
} else if (chan[i].pcm.pos>=(s->samples<<8)) {
chan[i].pcm.sample=-1;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
}
while (!writes.empty()) {
QueuedWrite w=writes.front();
pcm.write(w.addr,w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
}
os[0]=pcmL;
pcm.sound_stream_update(os);
if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767;
os[1]=pcmR;
if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767;
buf[0][h]=os[0];
buf[1][h]=os[1];
for (int i=0; i<16; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=pcm.lastOut[i][0]+pcm.lastOut[i][1];
}
}
}
@ -81,10 +63,8 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127;
chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127;
if (dumpWrites) {
addWrite(0x10002+(i<<3),chan[i].chVolL);
addWrite(0x10003+(i<<3),chan[i].chVolR);
}
rWrite(2+(i<<3),chan[i].chVolL);
rWrite(3+(i<<3),chan[i].chVolR);
}
}
@ -100,17 +80,13 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
if (parent->song.newSegaPCM) if (chan[i].std.panL.had) {
chan[i].chPanL=chan[i].std.panL.val&127;
chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127;
if (dumpWrites) {
addWrite(0x10002+(i<<3),chan[i].chVolL);
}
rWrite(2+(i<<3),chan[i].chVolL);
}
if (parent->song.newSegaPCM) if (chan[i].std.panR.had) {
chan[i].chPanR=chan[i].std.panR.val&127;
chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127;
if (dumpWrites) {
addWrite(0x10003+(i<<3),chan[i].chVolR);
}
rWrite(3+(i<<3),chan[i].chVolR);
}
if (chan[i].std.pitch.had) {
@ -145,56 +121,48 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
off=(double)s->centerRate/8363.0;
}
chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250)+chan[i].pitch2;
if (dumpWrites) {
addWrite(0x10007+(i<<3),chan[i].pcm.freq);
}
rWrite(7+(i<<3),chan[i].pcm.freq);
}
chan[i].freqChanged=false;
if (chan[i].keyOn || chan[i].keyOff) {
if (chan[i].keyOn && !chan[i].keyOff) {
if (dumpWrites) {
addWrite(0x10086+(i<<3),3);
}
rWrite(0x86+(i<<3),3);
chan[i].pcm.pos=0;
if (chan[i].furnacePCM) {
if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff;
addWrite(0x10086+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
addWrite(0x10084+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
addWrite(0x10085+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
addWrite(0x10006+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
if (loopStart<0 || loopStart>=actualLength) {
addWrite(0x10086+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+s->loopOffP;
addWrite(0x10004+(i<<3),loopPos&0xff);
addWrite(0x10005+(i<<3),(loopPos>>8)&0xff);
addWrite(0x10086+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
}
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff;
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
rWrite(6+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
if (loopStart<0 || loopStart>=actualLength) {
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+sampleLoopOff[chan[i].pcm.sample];
rWrite(4+(i<<3),loopPos&0xff);
rWrite(5+(i<<3),(loopPos>>8)&0xff);
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
}
} else {
if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>65536) actualLength=65536;
addWrite(0x10086+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
addWrite(0x10084+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
addWrite(0x10085+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
addWrite(0x10006+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
if (loopStart<0 || loopStart>=actualLength) {
addWrite(0x10086+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+s->loopOffP;
addWrite(0x10004+(i<<3),loopPos&0xff);
addWrite(0x10005+(i<<3),(loopPos>>8)&0xff);
addWrite(0x10086+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
}
addWrite(0x10007+(i<<3),chan[i].pcm.freq);
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff;
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
rWrite(6+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
if (loopStart<0 || loopStart>=actualLength) {
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+sampleLoopOff[chan[i].pcm.sample];
rWrite(4+(i<<3),loopPos&0xff);
rWrite(5+(i<<3),(loopPos>>8)&0xff);
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
}
rWrite(7+(i<<3),chan[i].pcm.freq);
}
}
chan[i].keyOn=false;
@ -206,6 +174,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
void DivPlatformSegaPCM::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
pcm.mute(ch,mute);
}
int DivPlatformSegaPCM::dispatch(DivCommand c) {
@ -219,9 +188,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
if (c.value!=DIV_NOTE_NULL) chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
if (dumpWrites) {
addWrite(0x10086+(c.chan<<3),3);
}
rWrite(0x86+(c.chan<<3),3);
chan[c.chan].macroInit(NULL);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
@ -245,9 +212,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].pcm.sample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
if (dumpWrites) {
addWrite(0x10086+(c.chan<<3),3);
}
rWrite(0x86+(c.chan<<3),3);
break;
}
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/31250);
@ -259,9 +224,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].pcm.sample=-1;
if (dumpWrites) {
addWrite(0x10086+(c.chan<<3),3);
}
rWrite(0x86+(c.chan<<3),3);
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
@ -288,10 +251,8 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].chVolL=c.value;
chan[c.chan].chVolR=c.value;
}
if (dumpWrites) {
addWrite(0x10002+(c.chan<<3),chan[c.chan].chVolL);
addWrite(0x10003+(c.chan<<3),chan[c.chan].chVolR);
}
rWrite(2+(c.chan<<3),chan[c.chan].chVolL);
rWrite(3+(c.chan<<3),chan[c.chan].chVolR);
break;
}
case DIV_CMD_GET_VOLUME: {
@ -314,10 +275,8 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].chVolL=c.value>>1;
chan[c.chan].chVolR=c.value2>>1;
}
if (dumpWrites) {
addWrite(0x10002+(c.chan<<3),chan[c.chan].chVolL);
addWrite(0x10003+(c.chan<<3),chan[c.chan].chVolR);
}
rWrite(2+(c.chan<<3),chan[c.chan].chVolL);
rWrite(3+(c.chan<<3),chan[c.chan].chVolR);
break;
}
case DIV_CMD_PITCH: {
@ -381,9 +340,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
break;
case DIV_CMD_SAMPLE_FREQ:
chan[c.chan].pcm.freq=c.value;
if (dumpWrites) {
addWrite(0x10007+(c.chan<<3),chan[c.chan].pcm.freq);
}
rWrite(7+(c.chan<<3),chan[c.chan].pcm.freq);
break;
default:
//printf("WARNING: unimplemented command %d\n",c.cmd);
@ -395,6 +352,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
void DivPlatformSegaPCM::forceIns() {
for (int i=0; i<16; i++) {
chan[i].insChanged=true;
rWrite(2+(i<<3),chan[i].chVolL);
rWrite(3+(i<<3),chan[i].chVolR);
rWrite(7+(i<<3),chan[i].pcm.freq);
}
}
@ -406,6 +367,12 @@ void DivPlatformSegaPCM::notifyInsChange(int ins) {
}
}
void DivPlatformSegaPCM::notifyInsDeletion(void* ins) {
for (int i=0; i<16; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void* DivPlatformSegaPCM::getChanState(int ch) {
return &chan[ch];
}
@ -419,7 +386,7 @@ DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) {
}
unsigned char* DivPlatformSegaPCM::getRegisterPool() {
return regPool;
return pcm.get_ram();
}
int DivPlatformSegaPCM::getRegisterPoolSize() {
@ -427,11 +394,29 @@ int DivPlatformSegaPCM::getRegisterPoolSize() {
}
void DivPlatformSegaPCM::poke(unsigned int addr, unsigned short val) {
//immWrite(addr,val);
rWrite(addr,val);
}
void DivPlatformSegaPCM::poke(std::vector<DivRegWrite>& wlist) {
//for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
const void* DivPlatformSegaPCM::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformSegaPCM::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0;
}
size_t DivPlatformSegaPCM::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
bool DivPlatformSegaPCM::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>255) return false;
return sampleLoaded[sample];
}
void DivPlatformSegaPCM::reset() {
@ -451,17 +436,20 @@ void DivPlatformSegaPCM::reset() {
sampleBank=0;
delay=0;
if (dumpWrites) {
for (int i=0; i<16; i++) {
addWrite(0x10086+(i<<3),3);
addWrite(0x10002+(i<<3),0x7f);
addWrite(0x10003+(i<<3),0x7f);
}
pcm.device_start();
for (int i=0; i<16; i++) {
rWrite(0x86+(i<<3),3);
rWrite(2+(i<<3),0x7f);
rWrite(3+(i<<3),0x7f);
}
}
void DivPlatformSegaPCM::renderSamples(int sysID) {
size_t memPos=0;
memset(sampleMem,0,16777216);
memset(sampleLoaded,0,256*sizeof(bool));
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* sample=parent->getSample(i);
@ -471,6 +459,7 @@ void DivPlatformSegaPCM::reset() {
memPos=(memPos+0xffff)&0xff0000;
}
logV("- sample %d will be at %x with length %x",i,memPos,alignedSize);
sampleLoaded[i]=true;
if (memPos>=16777216) break;
sampleOffSegaPCM[i]=memPos;
unsigned int readPos=0;
@ -478,20 +467,21 @@ void DivPlatformSegaPCM::reset() {
if (readPos>=(unsigned int)sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
if (sample->isLoopable()) {
readPos=sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
memPos++;
sampleMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
} else {
memPos++;
sampleMem[memPos++]=0x80;
}
} else {
memPos++;
sampleMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
}
readPos++;
if (memPos>=16777216) break;
}
sample->loopOffP=readPos-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
sampleLoopOff[i]=readPos-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
if (memPos>=16777216) break;
}
}
sampleMemLen=memPos;
}
void DivPlatformSegaPCM::setFlags(const DivConfig& flags) {
chipClock=8000000.0;
@ -514,6 +504,11 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, const DivC
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
sampleMem=new unsigned char[16777216];
pcm.set_bank(segapcm_device::BANK_12M|segapcm_device::BANK_MASKF8);
pcm.set_read([this](unsigned int addr) -> unsigned char {
return sampleMem[addr&0xffffff];
});
setFlags(flags);
reset();
@ -524,6 +519,7 @@ void DivPlatformSegaPCM::quit() {
for (int i=0; i<16; i++) {
delete oscBuf[i];
}
delete sampleMem;
}
DivPlatformSegaPCM::~DivPlatformSegaPCM() {

View file

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 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
@ -22,6 +22,7 @@
#include "../dispatch.h"
#include "../instrument.h"
#include "sound/segapcm.h"
#include <queue>
class DivPlatformSegaPCM: public DivDispatch {
@ -52,6 +53,8 @@ class DivPlatformSegaPCM: public DivDispatch {
};
Channel chan[16];
DivDispatchOscBuffer* oscBuf[16];
unsigned char* sampleMem;
size_t sampleMemLen;
struct QueuedWrite {
unsigned short addr;
unsigned char val;
@ -59,6 +62,7 @@ class DivPlatformSegaPCM: public DivDispatch {
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
segapcm_device pcm;
int delay;
int pcmL, pcmR, pcmCycles;
unsigned char sampleBank;
@ -72,6 +76,8 @@ class DivPlatformSegaPCM: public DivDispatch {
short pendingWrites[256];
unsigned int sampleOffSegaPCM[256];
unsigned int sampleLoopOff[256];
bool sampleLoaded[256];
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
@ -89,11 +95,16 @@ class DivPlatformSegaPCM: public DivDispatch {
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void renderSamples(int chipID);
void setFlags(const DivConfig& flags);
int getOutputCount();
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const void* getSampleMem(int index=0);
size_t getSampleMemCapacity(int index=0);
size_t getSampleMemUsage(int index=0);
bool isSampleLoaded(int index, int sample);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformSegaPCM();

View file

@ -0,0 +1,402 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 "sm8521.h"
#include "../engine.h"
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 64
const char* regCheatSheetSM8521[]={
"SGC", "40",
"SG0L", "42",
"SG1L", "44",
"SG0TL", "46",
"SG0TH", "47",
"SG1TL", "48",
"SG1TH", "49",
"SG2L", "4A",
"SG2TL", "4C",
"SG2TH", "4D",
"SGDA", "4E",
"SG0Wn", "60+n",
"SG1Wn", "70+n",
NULL
};
const char** DivPlatformSM8521::getRegisterSheet() {
return regCheatSheetSM8521;
}
void DivPlatformSM8521::acquire(short** buf, size_t len) {
while (!writes.empty()) {
QueuedWrite w=writes.front();
sm8521_write(&sm8521,w.addr,w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
}
for (size_t h=0; h<len; h++) {
sm8521_sound_tick(&sm8521,8);
buf[0][h]=sm8521.out<<6;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<6;
}
oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<6;
}
}
void DivPlatformSM8521::updateWave(int ch) {
if (ch<2) {
const unsigned char temp=regPool[0x40];
rWrite(0x40,temp&~(1<<ch));
for (int i=0; i<16; i++) {
int nibble1=(chan[ch].ws.output[((i<<1)+chan[ch].antiClickWavePos-1)&31]-8)&0xf;
int nibble2=(chan[ch].ws.output[((1+(i<<1))+chan[ch].antiClickWavePos-1)&31]-8)&0xf;
rWrite(0x60+i+(ch*16),(nibble2<<4)|nibble1);
}
if (chan[ch].active) {
rWrite(0x40,temp|(1<<ch));
}
chan[ch].antiClickWavePos&=31;
}
}
void DivPlatformSM8521::tick(bool sysTick) {
unsigned char keyState=0x80;
for (int i=0; i<3; i++) {
// anti-click
if (antiClickEnabled && sysTick && chan[i].freq>0) {
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
chan[i].antiClickPeriodCount%=chan[i].freq;
}
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&31)*MIN(31,chan[i].std.vol.val))>>5;
if (!isMuted[i]) {
chan[i].volumeChanged=true;
}
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].volumeChanged) {
if (isMuted[i]) {
rWrite(volMap[i],0);
} else {
rWrite(volMap[i],chan[i].outVol&0x1f);
}
chan[i].volumeChanged=false;
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
chan[i].antiClickWavePos=0;
chan[i].antiClickPeriodCount=0;
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>4095) chan[i].freq=4095;
rWrite(freqMap[i][0],chan[i].freq>>8);
rWrite(freqMap[i][1],chan[i].freq&0xff);
if (chan[i].keyOn) {
}
if (chan[i].keyOff) {
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
if (!isMuted[i] && chan[i].active) {
keyState|=(1<<i);
} else {
keyState&=~(1<<i);
}
}
if (regPool[0x40]!=keyState) {
rWrite(0x40,keyState);
regPool[0x40]=keyState;
}
}
int DivPlatformSM8521::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
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;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(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;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
if (!isMuted[c.chan]) {
chan[c.chan].volumeChanged=true;
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
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;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
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*((parent->song.linearPitch==2)?1:8);
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:8);
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+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(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].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 31;
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);
break;
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformSM8521::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].volumeChanged=true;
if (mute) {
rWrite(0x40,regPool[0x40]&~(1<<ch));
} else if (chan[ch].active) {
rWrite(0x40,regPool[0x40]|0x80|(1<<ch));
}
}
void DivPlatformSM8521::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
updateWave(i);
}
}
void* DivPlatformSM8521::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformSM8521::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformSM8521::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformSM8521::getRegisterPool() {
return regPool;
}
int DivPlatformSM8521::getRegisterPoolSize() {
return 256;
}
void DivPlatformSM8521::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,256);
for (int i=0; i<3; i++) {
chan[i]=DivPlatformSM8521::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,15,false);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
sm8521_reset(&sm8521);
rWrite(0x40,0x80); // initialize SGC
}
int DivPlatformSM8521::getOutputCount() {
return 1;
}
bool DivPlatformSM8521::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformSM8521::notifyWaveChange(int wave) {
for (int i=0; i<2; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}
}
void DivPlatformSM8521::notifyInsDeletion(void* ins) {
for (int i=0; i<3; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformSM8521::setFlags(const DivConfig& flags) {
antiClickEnabled=!flags.getBool("noAntiClick",false);
chipClock=11059200;
CHECK_CUSTOM_CLOCK;
rate=chipClock/4/8; // CKIN -> fCLK(/2) -> Function blocks (/2)
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;
}
}
void DivPlatformSM8521::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformSM8521::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformSM8521::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<3; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 6;
}
void DivPlatformSM8521::quit() {
for (int i=0; i<3; i++) {
delete oscBuf[i];
}
}
DivPlatformSM8521::~DivPlatformSM8521() {
}

View file

@ -0,0 +1,86 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 _SM8521_H
#define _SM8521_H
#include "../dispatch.h"
#include <queue>
#include "../waveSynth.h"
#include "sound/sm8521.h"
class DivPlatformSM8521: public DivDispatch {
const unsigned char volMap[3]={0x42,0x44,0x4a};
const unsigned char freqMap[3][2]={{0x46,0x47},{0x48,0x49},{0x4c,0x4d}};
struct Channel: public SharedChannel<signed char> {
int antiClickPeriodCount, antiClickWavePos;
signed short wave;
bool volumeChanged;
DivWaveSynth ws;
Channel():
SharedChannel<signed char>(31),
antiClickPeriodCount(0),
antiClickWavePos(0),
wave(-1),
volumeChanged(false) {}
};
Channel chan[3];
DivDispatchOscBuffer* oscBuf[3];
bool isMuted[3];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
bool antiClickEnabled;
struct sm8521_t sm8521;
unsigned char regPool[256];
void updateWave(int ch);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
bool keyOffAffectsArp(int ch);
void setFlags(const DivConfig& 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();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformSM8521();
};
#endif

Some files were not shown because too many files have changed in this diff Show more