Merge branch 'master' of https://github.com/tildearrow/furnace into x16
This commit is contained in:
commit
3b88786ffb
|
@ -0,0 +1 @@
|
||||||
|
<!-- NOTICE: if you are contributing a new system, please be sure to ask tildearrow first in order to get system IDs allocated! -->
|
|
@ -19,3 +19,6 @@
|
||||||
[submodule "extern/adpcm"]
|
[submodule "extern/adpcm"]
|
||||||
path = extern/adpcm
|
path = extern/adpcm
|
||||||
url = https://github.com/superctr/adpcm
|
url = https://github.com/superctr/adpcm
|
||||||
|
[submodule "extern/Nuked-OPL3"]
|
||||||
|
path = extern/Nuked-OPL3
|
||||||
|
url = https://github.com/nukeykt/Nuked-OPL3.git
|
||||||
|
|
|
@ -226,6 +226,7 @@ extern/adpcm/ymz_codec.c
|
||||||
extern/Nuked-OPN2/ym3438.c
|
extern/Nuked-OPN2/ym3438.c
|
||||||
extern/opm/opm.c
|
extern/opm/opm.c
|
||||||
extern/Nuked-OPLL/opll.c
|
extern/Nuked-OPLL/opll.c
|
||||||
|
extern/Nuked-OPL3/opl3.c
|
||||||
src/engine/platform/sound/sn76496.cpp
|
src/engine/platform/sound/sn76496.cpp
|
||||||
src/engine/platform/sound/ay8910.cpp
|
src/engine/platform/sound/ay8910.cpp
|
||||||
src/engine/platform/sound/saa1099.cpp
|
src/engine/platform/sound/saa1099.cpp
|
||||||
|
@ -298,9 +299,11 @@ src/engine/platform/ym2610b.cpp
|
||||||
src/engine/platform/ym2610bext.cpp
|
src/engine/platform/ym2610bext.cpp
|
||||||
src/engine/platform/ay.cpp
|
src/engine/platform/ay.cpp
|
||||||
src/engine/platform/ay8930.cpp
|
src/engine/platform/ay8930.cpp
|
||||||
|
src/engine/platform/opl.cpp
|
||||||
src/engine/platform/tia.cpp
|
src/engine/platform/tia.cpp
|
||||||
src/engine/platform/saa.cpp
|
src/engine/platform/saa.cpp
|
||||||
src/engine/platform/amiga.cpp
|
src/engine/platform/amiga.cpp
|
||||||
|
src/engine/platform/pcspkr.cpp
|
||||||
src/engine/platform/segapcm.cpp
|
src/engine/platform/segapcm.cpp
|
||||||
src/engine/platform/qsound.cpp
|
src/engine/platform/qsound.cpp
|
||||||
src/engine/platform/vera.cpp
|
src/engine/platform/vera.cpp
|
||||||
|
|
|
@ -29,6 +29,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (.
|
||||||
- accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm)
|
- accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm)
|
||||||
- additional features on top:
|
- additional features on top:
|
||||||
- FM macros!
|
- FM macros!
|
||||||
|
- negative octaves
|
||||||
- arbitrary pitch samples
|
- arbitrary pitch samples
|
||||||
- sample loop points
|
- sample loop points
|
||||||
- SSG envelopes in Neo Geo
|
- SSG envelopes in Neo Geo
|
||||||
|
@ -186,6 +187,10 @@ also provided are two effects:
|
||||||
- `3xxx`: set fine duty.
|
- `3xxx`: set fine duty.
|
||||||
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
|
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
|
||||||
|
|
||||||
|
> how do I use PCM on a PCM-capable system?
|
||||||
|
|
||||||
|
Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect
|
||||||
|
|
||||||
> my song sounds very odd at a certain point
|
> my song sounds very odd at a certain point
|
||||||
|
|
||||||
file a bug report. use the Issues page.
|
file a bug report. use the Issues page.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66
|
|
@ -173,6 +173,8 @@ size | description
|
||||||
| - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels
|
| - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels
|
||||||
| - 0xaa: MSM6295 - 4 channels
|
| - 0xaa: MSM6295 - 4 channels
|
||||||
| - 0xab: MSM6258 - 1 channel
|
| - 0xab: MSM6258 - 1 channel
|
||||||
|
| - 0xac: Commander X16 (VERA) - 17 channels
|
||||||
|
| - 0xb0: Seta/Allumer X1-010 - 16 channels
|
||||||
| - 0xde: YM2610B extended - 19 channels
|
| - 0xde: YM2610B extended - 19 channels
|
||||||
| - 0xe0: QSound - 19 channels
|
| - 0xe0: QSound - 19 channels
|
||||||
| - (compound!) means that the system is composed of two or more chips,
|
| - (compound!) means that the system is composed of two or more chips,
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
|
|
||||||
#define addWrite(a,v) regWrites.push_back(DivRegWrite(a,v));
|
#define addWrite(a,v) regWrites.push_back(DivRegWrite(a,v));
|
||||||
|
|
||||||
|
// HOW TO ADD A NEW COMMAND:
|
||||||
|
// add it to this enum. then see playback.cpp.
|
||||||
|
// there is a const char* cmdName[] array, which contains the command
|
||||||
|
// names as strings for the commands (and other debug stuff).
|
||||||
|
//
|
||||||
|
// if you miss it, the program will crash or misbehave at some point.
|
||||||
enum DivDispatchCmds {
|
enum DivDispatchCmds {
|
||||||
DIV_CMD_NOTE_ON=0,
|
DIV_CMD_NOTE_ON=0,
|
||||||
DIV_CMD_NOTE_OFF,
|
DIV_CMD_NOTE_OFF,
|
||||||
|
@ -310,6 +316,11 @@ class DivDispatch {
|
||||||
*/
|
*/
|
||||||
virtual void notifyInsDeletion(void* ins);
|
virtual void notifyInsDeletion(void* ins);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notify that playback stopped.
|
||||||
|
*/
|
||||||
|
virtual void notifyPlaybackStop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* force-retrigger instruments.
|
* force-retrigger instruments.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -34,9 +34,11 @@
|
||||||
#include "platform/ym2610bext.h"
|
#include "platform/ym2610bext.h"
|
||||||
#include "platform/ay.h"
|
#include "platform/ay.h"
|
||||||
#include "platform/ay8930.h"
|
#include "platform/ay8930.h"
|
||||||
|
#include "platform/opl.h"
|
||||||
#include "platform/tia.h"
|
#include "platform/tia.h"
|
||||||
#include "platform/saa.h"
|
#include "platform/saa.h"
|
||||||
#include "platform/amiga.h"
|
#include "platform/amiga.h"
|
||||||
|
#include "platform/pcspkr.h"
|
||||||
#include "platform/segapcm.h"
|
#include "platform/segapcm.h"
|
||||||
#include "platform/qsound.h"
|
#include "platform/qsound.h"
|
||||||
#include "platform/vera.h"
|
#include "platform/vera.h"
|
||||||
|
@ -210,6 +212,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7);
|
((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7);
|
||||||
((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS);
|
((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS);
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_OPL3:
|
||||||
|
dispatch=new DivPlatformOPL;
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_SAA1099: {
|
case DIV_SYSTEM_SAA1099: {
|
||||||
int saaCore=eng->getConfInt("saaCore",0);
|
int saaCore=eng->getConfInt("saaCore",0);
|
||||||
if (saaCore<0 || saaCore>2) saaCore=0;
|
if (saaCore<0 || saaCore>2) saaCore=0;
|
||||||
|
@ -217,6 +222,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore);
|
((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case DIV_SYSTEM_PCSPKR:
|
||||||
|
dispatch=new DivPlatformPCSpeaker;
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_LYNX:
|
case DIV_SYSTEM_LYNX:
|
||||||
dispatch=new DivPlatformLynx;
|
dispatch=new DivPlatformLynx;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -828,6 +828,9 @@ void DivEngine::stop() {
|
||||||
sPreview.sample=-1;
|
sPreview.sample=-1;
|
||||||
sPreview.wave=-1;
|
sPreview.wave=-1;
|
||||||
sPreview.pos=0;
|
sPreview.pos=0;
|
||||||
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
|
disCont[i].dispatch->notifyPlaybackStop();
|
||||||
|
}
|
||||||
isBusy.unlock();
|
isBusy.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1112,6 +1112,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
logW("%d: sample depth is wrong! (%d)\n",i,sample->depth);
|
logW("%d: sample depth is wrong! (%d)\n",i,sample->depth);
|
||||||
sample->depth=16;
|
sample->depth=16;
|
||||||
}
|
}
|
||||||
|
sample->samples=(double)sample->samples/samplePitches[pitch];
|
||||||
sample->init(sample->samples);
|
sample->init(sample->samples);
|
||||||
|
|
||||||
unsigned int k=0;
|
unsigned int k=0;
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
#include "dataErrors.h"
|
#include "dataErrors.h"
|
||||||
#include "../ta-utils.h"
|
#include "../ta-utils.h"
|
||||||
|
|
||||||
|
// NOTICE!
|
||||||
|
// before adding new instrument types to this struct, please ask me first.
|
||||||
|
// absolutely zero support granted to conflicting formats.
|
||||||
enum DivInstrumentType {
|
enum DivInstrumentType {
|
||||||
DIV_INS_STD=0,
|
DIV_INS_STD=0,
|
||||||
DIV_INS_FM=1,
|
DIV_INS_FM=1,
|
||||||
|
|
|
@ -97,6 +97,10 @@ void DivDispatch::notifyInsDeletion(void* ins) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivDispatch::notifyPlaybackStop() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void DivDispatch::forceIns() {
|
void DivDispatch::forceIns() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
||||||
} else {
|
} else {
|
||||||
chan[i].sample=-1;
|
chan[i].sample=-1;
|
||||||
}
|
}
|
||||||
if (chan[i].freq<124) {
|
/*if (chan[i].freq<124) {
|
||||||
if (++chan[i].busClock>=512) {
|
if (++chan[i].busClock>=512) {
|
||||||
unsigned int rAmount=(124-chan[i].freq)*2;
|
unsigned int rAmount=(124-chan[i].freq)*2;
|
||||||
if (chan[i].audPos>=rAmount) {
|
if (chan[i].audPos>=rAmount) {
|
||||||
|
@ -92,7 +92,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
||||||
}
|
}
|
||||||
chan[i].busClock=0;
|
chan[i].busClock=0;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
chan[i].audSub+=MAX(114,chan[i].freq);
|
chan[i].audSub+=MAX(114,chan[i].freq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -495,8 +495,8 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
||||||
chan[c.chan].ins=c.value;
|
chan[c.chan].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
chan[c.chan].chVolL=((c.value>>4)==1);
|
chan[c.chan].chVolL=((c.value>>4)>0);
|
||||||
chan[c.chan].chVolR=((c.value&15)==1);
|
chan[c.chan].chVolR=((c.value&15)>0);
|
||||||
if (isMuted[c.chan]) {
|
if (isMuted[c.chan]) {
|
||||||
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
|
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -491,6 +491,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
|
||||||
case 8:
|
case 8:
|
||||||
chipClock=COLOR_PAL*3.0/16.0;
|
chipClock=COLOR_PAL*3.0/16.0;
|
||||||
break;
|
break;
|
||||||
|
case 9:
|
||||||
|
chipClock=COLOR_PAL/4.0;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
chipClock=2097152;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
chipClock=COLOR_NTSC/2.0;
|
chipClock=COLOR_NTSC/2.0;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -552,6 +552,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) {
|
||||||
case 8:
|
case 8:
|
||||||
chipClock=COLOR_PAL*3.0/16.0;
|
chipClock=COLOR_PAL*3.0/16.0;
|
||||||
break;
|
break;
|
||||||
|
case 9:
|
||||||
|
chipClock=COLOR_PAL/4.0;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
chipClock=2097152;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
chipClock=COLOR_NTSC/2.0;
|
chipClock=COLOR_NTSC/2.0;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -340,6 +340,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
lastPan&=~(0x11<<c.chan);
|
lastPan&=~(0x11<<c.chan);
|
||||||
if (c.value==0) c.value=0x11;
|
if (c.value==0) c.value=0x11;
|
||||||
|
c.value=((c.value&15)>0)|(((c.value>>4)>0)<<4);
|
||||||
lastPan|=c.value<<c.chan;
|
lastPan|=c.value<<c.chan;
|
||||||
rWrite(0x25,procMute());
|
rWrite(0x25,procMute());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -590,16 +590,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
||||||
chan[c.chan].ins=c.value;
|
chan[c.chan].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
|
||||||
chan[c.chan].pan=1;
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
chan[c.chan].pan=2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
chan[c.chan].pan=3;
|
chan[c.chan].pan=3;
|
||||||
break;
|
} else {
|
||||||
|
chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||||
}
|
}
|
||||||
rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -106,16 +106,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
opChan[ch].ins=c.value;
|
opChan[ch].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
|
||||||
opChan[ch].pan=1;
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
opChan[ch].pan=2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
opChan[ch].pan=3;
|
opChan[ch].pan=3;
|
||||||
break;
|
} else {
|
||||||
|
opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||||
}
|
}
|
||||||
// TODO: ???
|
// TODO: ???
|
||||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
|
|
|
@ -0,0 +1,845 @@
|
||||||
|
/**
|
||||||
|
* Furnace Tracker - multi-system chiptune tracker
|
||||||
|
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "opl.h"
|
||||||
|
#include "../engine.h"
|
||||||
|
#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 CHIP_FREQBASE 4720272
|
||||||
|
|
||||||
|
// N = invalid
|
||||||
|
#define N 255
|
||||||
|
|
||||||
|
const unsigned char slotsOPL2[4][20]={
|
||||||
|
{0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1
|
||||||
|
{3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2
|
||||||
|
{N, N, N, N, N, N, N, N, N},
|
||||||
|
{N, N, N, N, N, N, N, N, N}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned char slotsOPL2Drums[4][20]={
|
||||||
|
{0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1
|
||||||
|
{3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2
|
||||||
|
{N, N, N, N, N, N, N, N, N, N, N},
|
||||||
|
{N, N, N, N, N, N, N, N, N, N, N}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned char chanMapOPL2[20]={
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned char slotsOPL3[4][20]={
|
||||||
|
{0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1
|
||||||
|
{3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2
|
||||||
|
{6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3
|
||||||
|
{9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned char slotsOPL3Drums[4][20]={
|
||||||
|
{0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1
|
||||||
|
{3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2
|
||||||
|
{6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3
|
||||||
|
{9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned char chanMapOPL3[20]={
|
||||||
|
0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef N
|
||||||
|
|
||||||
|
const char* DivPlatformOPL::getEffectName(unsigned char effect) {
|
||||||
|
switch (effect) {
|
||||||
|
case 0x10:
|
||||||
|
return "10xy: Setup LFO (x: enable; y: speed)";
|
||||||
|
break;
|
||||||
|
case 0x11:
|
||||||
|
return "11xx: Set feedback (0 to 7)";
|
||||||
|
break;
|
||||||
|
case 0x12:
|
||||||
|
return "12xx: Set level of operator 1 (0 highest, 3F lowest)";
|
||||||
|
break;
|
||||||
|
case 0x13:
|
||||||
|
return "13xx: Set level of operator 2 (0 highest, 3F lowest)";
|
||||||
|
break;
|
||||||
|
case 0x14:
|
||||||
|
return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)";
|
||||||
|
break;
|
||||||
|
case 0x15:
|
||||||
|
return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)";
|
||||||
|
break;
|
||||||
|
case 0x16:
|
||||||
|
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||||
|
break;
|
||||||
|
case 0x17:
|
||||||
|
return "17xx: Enable channel 6 DAC";
|
||||||
|
break;
|
||||||
|
case 0x18:
|
||||||
|
return "18xx: Toggle extended channel 3 mode";
|
||||||
|
break;
|
||||||
|
case 0x19:
|
||||||
|
return "19xx: Set attack of all operators (0 to F)";
|
||||||
|
break;
|
||||||
|
case 0x1a:
|
||||||
|
return "1Axx: Set attack of operator 1 (0 to F)";
|
||||||
|
break;
|
||||||
|
case 0x1b:
|
||||||
|
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||||
|
break;
|
||||||
|
case 0x1c:
|
||||||
|
return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)";
|
||||||
|
break;
|
||||||
|
case 0x1d:
|
||||||
|
return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)";
|
||||||
|
break;
|
||||||
|
case 0x20:
|
||||||
|
return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
static short o[2];
|
||||||
|
static int os[2];
|
||||||
|
|
||||||
|
for (size_t h=start; h<start+len; h++) {
|
||||||
|
os[0]=0; os[1]=0;
|
||||||
|
if (!writes.empty() && --delay<0) {
|
||||||
|
delay=12;
|
||||||
|
QueuedWrite& w=writes.front();
|
||||||
|
if (w.addrOrVal) {
|
||||||
|
OPL3_WriteReg(&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();
|
||||||
|
} else {
|
||||||
|
lastBusy++;
|
||||||
|
//printf("busycounter: %d\n",lastBusy);
|
||||||
|
OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr);
|
||||||
|
w.addrOrVal=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||||
|
|
||||||
|
if (os[0]<-32768) os[0]=-32768;
|
||||||
|
if (os[0]>32767) os[0]=32767;
|
||||||
|
|
||||||
|
if (os[1]<-32768) os[1]=-32768;
|
||||||
|
if (os[1]>32767) os[1]=32767;
|
||||||
|
|
||||||
|
bufL[h]=os[0];
|
||||||
|
bufR[h]=os[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
//if (useYMFM) {
|
||||||
|
// acquire_ymfm(bufL,bufR,start,len);
|
||||||
|
//} else {
|
||||||
|
acquire_nuked(bufL,bufR,start,len);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::tick() {
|
||||||
|
/*
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
if (i==2 && extMode) continue;
|
||||||
|
chan[i].std.next();
|
||||||
|
|
||||||
|
if (chan[i].std.hadVol) {
|
||||||
|
chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127;
|
||||||
|
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]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chan[i].std.hadArp) {
|
||||||
|
if (!chan[i].inPorta) {
|
||||||
|
if (chan[i].std.arpMode) {
|
||||||
|
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp);
|
||||||
|
} else {
|
||||||
|
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
} else {
|
||||||
|
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||||
|
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chan[i].std.hadAlg) {
|
||||||
|
chan[i].state.alg=chan[i].std.alg;
|
||||||
|
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||||
|
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]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].std.hadFb) {
|
||||||
|
chan[i].state.fb=chan[i].std.fb;
|
||||||
|
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||||
|
}
|
||||||
|
if (chan[i].std.hadFms) {
|
||||||
|
chan[i].state.fms=chan[i].std.fms;
|
||||||
|
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
|
}
|
||||||
|
if (chan[i].std.hadAms) {
|
||||||
|
chan[i].state.ams=chan[i].std.ams;
|
||||||
|
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
|
}
|
||||||
|
for (int j=0; j<4; j++) {
|
||||||
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
DivMacroInt::IntOp& m=chan[i].std.op[j];
|
||||||
|
if (m.hadAm) {
|
||||||
|
op.am=m.am;
|
||||||
|
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||||
|
}
|
||||||
|
if (m.hadAr) {
|
||||||
|
op.ar=m.ar;
|
||||||
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||||
|
}
|
||||||
|
if (m.hadDr) {
|
||||||
|
op.dr=m.dr;
|
||||||
|
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||||
|
}
|
||||||
|
if (m.hadMult) {
|
||||||
|
op.mult=m.mult;
|
||||||
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||||
|
}
|
||||||
|
if (m.hadRr) {
|
||||||
|
op.rr=m.rr;
|
||||||
|
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||||
|
}
|
||||||
|
if (m.hadSl) {
|
||||||
|
op.sl=m.sl;
|
||||||
|
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||||
|
}
|
||||||
|
if (m.hadTl) {
|
||||||
|
op.tl=127-m.tl;
|
||||||
|
if (isMuted[i]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m.hadRs) {
|
||||||
|
op.rs=m.rs;
|
||||||
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||||
|
}
|
||||||
|
if (m.hadDt) {
|
||||||
|
op.dt=m.dt;
|
||||||
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||||
|
}
|
||||||
|
if (m.hadD2r) {
|
||||||
|
op.d2r=m.d2r;
|
||||||
|
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||||
|
}
|
||||||
|
if (m.hadSsg) {
|
||||||
|
op.ssgEnv=m.ssg;
|
||||||
|
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chan[i].keyOn || chan[i].keyOff) {
|
||||||
|
immWrite(0x28,0x00|konOffs[i]);
|
||||||
|
chan[i].keyOff=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (int i=0; i<512; i++) {
|
||||||
|
if (pendingWrites[i]!=oldWrites[i]) {
|
||||||
|
immWrite(i,pendingWrites[i]&0xff);
|
||||||
|
oldWrites[i]=pendingWrites[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
if (chan[i].freqChanged) {
|
||||||
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
||||||
|
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||||
|
int freqt=toFreq(chan[i].freq);
|
||||||
|
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
||||||
|
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
||||||
|
if (chan[i].furnaceDac && dacMode) {
|
||||||
|
double off=1.0;
|
||||||
|
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||||
|
DivSample* s=parent->getSample(dacSample);
|
||||||
|
if (s->centerRate<1) {
|
||||||
|
off=1.0;
|
||||||
|
} else {
|
||||||
|
off=8363.0/(double)s->centerRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq);
|
||||||
|
if (dacRate<1) dacRate=1;
|
||||||
|
if (dumpWrites) addWrite(0xffff0001,1280000/dacRate);
|
||||||
|
}
|
||||||
|
chan[i].freqChanged=false;
|
||||||
|
}
|
||||||
|
if (chan[i].keyOn) {
|
||||||
|
immWrite(0x28,0xf0|konOffs[i]);
|
||||||
|
chan[i].keyOn=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformOPL::octave(int freq) {
|
||||||
|
if (freq>=82432) {
|
||||||
|
return 128;
|
||||||
|
} else if (freq>=41216) {
|
||||||
|
return 64;
|
||||||
|
} else if (freq>=20608) {
|
||||||
|
return 32;
|
||||||
|
} else if (freq>=10304) {
|
||||||
|
return 16;
|
||||||
|
} else if (freq>=5152) {
|
||||||
|
return 8;
|
||||||
|
} else if (freq>=2576) {
|
||||||
|
return 4;
|
||||||
|
} else if (freq>=1288) {
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformOPL::toFreq(int freq) {
|
||||||
|
if (freq>=82432) {
|
||||||
|
return 0x3800|((freq>>7)&0x7ff);
|
||||||
|
} else if (freq>=41216) {
|
||||||
|
return 0x3000|((freq>>6)&0x7ff);
|
||||||
|
} else if (freq>=20608) {
|
||||||
|
return 0x2800|((freq>>5)&0x7ff);
|
||||||
|
} else if (freq>=10304) {
|
||||||
|
return 0x2000|((freq>>4)&0x7ff);
|
||||||
|
} else if (freq>=5152) {
|
||||||
|
return 0x1800|((freq>>3)&0x7ff);
|
||||||
|
} else if (freq>=2576) {
|
||||||
|
return 0x1000|((freq>>2)&0x7ff);
|
||||||
|
} else if (freq>=1288) {
|
||||||
|
return 0x800|((freq>>1)&0x7ff);
|
||||||
|
} else {
|
||||||
|
return freq&0x7ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
||||||
|
isMuted[ch]=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]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
|
if (isOutput[chan[ch].state.alg][j]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4));
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformOPL::dispatch(DivCommand c) {
|
||||||
|
switch (c.cmd) {
|
||||||
|
case DIV_CMD_NOTE_ON: {
|
||||||
|
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||||
|
|
||||||
|
if (chan[c.chan].insChanged) {
|
||||||
|
chan[c.chan].state=ins->fm;
|
||||||
|
}
|
||||||
|
|
||||||
|
chan[c.chan].std.init(ins);
|
||||||
|
if (!chan[c.chan].std.willVol) {
|
||||||
|
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 (isOutput[chan[c.chan].state.alg][i]) {
|
||||||
|
if (!chan[c.chan].active || chan[c.chan].insChanged) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((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,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
chan[c.chan].insChanged=false;
|
||||||
|
|
||||||
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
|
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||||
|
chan[c.chan].note=c.value;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
}
|
||||||
|
chan[c.chan].keyOn=true;
|
||||||
|
chan[c.chan].active=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_NOTE_OFF:
|
||||||
|
if (c.chan==5) {
|
||||||
|
dacSample=-1;
|
||||||
|
if (dumpWrites) addWrite(0xffff0002,0);
|
||||||
|
}
|
||||||
|
chan[c.chan].keyOff=true;
|
||||||
|
chan[c.chan].keyOn=false;
|
||||||
|
chan[c.chan].active=false;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NOTE_OFF_ENV:
|
||||||
|
if (c.chan==5) {
|
||||||
|
dacSample=-1;
|
||||||
|
if (dumpWrites) addWrite(0xffff0002,0);
|
||||||
|
}
|
||||||
|
chan[c.chan].keyOff=true;
|
||||||
|
chan[c.chan].keyOn=false;
|
||||||
|
chan[c.chan].active=false;
|
||||||
|
chan[c.chan].std.release();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_ENV_RELEASE:
|
||||||
|
chan[c.chan].std.release();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_VOLUME: {
|
||||||
|
chan[c.chan].vol=c.value;
|
||||||
|
if (!chan[c.chan].std.hasVol) {
|
||||||
|
chan[c.chan].outVol=c.value;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
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 (isOutput[chan[c.chan].state.alg][i]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_GET_VOLUME: {
|
||||||
|
return chan[c.chan].vol;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_INSTRUMENT:
|
||||||
|
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||||
|
chan[c.chan].insChanged=true;
|
||||||
|
}
|
||||||
|
chan[c.chan].ins=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PANNING: {
|
||||||
|
switch (c.value) {
|
||||||
|
case 0x01:
|
||||||
|
chan[c.chan].pan=1;
|
||||||
|
break;
|
||||||
|
case 0x10:
|
||||||
|
chan[c.chan].pan=2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
chan[c.chan].pan=3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_PITCH: {
|
||||||
|
chan[c.chan].pitch=c.value;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
|
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||||
|
int newFreq;
|
||||||
|
bool return2=false;
|
||||||
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
|
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
||||||
|
if (newFreq>=destFreq) {
|
||||||
|
newFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
||||||
|
if (newFreq<=destFreq) {
|
||||||
|
newFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!chan[c.chan].portaPause) {
|
||||||
|
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
||||||
|
chan[c.chan].portaPause=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[c.chan].baseFreq=newFreq;
|
||||||
|
chan[c.chan].portaPause=false;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
if (return2) {
|
||||||
|
chan[c.chan].inPorta=false;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_SAMPLE_MODE: {
|
||||||
|
dacMode=c.value;
|
||||||
|
rWrite(0x2b,c.value<<7);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
|
sampleBank=c.value;
|
||||||
|
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||||
|
sampleBank=parent->song.sample.size()/12;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DIV_CMD_LEGATO: {
|
||||||
|
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||||
|
chan[c.chan].note=c.value;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_FM_LFO: {
|
||||||
|
lfoValue=(c.value&7)|((c.value>>4)<<3);
|
||||||
|
rWrite(0x22,lfoValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_FM_FB: {
|
||||||
|
chan[c.chan].state.fb=c.value&7;
|
||||||
|
//rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_FM_MULT: {
|
||||||
|
/*
|
||||||
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||||
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||||
|
op.mult=c.value2&15;
|
||||||
|
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_FM_TL: {
|
||||||
|
/*
|
||||||
|
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]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
|
if (isOutput[chan[c.chan].state.alg][c.value]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_FM_AR: {
|
||||||
|
/*
|
||||||
|
if (c.value<0) {
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||||
|
op.ar=c.value2&31;
|
||||||
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||||
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||||
|
op.ar=c.value2&31;
|
||||||
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||||
|
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_ALWAYS_SET_VOLUME:
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_GET_VOLMAX:
|
||||||
|
return 127;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PRE_PORTA:
|
||||||
|
chan[c.chan].inPorta=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PRE_NOTE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//printf("WARNING: unimplemented command %d\n",c.cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::forceIns() {
|
||||||
|
/*
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
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]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127);
|
||||||
|
} else {
|
||||||
|
if (isOutput[chan[i].state.alg][j]) {
|
||||||
|
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||||
|
} else {
|
||||||
|
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||||
|
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[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 (dacMode) {
|
||||||
|
rWrite(0x2b,0x80);
|
||||||
|
}
|
||||||
|
immWrite(0x22,lfoValue);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::toggleRegisterDump(bool enable) {
|
||||||
|
DivDispatch::toggleRegisterDump(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* DivPlatformOPL::getChanState(int ch) {
|
||||||
|
return &chan[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char* DivPlatformOPL::getRegisterPool() {
|
||||||
|
return regPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformOPL::getRegisterPoolSize() {
|
||||||
|
return 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::reset() {
|
||||||
|
while (!writes.empty()) writes.pop();
|
||||||
|
memset(regPool,0,512);
|
||||||
|
/*
|
||||||
|
if (useYMFM) {
|
||||||
|
fm_ymfm->reset();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
OPL3_Reset(&fm,rate);
|
||||||
|
if (dumpWrites) {
|
||||||
|
addWrite(0xffffffff,0);
|
||||||
|
}
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
chan[i]=DivPlatformOPL::Channel();
|
||||||
|
chan[i].vol=0x3f;
|
||||||
|
chan[i].outVol=0x3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i<512; i++) {
|
||||||
|
oldWrites[i]=-1;
|
||||||
|
pendingWrites[i]=-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastBusy=60;
|
||||||
|
dacMode=0;
|
||||||
|
dacPeriod=0;
|
||||||
|
dacPos=0;
|
||||||
|
dacRate=0;
|
||||||
|
dacSample=-1;
|
||||||
|
sampleBank=0;
|
||||||
|
lfoValue=8;
|
||||||
|
|
||||||
|
extMode=false;
|
||||||
|
|
||||||
|
// LFO
|
||||||
|
immWrite(0x22,lfoValue);
|
||||||
|
|
||||||
|
delay=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformOPL::isStereo() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
||||||
|
return (ch>5);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformOPL::keyOffAffectsPorta(int ch) {
|
||||||
|
return (ch>5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::notifyInsChange(int ins) {
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
if (chan[i].ins==ins) {
|
||||||
|
chan[i].insChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::notifyInsDeletion(void* ins) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::poke(unsigned int addr, unsigned short val) {
|
||||||
|
immWrite(addr,val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
|
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformOPL::getPortaFloor(int ch) {
|
||||||
|
return (ch>5)?12:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::setYMFM(bool use) {
|
||||||
|
useYMFM=use;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::setOPLType(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case 1: case 2:
|
||||||
|
slotsNonDrums=(const unsigned char**)slotsOPL2;
|
||||||
|
slotsDrums=(const unsigned char**)slotsOPL2Drums;
|
||||||
|
chanMap=chanMapOPL2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
slotsNonDrums=(const unsigned char**)slotsOPL3;
|
||||||
|
slotsDrums=(const unsigned char**)slotsOPL3Drums;
|
||||||
|
chanMap=chanMapOPL3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
oplType=type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::setFlags(unsigned int flags) {
|
||||||
|
/*
|
||||||
|
if (flags==3) {
|
||||||
|
chipClock=COLOR_NTSC*12.0/7.0;
|
||||||
|
} else if (flags==2) {
|
||||||
|
chipClock=8000000.0;
|
||||||
|
} else if (flags==1) {
|
||||||
|
chipClock=COLOR_PAL*12.0/7.0;
|
||||||
|
} else {
|
||||||
|
chipClock=COLOR_NTSC*15.0/7.0;
|
||||||
|
}
|
||||||
|
ladder=flags&0x80000000;
|
||||||
|
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);
|
||||||
|
if (useYMFM) {
|
||||||
|
if (fm_ymfm!=NULL) delete fm_ymfm;
|
||||||
|
if (ladder) {
|
||||||
|
fm_ymfm=new ymfm::ym2612(iface);
|
||||||
|
} else {
|
||||||
|
fm_ymfm=new ymfm::ym3438(iface);
|
||||||
|
}
|
||||||
|
rate=chipClock/144;
|
||||||
|
} else {
|
||||||
|
rate=chipClock/36;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
chipClock=COLOR_NTSC*4.0;
|
||||||
|
rate=chipClock/32;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
|
parent=p;
|
||||||
|
dumpWrites=false;
|
||||||
|
ladder=false;
|
||||||
|
skipRegisterWrites=false;
|
||||||
|
for (int i=0; i<20; i++) {
|
||||||
|
isMuted[i]=false;
|
||||||
|
}
|
||||||
|
setFlags(flags);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformOPL::quit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
DivPlatformOPL::~DivPlatformOPL() {
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/**
|
||||||
|
* Furnace Tracker - multi-system chiptune tracker
|
||||||
|
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _OPL_H
|
||||||
|
#define _OPL_H
|
||||||
|
#include "../dispatch.h"
|
||||||
|
#include "../macroInt.h"
|
||||||
|
#include <queue>
|
||||||
|
#include "../../../extern/Nuked-OPL3/opl3.h"
|
||||||
|
|
||||||
|
class DivPlatformOPL: public DivDispatch {
|
||||||
|
protected:
|
||||||
|
struct Channel {
|
||||||
|
DivInstrumentFM state;
|
||||||
|
DivMacroInt std;
|
||||||
|
unsigned char freqH, freqL;
|
||||||
|
int freq, baseFreq, pitch, note;
|
||||||
|
unsigned char ins;
|
||||||
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta;
|
||||||
|
int vol, outVol;
|
||||||
|
unsigned char pan;
|
||||||
|
Channel():
|
||||||
|
freqH(0),
|
||||||
|
freqL(0),
|
||||||
|
freq(0),
|
||||||
|
baseFreq(0),
|
||||||
|
pitch(0),
|
||||||
|
note(0),
|
||||||
|
ins(-1),
|
||||||
|
active(false),
|
||||||
|
insChanged(true),
|
||||||
|
freqChanged(false),
|
||||||
|
keyOn(false),
|
||||||
|
keyOff(false),
|
||||||
|
portaPause(false),
|
||||||
|
furnaceDac(false),
|
||||||
|
inPorta(false),
|
||||||
|
vol(0),
|
||||||
|
pan(3) {}
|
||||||
|
};
|
||||||
|
Channel chan[20];
|
||||||
|
bool isMuted[20];
|
||||||
|
struct QueuedWrite {
|
||||||
|
unsigned short addr;
|
||||||
|
unsigned char val;
|
||||||
|
bool addrOrVal;
|
||||||
|
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||||
|
};
|
||||||
|
std::queue<QueuedWrite> writes;
|
||||||
|
opl3_chip fm;
|
||||||
|
const unsigned char** slotsNonDrums;
|
||||||
|
const unsigned char** slotsDrums;
|
||||||
|
const unsigned char** slots;
|
||||||
|
const unsigned char* chanMap;
|
||||||
|
int delay, oplType;
|
||||||
|
unsigned char lastBusy;
|
||||||
|
|
||||||
|
unsigned char regPool[512];
|
||||||
|
|
||||||
|
bool dacMode;
|
||||||
|
int dacPeriod;
|
||||||
|
int dacRate;
|
||||||
|
unsigned int dacPos;
|
||||||
|
int dacSample;
|
||||||
|
unsigned char sampleBank;
|
||||||
|
unsigned char lfoValue;
|
||||||
|
|
||||||
|
bool extMode, useYMFM;
|
||||||
|
bool ladder;
|
||||||
|
|
||||||
|
short oldWrites[512];
|
||||||
|
short pendingWrites[512];
|
||||||
|
|
||||||
|
int octave(int freq);
|
||||||
|
int toFreq(int freq);
|
||||||
|
|
||||||
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
||||||
|
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
//void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
int dispatch(DivCommand c);
|
||||||
|
void* getChanState(int chan);
|
||||||
|
unsigned char* getRegisterPool();
|
||||||
|
int getRegisterPoolSize();
|
||||||
|
void reset();
|
||||||
|
void forceIns();
|
||||||
|
void tick();
|
||||||
|
void muteChannel(int ch, bool mute);
|
||||||
|
bool isStereo();
|
||||||
|
void setYMFM(bool use);
|
||||||
|
void setOPLType(int type);
|
||||||
|
bool keyOffAffectsArp(int ch);
|
||||||
|
bool keyOffAffectsPorta(int ch);
|
||||||
|
void toggleRegisterDump(bool enable);
|
||||||
|
void setFlags(unsigned int flags);
|
||||||
|
void notifyInsChange(int ins);
|
||||||
|
void notifyInsDeletion(void* ins);
|
||||||
|
int getPortaFloor(int ch);
|
||||||
|
void poke(unsigned int addr, unsigned short val);
|
||||||
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
|
const char* getEffectName(unsigned char effect);
|
||||||
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
|
void quit();
|
||||||
|
~DivPlatformOPL();
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -29,17 +29,14 @@
|
||||||
|
|
||||||
const char* DivPlatformOPLL::getEffectName(unsigned char effect) {
|
const char* DivPlatformOPLL::getEffectName(unsigned char effect) {
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case 0x10:
|
|
||||||
return "10xy: Setup LFO (x: enable; y: speed)";
|
|
||||||
break;
|
|
||||||
case 0x11:
|
case 0x11:
|
||||||
return "11xx: Set feedback (0 to 7)";
|
return "11xx: Set feedback (0 to 7)";
|
||||||
break;
|
break;
|
||||||
case 0x12:
|
case 0x12:
|
||||||
return "12xx: Set level of operator 1 (0 highest, 7F lowest)";
|
return "12xx: Set level of operator 1 (0 highest, 3F lowest)";
|
||||||
break;
|
break;
|
||||||
case 0x13:
|
case 0x13:
|
||||||
return "13xx: Set level of operator 2 (0 highest, 7F lowest)";
|
return "13xx: Set level of operator 2 (0 highest, F lowest)";
|
||||||
break;
|
break;
|
||||||
case 0x16:
|
case 0x16:
|
||||||
return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)";
|
return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)";
|
||||||
|
@ -50,13 +47,13 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x19:
|
case 0x19:
|
||||||
return "19xx: Set attack of all operators (0 to 1F)";
|
return "19xx: Set attack of all operators (0 to F)";
|
||||||
break;
|
break;
|
||||||
case 0x1a:
|
case 0x1a:
|
||||||
return "1Axx: Set attack of operator 1 (0 to 1F)";
|
return "1Axx: Set attack of operator 1 (0 to F)";
|
||||||
break;
|
break;
|
||||||
case 0x1b:
|
case 0x1b:
|
||||||
return "1Bxx: Set attack of operator 2 (0 to 1F)";
|
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -0,0 +1,413 @@
|
||||||
|
/**
|
||||||
|
* Furnace Tracker - multi-system chiptune tracker
|
||||||
|
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pcspkr.h"
|
||||||
|
#include "../engine.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <linux/kd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PCSPKR_DIVIDER 4
|
||||||
|
#define CHIP_DIVIDER 1
|
||||||
|
|
||||||
|
const char* regCheatSheetPCSpeaker[]={
|
||||||
|
"Period", "0",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
const char** DivPlatformPCSpeaker::getRegisterSheet() {
|
||||||
|
return regCheatSheetPCSpeaker;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float cut=0.05;
|
||||||
|
const float reso=0.06;
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
for (size_t i=start; i<start+len; i++) {
|
||||||
|
if (on) {
|
||||||
|
pos-=PCSPKR_DIVIDER;
|
||||||
|
while (pos<0) {
|
||||||
|
if (freq<1) {
|
||||||
|
pos=1;
|
||||||
|
} else {
|
||||||
|
pos+=freq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bufL[i]=(pos>(freq>>1) && !isMuted[0])?32767:0;
|
||||||
|
} else {
|
||||||
|
bufL[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
for (size_t i=start; i<start+len; i++) {
|
||||||
|
if (on) {
|
||||||
|
pos-=PCSPKR_DIVIDER;
|
||||||
|
while (pos<0) {
|
||||||
|
if (freq<1) {
|
||||||
|
pos=1;
|
||||||
|
} else {
|
||||||
|
pos+=freq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float next=(pos>((freq+16)>>1) && !isMuted[0])?1:0;
|
||||||
|
low+=0.04*band;
|
||||||
|
band+=0.04*(next-low-band);
|
||||||
|
float out=(low+band)*0.75;
|
||||||
|
if (out>1.0) out=1.0;
|
||||||
|
if (out<-1.0) out=-1.0;
|
||||||
|
bufL[i]=out*32767;
|
||||||
|
} else {
|
||||||
|
bufL[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
for (size_t i=start; i<start+len; i++) {
|
||||||
|
if (on) {
|
||||||
|
pos-=PCSPKR_DIVIDER;
|
||||||
|
while (pos<0) {
|
||||||
|
if (freq<1) {
|
||||||
|
pos=1;
|
||||||
|
} else {
|
||||||
|
pos+=freq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float next=(pos>((freq+64)>>1) && !isMuted[0])?1:0;
|
||||||
|
low+=cut*band;
|
||||||
|
band+=cut*(next-low-(reso*band));
|
||||||
|
float out=band*0.15-(next-low)*0.06;
|
||||||
|
if (out>1.0) out=1.0;
|
||||||
|
if (out<-1.0) out=-1.0;
|
||||||
|
bufL[i]=out*32767;
|
||||||
|
} else {
|
||||||
|
bufL[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::beepFreq(int freq) {
|
||||||
|
#ifdef __linux__
|
||||||
|
static struct input_event ie;
|
||||||
|
if (beepFD>=0) {
|
||||||
|
gettimeofday(&ie.time,NULL);
|
||||||
|
ie.type=EV_SND;
|
||||||
|
ie.code=SND_TONE;
|
||||||
|
if (freq>0) {
|
||||||
|
ie.value=chipClock/freq;
|
||||||
|
} else {
|
||||||
|
ie.value=0;
|
||||||
|
}
|
||||||
|
if (write(beepFD,&ie,sizeof(struct input_event))<0) {
|
||||||
|
perror("error while writing frequency!");
|
||||||
|
} else {
|
||||||
|
//printf("writing freq: %d\n",freq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
if (lastOn!=on || lastFreq!=freq) {
|
||||||
|
lastOn=on;
|
||||||
|
lastFreq=freq;
|
||||||
|
beepFreq((on && !isMuted[0])?freq:0);
|
||||||
|
}
|
||||||
|
for (size_t i=start; i<start+len; i++) {
|
||||||
|
bufL[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
switch (speakerType) {
|
||||||
|
case 0:
|
||||||
|
acquire_unfilt(bufL,bufR,start,len);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
acquire_cone(bufL,bufR,start,len);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
acquire_piezo(bufL,bufR,start,len);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
acquire_real(bufL,bufR,start,len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::tick() {
|
||||||
|
for (int i=0; i<1; i++) {
|
||||||
|
chan[i].std.next();
|
||||||
|
if (chan[i].std.hadVol) {
|
||||||
|
// ok, why are the volumes like that?
|
||||||
|
chan[i].outVol=chan[i].vol;
|
||||||
|
}
|
||||||
|
if (chan[i].std.hadArp) {
|
||||||
|
if (!chan[i].inPorta) {
|
||||||
|
if (chan[i].std.arpMode) {
|
||||||
|
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp);
|
||||||
|
} else {
|
||||||
|
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
} else {
|
||||||
|
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||||
|
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
|
||||||
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
|
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||||
|
if (chan[i].keyOn) {
|
||||||
|
on=true;
|
||||||
|
}
|
||||||
|
if (chan[i].keyOff) {
|
||||||
|
on=false;
|
||||||
|
}
|
||||||
|
freq=chan[i].freq;
|
||||||
|
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||||
|
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||||
|
chan[i].freqChanged=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformPCSpeaker::dispatch(DivCommand c) {
|
||||||
|
switch (c.cmd) {
|
||||||
|
case DIV_CMD_NOTE_ON:
|
||||||
|
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].std.init(parent->getIns(chan[c.chan].ins));
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NOTE_OFF:
|
||||||
|
chan[c.chan].active=false;
|
||||||
|
chan[c.chan].keyOff=true;
|
||||||
|
chan[c.chan].std.init(NULL);
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NOTE_OFF_ENV:
|
||||||
|
case DIV_CMD_ENV_RELEASE:
|
||||||
|
chan[c.chan].std.release();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_INSTRUMENT:
|
||||||
|
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||||
|
chan[c.chan].ins=c.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DIV_CMD_VOLUME:
|
||||||
|
if (chan[c.chan].vol!=c.value) {
|
||||||
|
chan[c.chan].vol=c.value;
|
||||||
|
if (!chan[c.chan].std.hasVol) {
|
||||||
|
chan[c.chan].outVol=c.value;
|
||||||
|
}
|
||||||
|
if (chan[c.chan].active) {
|
||||||
|
on=chan[c.chan].vol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DIV_CMD_GET_VOLUME:
|
||||||
|
return chan[c.chan].vol;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PITCH:
|
||||||
|
chan[c.chan].pitch=c.value;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
|
int destFreq=NOTE_PERIODIC(c.value2);
|
||||||
|
bool return2=false;
|
||||||
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
|
chan[c.chan].baseFreq+=c.value;
|
||||||
|
if (chan[c.chan].baseFreq>=destFreq) {
|
||||||
|
chan[c.chan].baseFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chan[c.chan].baseFreq-=c.value;
|
||||||
|
if (chan[c.chan].baseFreq<=destFreq) {
|
||||||
|
chan[c.chan].baseFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
if (return2) {
|
||||||
|
chan[c.chan].inPorta=false;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_LEGATO:
|
||||||
|
if (c.chan==3) break;
|
||||||
|
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
chan[c.chan].note=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PRE_PORTA:
|
||||||
|
if (chan[c.chan].active && c.value2) {
|
||||||
|
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||||
|
}
|
||||||
|
chan[c.chan].inPorta=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_GET_VOLMAX:
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
case DIV_ALWAYS_SET_VOLUME:
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::muteChannel(int ch, bool mute) {
|
||||||
|
isMuted[ch]=mute;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::forceIns() {
|
||||||
|
for (int i=0; i<1; i++) {
|
||||||
|
chan[i].insChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* DivPlatformPCSpeaker::getChanState(int ch) {
|
||||||
|
return &chan[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char* DivPlatformPCSpeaker::getRegisterPool() {
|
||||||
|
if (on) {
|
||||||
|
regPool[0]=freq;
|
||||||
|
regPool[1]=freq>>8;
|
||||||
|
} else {
|
||||||
|
regPool[0]=0;
|
||||||
|
regPool[1]=0;
|
||||||
|
}
|
||||||
|
return regPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformPCSpeaker::getRegisterPoolSize() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::reset() {
|
||||||
|
for (int i=0; i<1; i++) {
|
||||||
|
chan[i]=DivPlatformPCSpeaker::Channel();
|
||||||
|
}
|
||||||
|
if (dumpWrites) {
|
||||||
|
addWrite(0xffffffff,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
on=false;
|
||||||
|
lastOn=false;
|
||||||
|
freq=0;
|
||||||
|
lastFreq=0;
|
||||||
|
pos=0;
|
||||||
|
flip=false;
|
||||||
|
low=0;
|
||||||
|
band=0;
|
||||||
|
|
||||||
|
if (speakerType==3) {
|
||||||
|
#ifdef __linux__
|
||||||
|
if (beepFD==-1) {
|
||||||
|
beepFD=open("/dev/input/by-path/platform-pcspkr-event-spkr",O_WRONLY);
|
||||||
|
if (beepFD<0) {
|
||||||
|
perror("error while opening PC speaker");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
beepFreq(0);
|
||||||
|
} else {
|
||||||
|
beepFreq(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(regPool,0,2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::setFlags(unsigned int flags) {
|
||||||
|
chipClock=COLOR_NTSC/3.0;
|
||||||
|
rate=chipClock/PCSPKR_DIVIDER;
|
||||||
|
speakerType=flags&3;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) {
|
||||||
|
for (int i=0; i<1; i++) {
|
||||||
|
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::notifyPlaybackStop() {
|
||||||
|
beepFreq(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) {
|
||||||
|
// ???
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
|
// ???
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformPCSpeaker::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
|
parent=p;
|
||||||
|
dumpWrites=false;
|
||||||
|
skipRegisterWrites=false;
|
||||||
|
beepFD=-1;
|
||||||
|
for (int i=0; i<1; i++) {
|
||||||
|
isMuted[i]=false;
|
||||||
|
}
|
||||||
|
setFlags(flags);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformPCSpeaker::quit() {
|
||||||
|
if (speakerType==3) {
|
||||||
|
beepFreq(0);
|
||||||
|
}
|
||||||
|
#ifdef __linux__
|
||||||
|
if (beepFD>=0) close(beepFD);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DivPlatformPCSpeaker::~DivPlatformPCSpeaker() {
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* Furnace Tracker - multi-system chiptune tracker
|
||||||
|
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PCSPKR_H
|
||||||
|
#define _PCSPKR_H
|
||||||
|
|
||||||
|
#include "../dispatch.h"
|
||||||
|
#include "../macroInt.h"
|
||||||
|
|
||||||
|
class DivPlatformPCSpeaker: public DivDispatch {
|
||||||
|
struct Channel {
|
||||||
|
int freq, baseFreq, pitch, note;
|
||||||
|
unsigned char ins, duty, sweep;
|
||||||
|
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
|
||||||
|
signed char vol, outVol, wave;
|
||||||
|
DivMacroInt std;
|
||||||
|
Channel():
|
||||||
|
freq(0),
|
||||||
|
baseFreq(0),
|
||||||
|
pitch(0),
|
||||||
|
note(0),
|
||||||
|
ins(-1),
|
||||||
|
duty(0),
|
||||||
|
sweep(8),
|
||||||
|
active(false),
|
||||||
|
insChanged(true),
|
||||||
|
freqChanged(false),
|
||||||
|
sweepChanged(false),
|
||||||
|
keyOn(false),
|
||||||
|
keyOff(false),
|
||||||
|
inPorta(false),
|
||||||
|
furnaceDac(false),
|
||||||
|
vol(15),
|
||||||
|
outVol(15),
|
||||||
|
wave(-1) {}
|
||||||
|
};
|
||||||
|
Channel chan[1];
|
||||||
|
bool isMuted[1];
|
||||||
|
bool on, flip, lastOn;
|
||||||
|
int pos, speakerType, beepFD;
|
||||||
|
float low, band;
|
||||||
|
float low2, high2, band2;
|
||||||
|
float low3, band3;
|
||||||
|
unsigned short freq, lastFreq;
|
||||||
|
unsigned char regPool[2];
|
||||||
|
|
||||||
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
||||||
|
void beepFreq(int freq);
|
||||||
|
|
||||||
|
void acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
void acquire_cone(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
void acquire_piezo(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
void acquire_real(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
int dispatch(DivCommand c);
|
||||||
|
void* getChanState(int chan);
|
||||||
|
unsigned char* getRegisterPool();
|
||||||
|
int getRegisterPoolSize();
|
||||||
|
void reset();
|
||||||
|
void forceIns();
|
||||||
|
void tick();
|
||||||
|
void muteChannel(int ch, bool mute);
|
||||||
|
bool keyOffAffectsArp(int ch);
|
||||||
|
void setFlags(unsigned int flags);
|
||||||
|
void notifyInsDeletion(void* ins);
|
||||||
|
void notifyPlaybackStop();
|
||||||
|
void poke(unsigned int addr, unsigned short val);
|
||||||
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
|
const char** getRegisterSheet();
|
||||||
|
const char* getEffectName(unsigned char effect);
|
||||||
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
|
void quit();
|
||||||
|
~DivPlatformPCSpeaker();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -892,16 +892,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
||||||
chan[c.chan].ins=c.value;
|
chan[c.chan].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
|
||||||
chan[c.chan].pan=1;
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
chan[c.chan].pan=2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
chan[c.chan].pan=3;
|
chan[c.chan].pan=3;
|
||||||
break;
|
} else {
|
||||||
|
chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||||
}
|
}
|
||||||
if (c.chan>12) {
|
if (c.chan>12) {
|
||||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||||
|
|
|
@ -955,16 +955,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
||||||
chan[c.chan].ins=c.value;
|
chan[c.chan].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
|
||||||
chan[c.chan].pan=1;
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
chan[c.chan].pan=2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
chan[c.chan].pan=3;
|
chan[c.chan].pan=3;
|
||||||
break;
|
} else {
|
||||||
|
chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||||
}
|
}
|
||||||
if (c.chan>14) {
|
if (c.chan>14) {
|
||||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||||
|
|
|
@ -97,16 +97,10 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
||||||
opChan[ch].ins=c.value;
|
opChan[ch].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
|
||||||
opChan[ch].pan=1;
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
opChan[ch].pan=2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
opChan[ch].pan=3;
|
opChan[ch].pan=3;
|
||||||
break;
|
} else {
|
||||||
|
opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||||
}
|
}
|
||||||
DivInstrument* ins=parent->getIns(opChan[ch].ins);
|
DivInstrument* ins=parent->getIns(opChan[ch].ins);
|
||||||
// TODO: ???
|
// TODO: ???
|
||||||
|
|
|
@ -97,16 +97,10 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
||||||
opChan[ch].ins=c.value;
|
opChan[ch].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
|
||||||
opChan[ch].pan=1;
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
opChan[ch].pan=2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
opChan[ch].pan=3;
|
opChan[ch].pan=3;
|
||||||
break;
|
} else {
|
||||||
|
opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||||
}
|
}
|
||||||
DivInstrument* ins=parent->getIns(opChan[ch].ins);
|
DivInstrument* ins=parent->getIns(opChan[ch].ins);
|
||||||
// TODO: ???
|
// TODO: ???
|
||||||
|
|
|
@ -42,6 +42,7 @@ const char* notes[12]={
|
||||||
"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"
|
"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// update this when adding new commands.
|
||||||
const char* cmdName[DIV_CMD_MAX]={
|
const char* cmdName[DIV_CMD_MAX]={
|
||||||
"NOTE_ON",
|
"NOTE_ON",
|
||||||
"NOTE_OFF",
|
"NOTE_OFF",
|
||||||
|
@ -224,14 +225,6 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DIV_SYSTEM_LYNX:
|
|
||||||
if (effect>=0x30 && effect<0x40) {
|
|
||||||
int value = ((int)(effect&0x0f)<<8)|effectVal;
|
|
||||||
dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case DIV_SYSTEM_OPLL_DRUMS:
|
case DIV_SYSTEM_OPLL_DRUMS:
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case 0x18: // drum mode toggle
|
case 0x18: // drum mode toggle
|
||||||
|
@ -545,6 +538,14 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_LYNX:
|
||||||
|
if (effect>=0x30 && effect<0x40) {
|
||||||
|
int value = ((int)(effect&0x0f)<<8)|effectVal;
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,8 @@ struct DivSong {
|
||||||
// - 6: 0.89MHz (Sunsoft 5B)
|
// - 6: 0.89MHz (Sunsoft 5B)
|
||||||
// - 7: 1.67MHz
|
// - 7: 1.67MHz
|
||||||
// - 8: 0.83MHz (Sunsoft 5B on PAL)
|
// - 8: 0.83MHz (Sunsoft 5B on PAL)
|
||||||
|
// - 9: 1.10MHz (Gamate/VIC-20 PAL)
|
||||||
|
// - 10: 2.097152MHz (Game Boy)
|
||||||
// - bit 4-5: chip type (ignored on AY8930)
|
// - bit 4-5: chip type (ignored on AY8930)
|
||||||
// - 0: AY-3-8910 or similar
|
// - 0: AY-3-8910 or similar
|
||||||
// - 1: YM2149
|
// - 1: YM2149
|
||||||
|
@ -216,6 +218,12 @@ struct DivSong {
|
||||||
// - 1: Amiga 1200
|
// - 1: Amiga 1200
|
||||||
// - bit 8-14: stereo separation
|
// - bit 8-14: stereo separation
|
||||||
// - 0 is 0% while 127 is 100%
|
// - 0 is 0% while 127 is 100%
|
||||||
|
// - PC Speaker:
|
||||||
|
// - bit 0-1: speaker type
|
||||||
|
// - 0: unfiltered
|
||||||
|
// - 1: cone
|
||||||
|
// - 2: piezo
|
||||||
|
// - 3: real (TODO)
|
||||||
// - QSound:
|
// - QSound:
|
||||||
// - bit 12-20: echo feedback
|
// - bit 12-20: echo feedback
|
||||||
// - Valid values are 0-255
|
// - Valid values are 0-255
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <SDL_video.h>
|
|
||||||
#define _USE_MATH_DEFINES
|
#define _USE_MATH_DEFINES
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
@ -623,6 +622,10 @@ void FurnaceGUI::drawEditControls() {
|
||||||
e->noteOff(activeNotes[i].chan);
|
e->noteOff(activeNotes[i].chan);
|
||||||
}
|
}
|
||||||
activeNotes.clear();
|
activeNotes.clear();
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Edit Step");
|
ImGui::Text("Edit Step");
|
||||||
|
@ -630,6 +633,10 @@ void FurnaceGUI::drawEditControls() {
|
||||||
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
||||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||||
if (editStep<0) editStep=0;
|
if (editStep<0) editStep=0;
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
|
||||||
|
@ -649,9 +656,9 @@ void FurnaceGUI::drawEditControls() {
|
||||||
|
|
||||||
ImGui::Text("Follow");
|
ImGui::Text("Follow");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Orders",&followOrders);
|
unimportant(ImGui::Checkbox("Orders",&followOrders));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Pattern",&followPattern);
|
unimportant(ImGui::Checkbox("Pattern",&followPattern));
|
||||||
|
|
||||||
bool repeatPattern=e->getRepeatPattern();
|
bool repeatPattern=e->getRepeatPattern();
|
||||||
if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) {
|
if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) {
|
||||||
|
@ -713,6 +720,10 @@ void FurnaceGUI::drawEditControls() {
|
||||||
e->noteOff(activeNotes[i].chan);
|
e->noteOff(activeNotes[i].chan);
|
||||||
}
|
}
|
||||||
activeNotes.clear();
|
activeNotes.clear();
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
@ -722,14 +733,18 @@ void FurnaceGUI::drawEditControls() {
|
||||||
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
||||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||||
if (editStep<0) editStep=0;
|
if (editStep<0) editStep=0;
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Text("Follow");
|
ImGui::Text("Follow");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Orders",&followOrders);
|
unimportant(ImGui::Checkbox("Orders",&followOrders));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Pattern",&followPattern);
|
unimportant(ImGui::Checkbox("Pattern",&followPattern));
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -776,6 +791,10 @@ void FurnaceGUI::drawEditControls() {
|
||||||
e->noteOff(activeNotes[i].chan);
|
e->noteOff(activeNotes[i].chan);
|
||||||
}
|
}
|
||||||
activeNotes.clear();
|
activeNotes.clear();
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Step");
|
ImGui::Text("Step");
|
||||||
|
@ -783,16 +802,20 @@ void FurnaceGUI::drawEditControls() {
|
||||||
if (ImGui::InputInt("##EditStep",&editStep,0,0)) {
|
if (ImGui::InputInt("##EditStep",&editStep,0,0)) {
|
||||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||||
if (editStep<0) editStep=0;
|
if (editStep<0) editStep=0;
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Foll.");
|
ImGui::Text("Foll.");
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f));
|
||||||
if (ImGui::SmallButton("Ord##FollowOrders")) {
|
if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant
|
||||||
followOrders=!followOrders;
|
followOrders=!followOrders;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f));
|
||||||
if (ImGui::SmallButton("Pat##FollowPattern")) {
|
if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant
|
||||||
followPattern=!followPattern;
|
followPattern=!followPattern;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
@ -860,6 +883,10 @@ void FurnaceGUI::drawEditControls() {
|
||||||
e->noteOff(activeNotes[i].chan);
|
e->noteOff(activeNotes[i].chan);
|
||||||
}
|
}
|
||||||
activeNotes.clear();
|
activeNotes.clear();
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Step");
|
ImGui::Text("Step");
|
||||||
|
@ -869,11 +896,15 @@ void FurnaceGUI::drawEditControls() {
|
||||||
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
||||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||||
if (editStep<0) editStep=0;
|
if (editStep<0) editStep=0;
|
||||||
|
|
||||||
|
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
|
|
||||||
ImGui::Checkbox("Follow orders",&followOrders);
|
unimportant(ImGui::Checkbox("Follow orders",&followOrders));
|
||||||
ImGui::Checkbox("Follow pattern",&followPattern);
|
unimportant(ImGui::Checkbox("Follow pattern",&followPattern));
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -1169,10 +1200,15 @@ void FurnaceGUI::drawInsList() {
|
||||||
if (ImGui::Selectable(name.c_str(),curIns==i)) {
|
if (ImGui::Selectable(name.c_str(),curIns==i)) {
|
||||||
curIns=i;
|
curIns=i;
|
||||||
}
|
}
|
||||||
|
if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) {
|
||||||
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
|
curIns=i;
|
||||||
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
insEditOpen=true;
|
insEditOpen=true;
|
||||||
|
nextWindow=GUI_WINDOW_INS_EDIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4589,6 +4625,7 @@ bool FurnaceGUI::loop() {
|
||||||
sysAddOption(DIV_SYSTEM_YM2610B_EXT);
|
sysAddOption(DIV_SYSTEM_YM2610B_EXT);
|
||||||
sysAddOption(DIV_SYSTEM_AY8910);
|
sysAddOption(DIV_SYSTEM_AY8910);
|
||||||
sysAddOption(DIV_SYSTEM_AMIGA);
|
sysAddOption(DIV_SYSTEM_AMIGA);
|
||||||
|
sysAddOption(DIV_SYSTEM_PCSPKR);
|
||||||
sysAddOption(DIV_SYSTEM_OPLL);
|
sysAddOption(DIV_SYSTEM_OPLL);
|
||||||
sysAddOption(DIV_SYSTEM_OPLL_DRUMS);
|
sysAddOption(DIV_SYSTEM_OPLL_DRUMS);
|
||||||
sysAddOption(DIV_SYSTEM_VRC7);
|
sysAddOption(DIV_SYSTEM_VRC7);
|
||||||
|
@ -4782,6 +4819,14 @@ bool FurnaceGUI::loop() {
|
||||||
e->setSysFlags(i,(flags&(~15))|8,restart);
|
e->setSysFlags(i,(flags&(~15))|8,restart);
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
}
|
}
|
||||||
|
if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) {
|
||||||
|
e->setSysFlags(i,(flags&(~15))|9,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) {
|
||||||
|
e->setSysFlags(i,(flags&(~15))|10,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
if (e->song.system[i]==DIV_SYSTEM_AY8910) {
|
if (e->song.system[i]==DIV_SYSTEM_AY8910) {
|
||||||
ImGui::Text("Chip type:");
|
ImGui::Text("Chip type:");
|
||||||
if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) {
|
if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) {
|
||||||
|
@ -4843,6 +4888,26 @@ bool FurnaceGUI::loop() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case DIV_SYSTEM_PCSPKR: {
|
||||||
|
ImGui::Text("Speaker type:");
|
||||||
|
if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) {
|
||||||
|
e->setSysFlags(i,(flags&(~3))|0,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Cone",(flags&3)==1)) {
|
||||||
|
e->setSysFlags(i,(flags&(~3))|1,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Piezo",(flags&3)==2)) {
|
||||||
|
e->setSysFlags(i,(flags&(~3))|2,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) {
|
||||||
|
e->setSysFlags(i,(flags&(~3))|3,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case DIV_SYSTEM_QSOUND: {
|
case DIV_SYSTEM_QSOUND: {
|
||||||
ImGui::Text("Echo delay:");
|
ImGui::Text("Echo delay:");
|
||||||
int echoBufSize=2725 - (flags & 4095);
|
int echoBufSize=2725 - (flags & 4095);
|
||||||
|
@ -4906,6 +4971,7 @@ bool FurnaceGUI::loop() {
|
||||||
sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT);
|
sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT);
|
||||||
sysChangeOption(i,DIV_SYSTEM_AY8910);
|
sysChangeOption(i,DIV_SYSTEM_AY8910);
|
||||||
sysChangeOption(i,DIV_SYSTEM_AMIGA);
|
sysChangeOption(i,DIV_SYSTEM_AMIGA);
|
||||||
|
sysChangeOption(i,DIV_SYSTEM_PCSPKR);
|
||||||
sysChangeOption(i,DIV_SYSTEM_OPLL);
|
sysChangeOption(i,DIV_SYSTEM_OPLL);
|
||||||
sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS);
|
sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS);
|
||||||
sysChangeOption(i,DIV_SYSTEM_VRC7);
|
sysChangeOption(i,DIV_SYSTEM_VRC7);
|
||||||
|
@ -5072,19 +5138,19 @@ bool FurnaceGUI::loop() {
|
||||||
|
|
||||||
ImGui::DockSpaceOverViewport();
|
ImGui::DockSpaceOverViewport();
|
||||||
|
|
||||||
|
drawPattern();
|
||||||
drawEditControls();
|
drawEditControls();
|
||||||
drawSongInfo();
|
drawSongInfo();
|
||||||
drawOrders();
|
drawOrders();
|
||||||
drawInsList();
|
|
||||||
drawInsEdit();
|
|
||||||
drawWaveList();
|
|
||||||
drawWaveEdit();
|
|
||||||
drawSampleList();
|
drawSampleList();
|
||||||
drawSampleEdit();
|
drawSampleEdit();
|
||||||
|
drawWaveList();
|
||||||
|
drawWaveEdit();
|
||||||
|
drawInsList();
|
||||||
|
drawInsEdit();
|
||||||
drawMixer();
|
drawMixer();
|
||||||
drawOsc();
|
drawOsc();
|
||||||
drawVolMeter();
|
drawVolMeter();
|
||||||
drawPattern();
|
|
||||||
drawSettings();
|
drawSettings();
|
||||||
drawDebug();
|
drawDebug();
|
||||||
drawStats();
|
drawStats();
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
|
|
||||||
#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1);
|
#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1);
|
||||||
|
|
||||||
|
#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;}
|
||||||
|
#define unimportant(x) if (x) {handleUnimportant}
|
||||||
|
|
||||||
enum FurnaceGUIColors {
|
enum FurnaceGUIColors {
|
||||||
GUI_COLOR_BACKGROUND=0,
|
GUI_COLOR_BACKGROUND=0,
|
||||||
GUI_COLOR_FRAME_BACKGROUND,
|
GUI_COLOR_FRAME_BACKGROUND,
|
||||||
|
@ -495,6 +498,7 @@ class FurnaceGUI {
|
||||||
int viewPrevPattern;
|
int viewPrevPattern;
|
||||||
int guiColorsBase;
|
int guiColorsBase;
|
||||||
int avoidRaisingPattern;
|
int avoidRaisingPattern;
|
||||||
|
int insFocusesPattern;
|
||||||
unsigned int maxUndoSteps;
|
unsigned int maxUndoSteps;
|
||||||
String mainFontPath;
|
String mainFontPath;
|
||||||
String patFontPath;
|
String patFontPath;
|
||||||
|
@ -537,6 +541,7 @@ class FurnaceGUI {
|
||||||
viewPrevPattern(1),
|
viewPrevPattern(1),
|
||||||
guiColorsBase(0),
|
guiColorsBase(0),
|
||||||
avoidRaisingPattern(0),
|
avoidRaisingPattern(0),
|
||||||
|
insFocusesPattern(1),
|
||||||
maxUndoSteps(100),
|
maxUndoSteps(100),
|
||||||
mainFontPath(""),
|
mainFontPath(""),
|
||||||
patFontPath(""),
|
patFontPath(""),
|
||||||
|
|
|
@ -60,7 +60,7 @@ const char* ssgEnvTypes[8]={
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* fmParamNames[3][27]={
|
const char* fmParamNames[3][27]={
|
||||||
{"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "EnvAlternate", "EnvAlternate", "LevelScale/key", "Sustain", "Vibrato", "Waveform", "EnvScale/key", "OP2 HalfSine", "OP1 HalfSine"},
|
{"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"},
|
||||||
{"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"},
|
{"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"},
|
||||||
{"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}
|
{"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,6 +75,10 @@ void FurnaceGUI::drawOrders() {
|
||||||
e->setOrder(i);
|
e->setOrder(i);
|
||||||
curNibble=false;
|
curNibble=false;
|
||||||
orderCursor=-1;
|
orderCursor=-1;
|
||||||
|
|
||||||
|
if (orderEditMode==0) {
|
||||||
|
handleUnimportant;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
||||||
|
@ -111,6 +115,10 @@ void FurnaceGUI::drawOrders() {
|
||||||
curNibble=false;
|
curNibble=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (orderEditMode==0) {
|
||||||
|
handleUnimportant;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!pat->name.empty() && ImGui::IsItemHovered()) {
|
if (!pat->name.empty() && ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("%s",pat->name.c_str());
|
ImGui::SetTooltip("%s",pat->name.c_str());
|
||||||
|
@ -148,21 +156,21 @@ void FurnaceGUI::drawOrders() {
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
if (ImGui::Button(ICON_FA_PLUS)) {
|
if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant
|
||||||
// add order row (new)
|
// add order row (new)
|
||||||
doAction(GUI_ACTION_ORDERS_ADD);
|
doAction(GUI_ACTION_ORDERS_ADD);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Add new order");
|
ImGui::SetTooltip("Add new order");
|
||||||
}
|
}
|
||||||
if (ImGui::Button(ICON_FA_MINUS)) {
|
if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant
|
||||||
// remove this order row
|
// remove this order row
|
||||||
doAction(GUI_ACTION_ORDERS_REMOVE);
|
doAction(GUI_ACTION_ORDERS_REMOVE);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Remove order");
|
ImGui::SetTooltip("Remove order");
|
||||||
}
|
}
|
||||||
if (ImGui::Button(ICON_FA_FILES_O)) {
|
if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant
|
||||||
// duplicate order row
|
// duplicate order row
|
||||||
doAction(GUI_ACTION_ORDERS_DUPLICATE);
|
doAction(GUI_ACTION_ORDERS_DUPLICATE);
|
||||||
}
|
}
|
||||||
|
@ -172,21 +180,21 @@ void FurnaceGUI::drawOrders() {
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Duplicate order (right-click to deep clone)");
|
ImGui::SetTooltip("Duplicate order (right-click to deep clone)");
|
||||||
}
|
}
|
||||||
if (ImGui::Button(ICON_FA_ANGLE_UP)) {
|
if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant
|
||||||
// move order row up
|
// move order row up
|
||||||
doAction(GUI_ACTION_ORDERS_MOVE_UP);
|
doAction(GUI_ACTION_ORDERS_MOVE_UP);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Move order up");
|
ImGui::SetTooltip("Move order up");
|
||||||
}
|
}
|
||||||
if (ImGui::Button(ICON_FA_ANGLE_DOWN)) {
|
if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant
|
||||||
// move order row down
|
// move order row down
|
||||||
doAction(GUI_ACTION_ORDERS_MOVE_DOWN);
|
doAction(GUI_ACTION_ORDERS_MOVE_DOWN);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Move order down");
|
ImGui::SetTooltip("Move order down");
|
||||||
}
|
}
|
||||||
if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) {
|
if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant
|
||||||
// duplicate order row at end
|
// duplicate order row at end
|
||||||
doAction(GUI_ACTION_ORDERS_DUPLICATE_END);
|
doAction(GUI_ACTION_ORDERS_DUPLICATE_END);
|
||||||
}
|
}
|
||||||
|
@ -196,7 +204,7 @@ void FurnaceGUI::drawOrders() {
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)");
|
ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)");
|
||||||
}
|
}
|
||||||
if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) {
|
if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant
|
||||||
// whether to change one or all orders in a row
|
// whether to change one or all orders in a row
|
||||||
changeAllOrders=!changeAllOrders;
|
changeAllOrders=!changeAllOrders;
|
||||||
}
|
}
|
||||||
|
@ -217,7 +225,7 @@ void FurnaceGUI::drawOrders() {
|
||||||
} else {
|
} else {
|
||||||
orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode";
|
orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode";
|
||||||
}
|
}
|
||||||
if (ImGui::Button(orderEditModeLabel)) {
|
if (ImGui::Button(orderEditModeLabel)) { handleUnimportant
|
||||||
orderEditMode++;
|
orderEditMode++;
|
||||||
if (orderEditMode>3) orderEditMode=0;
|
if (orderEditMode>3) orderEditMode=0;
|
||||||
curNibble=false;
|
curNibble=false;
|
||||||
|
|
|
@ -167,6 +167,11 @@ void FurnaceGUI::drawSettings() {
|
||||||
settings.avoidRaisingPattern=avoidRaisingPatternB;
|
settings.avoidRaisingPattern=avoidRaisingPatternB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool insFocusesPatternB=settings.insFocusesPattern;
|
||||||
|
if (ImGui::Checkbox("Focus pattern editor when selecting instrument",&insFocusesPatternB)) {
|
||||||
|
settings.insFocusesPattern=insFocusesPatternB;
|
||||||
|
}
|
||||||
|
|
||||||
bool restartOnFlagChangeB=settings.restartOnFlagChange;
|
bool restartOnFlagChangeB=settings.restartOnFlagChange;
|
||||||
if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) {
|
if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) {
|
||||||
settings.restartOnFlagChange=restartOnFlagChangeB;
|
settings.restartOnFlagChange=restartOnFlagChangeB;
|
||||||
|
@ -388,7 +393,7 @@ void FurnaceGUI::drawSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool macroViewB=settings.macroView;
|
bool macroViewB=settings.macroView;
|
||||||
if (ImGui::Checkbox("Classic macro view (standard macros only)",¯oViewB)) {
|
if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) {
|
||||||
settings.macroView=macroViewB;
|
settings.macroView=macroViewB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,6 +834,14 @@ void FurnaceGUI::drawSettings() {
|
||||||
#define LOAD_KEYBIND(x,y) \
|
#define LOAD_KEYBIND(x,y) \
|
||||||
actionKeys[x]=e->getConfInt("keybind_" #x,y);
|
actionKeys[x]=e->getConfInt("keybind_" #x,y);
|
||||||
|
|
||||||
|
#define clampSetting(x,minV,maxV) \
|
||||||
|
if (x<minV) { \
|
||||||
|
x=minV; \
|
||||||
|
} \
|
||||||
|
if (x>maxV) { \
|
||||||
|
x=maxV; \
|
||||||
|
}
|
||||||
|
|
||||||
void FurnaceGUI::syncSettings() {
|
void FurnaceGUI::syncSettings() {
|
||||||
settings.mainFontSize=e->getConfInt("mainFontSize",18);
|
settings.mainFontSize=e->getConfInt("mainFontSize",18);
|
||||||
settings.patFontSize=e->getConfInt("patFontSize",18);
|
settings.patFontSize=e->getConfInt("patFontSize",18);
|
||||||
|
@ -856,7 +869,6 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.allowEditDocking=e->getConfInt("allowEditDocking",0);
|
settings.allowEditDocking=e->getConfInt("allowEditDocking",0);
|
||||||
settings.chipNames=e->getConfInt("chipNames",0);
|
settings.chipNames=e->getConfInt("chipNames",0);
|
||||||
settings.overflowHighlight=e->getConfInt("overflowHighlight",0);
|
settings.overflowHighlight=e->getConfInt("overflowHighlight",0);
|
||||||
if (settings.fmNames<0 || settings.fmNames>2) settings.fmNames=0;
|
|
||||||
settings.partyTime=e->getConfInt("partyTime",0);
|
settings.partyTime=e->getConfInt("partyTime",0);
|
||||||
settings.germanNotation=e->getConfInt("germanNotation",0);
|
settings.germanNotation=e->getConfInt("germanNotation",0);
|
||||||
settings.stepOnDelete=e->getConfInt("stepOnDelete",0);
|
settings.stepOnDelete=e->getConfInt("stepOnDelete",0);
|
||||||
|
@ -870,6 +882,44 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1);
|
settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1);
|
||||||
settings.guiColorsBase=e->getConfInt("guiColorsBase",0);
|
settings.guiColorsBase=e->getConfInt("guiColorsBase",0);
|
||||||
settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0);
|
settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0);
|
||||||
|
settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1);
|
||||||
|
|
||||||
|
clampSetting(settings.mainFontSize,2,96);
|
||||||
|
clampSetting(settings.patFontSize,2,96);
|
||||||
|
clampSetting(settings.iconSize,2,48);
|
||||||
|
clampSetting(settings.audioEngine,0,1);
|
||||||
|
clampSetting(settings.audioQuality,0,1);
|
||||||
|
clampSetting(settings.audioBufSize,32,4096);
|
||||||
|
clampSetting(settings.audioRate,8000,384000);
|
||||||
|
clampSetting(settings.arcadeCore,0,1);
|
||||||
|
clampSetting(settings.ym2612Core,0,1);
|
||||||
|
clampSetting(settings.saaCore,0,1);
|
||||||
|
clampSetting(settings.mainFont,0,6);
|
||||||
|
clampSetting(settings.patFont,0,6);
|
||||||
|
clampSetting(settings.patRowsBase,0,1);
|
||||||
|
clampSetting(settings.orderRowsBase,0,1);
|
||||||
|
clampSetting(settings.soloAction,0,2);
|
||||||
|
clampSetting(settings.pullDeleteBehavior,0,1);
|
||||||
|
clampSetting(settings.wrapHorizontal,0,2);
|
||||||
|
clampSetting(settings.wrapVertical,0,2);
|
||||||
|
clampSetting(settings.macroView,0,1);
|
||||||
|
clampSetting(settings.fmNames,0,2);
|
||||||
|
clampSetting(settings.allowEditDocking,0,1);
|
||||||
|
clampSetting(settings.chipNames,0,1);
|
||||||
|
clampSetting(settings.overflowHighlight,0,1);
|
||||||
|
clampSetting(settings.partyTime,0,1);
|
||||||
|
clampSetting(settings.germanNotation,0,1);
|
||||||
|
clampSetting(settings.stepOnDelete,0,1);
|
||||||
|
clampSetting(settings.scrollStep,0,1);
|
||||||
|
clampSetting(settings.sysSeparators,0,1);
|
||||||
|
clampSetting(settings.forceMono,0,1);
|
||||||
|
clampSetting(settings.controlLayout,0,3);
|
||||||
|
clampSetting(settings.statusDisplay,0,3);
|
||||||
|
clampSetting(settings.dpiScale,0.0f,4.0f);
|
||||||
|
clampSetting(settings.viewPrevPattern,0,1);
|
||||||
|
clampSetting(settings.guiColorsBase,0,1);
|
||||||
|
clampSetting(settings.avoidRaisingPattern,0,1);
|
||||||
|
clampSetting(settings.insFocusesPattern,0,1);
|
||||||
|
|
||||||
// keybinds
|
// keybinds
|
||||||
LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o);
|
LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o);
|
||||||
|
@ -1067,6 +1117,7 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("viewPrevPattern",settings.viewPrevPattern);
|
e->setConf("viewPrevPattern",settings.viewPrevPattern);
|
||||||
e->setConf("guiColorsBase",settings.guiColorsBase);
|
e->setConf("guiColorsBase",settings.guiColorsBase);
|
||||||
e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern);
|
e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern);
|
||||||
|
e->setConf("insFocusesPattern",settings.insFocusesPattern);
|
||||||
|
|
||||||
PUT_UI_COLOR(GUI_COLOR_BACKGROUND);
|
PUT_UI_COLOR(GUI_COLOR_BACKGROUND);
|
||||||
PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND);
|
PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND);
|
||||||
|
|
Loading…
Reference in New Issue