
* 'master' of https://github.com/tildearrow/furnace: (70 commits) whoops GUI: AY8930 credits GUI: fix inability to close subsongs BANK OPN: wire up ExtCh system fix build failure dev95 - multiple songs in a single file (READ) DO NOT USE - THIS FAILS - WORK IN PROGRESS enforce asset limits old .dmf loading improvements add AICA and YMZ ADPCM formats allocate ID for YMZ280B harden .fur file saver Fix AY VGM output, Fix presets preparations for UI improvements GUI: add more presets prepare for ExtCh OPN/OPNA GUI: clarify that lock layout doesn't work yet GUI: remember last state of order edit mode GUI: store edit/followOrders/followPattern state ... # Conflicts: # src/engine/fileOps.cpp # src/engine/platform/ym2610.cpp # src/engine/platform/ym2610b.cpp # src/engine/sample.cpp # src/engine/sample.h # src/engine/sysDef.cpp # src/gui/doAction.cpp # src/gui/sysConf.cpp
593 lines
13 KiB
C++
593 lines
13 KiB
C++
/**
|
|
* 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 _INSTRUMENT_H
|
|
#define _INSTRUMENT_H
|
|
#include <vector>
|
|
#include "sample.h"
|
|
#include "safeWriter.h"
|
|
#include "dataErrors.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: unsigned short {
|
|
DIV_INS_STD=0,
|
|
DIV_INS_FM=1,
|
|
DIV_INS_GB=2,
|
|
DIV_INS_C64=3,
|
|
DIV_INS_AMIGA=4,
|
|
DIV_INS_PCE=5,
|
|
DIV_INS_AY=6,
|
|
DIV_INS_AY8930=7,
|
|
DIV_INS_TIA=8,
|
|
DIV_INS_SAA1099=9,
|
|
DIV_INS_VIC=10,
|
|
DIV_INS_PET=11,
|
|
DIV_INS_VRC6=12,
|
|
DIV_INS_OPLL=13,
|
|
DIV_INS_OPL=14,
|
|
DIV_INS_FDS=15,
|
|
DIV_INS_VBOY=16,
|
|
DIV_INS_N163=17,
|
|
DIV_INS_SCC=18,
|
|
DIV_INS_OPZ=19,
|
|
DIV_INS_POKEY=20,
|
|
DIV_INS_BEEPER=21,
|
|
DIV_INS_SWAN=22,
|
|
DIV_INS_MIKEY=23,
|
|
DIV_INS_VERA=24,
|
|
DIV_INS_X1_010=25,
|
|
DIV_INS_VRC6_SAW=26,
|
|
DIV_INS_ES5506=27,
|
|
DIV_INS_MULTIPCM=28,
|
|
DIV_INS_SNES=29,
|
|
DIV_INS_SU=30,
|
|
DIV_INS_MAX,
|
|
DIV_INS_NULL
|
|
};
|
|
|
|
// FM operator structure:
|
|
// - OPN:
|
|
// - AM, AR, DR, MULT, RR, SL, TL, RS, DT, D2R, SSG-EG
|
|
// - OPM:
|
|
// - AM, AR, DR, MULT, RR, SL, TL, DT2, RS, DT, D2R
|
|
// - OPLL:
|
|
// - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S
|
|
// - KSL, VIB, KSR
|
|
// - OPL:
|
|
// - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S
|
|
// - KSL, VIB, WS (OPL2/3), KSR
|
|
// - OPZ:
|
|
// - AM, AR, DR, MULT (CRS), RR, SL, TL, DT2, RS, DT, D2R
|
|
// - WS, DVB = MULT (FINE), DAM = REV, KSL = EGShift, EGT = Fixed
|
|
|
|
struct DivInstrumentFM {
|
|
unsigned char alg, fb, fms, ams, fms2, ams2, ops, opllPreset;
|
|
bool fixedDrums;
|
|
unsigned short kickFreq, snareHatFreq, tomTopFreq;
|
|
struct Operator {
|
|
bool enable;
|
|
unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv;
|
|
unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ
|
|
Operator():
|
|
enable(true),
|
|
am(0),
|
|
ar(0),
|
|
dr(0),
|
|
mult(0),
|
|
rr(0),
|
|
sl(0),
|
|
tl(0),
|
|
dt2(0),
|
|
rs(0),
|
|
dt(0),
|
|
d2r(0),
|
|
ssgEnv(0),
|
|
dam(0),
|
|
dvb(0),
|
|
egt(0),
|
|
ksl(0),
|
|
sus(0),
|
|
vib(0),
|
|
ws(0),
|
|
ksr(0) {}
|
|
} op[4];
|
|
DivInstrumentFM():
|
|
alg(0),
|
|
fb(0),
|
|
fms(0),
|
|
ams(0),
|
|
fms2(0),
|
|
ams2(0),
|
|
ops(2),
|
|
opllPreset(0),
|
|
fixedDrums(false),
|
|
kickFreq(0x520),
|
|
snareHatFreq(0x550),
|
|
tomTopFreq(0x1c0) {
|
|
// default instrument
|
|
fb=4;
|
|
op[0].tl=42;
|
|
op[0].ar=31;
|
|
op[0].dr=8;
|
|
op[0].sl=15;
|
|
op[0].rr=3;
|
|
op[0].mult=5;
|
|
op[0].dt=5;
|
|
|
|
op[2].tl=18;
|
|
op[2].ar=31;
|
|
op[2].dr=10;
|
|
op[2].sl=15;
|
|
op[2].rr=4;
|
|
op[2].mult=1;
|
|
op[2].dt=0;
|
|
|
|
op[1].tl=48;
|
|
op[1].ar=31;
|
|
op[1].dr=4;
|
|
op[1].sl=11;
|
|
op[1].rr=1;
|
|
op[1].mult=1;
|
|
op[1].dt=5;
|
|
|
|
op[3].tl=2;
|
|
op[3].ar=31;
|
|
op[3].dr=9;
|
|
op[3].sl=15;
|
|
op[3].rr=9;
|
|
op[3].mult=1;
|
|
op[3].dt=0;
|
|
}
|
|
};
|
|
|
|
// this is getting out of hand
|
|
struct DivInstrumentMacro {
|
|
String name;
|
|
int val[256];
|
|
unsigned int mode;
|
|
bool open;
|
|
unsigned char len;
|
|
signed char loop;
|
|
signed char rel;
|
|
|
|
// the following variables are used by the GUI and not saved in the file
|
|
int vScroll, vZoom;
|
|
|
|
|
|
explicit DivInstrumentMacro(const String& n, bool initOpen=false):
|
|
name(n),
|
|
mode(0),
|
|
open(initOpen),
|
|
len(0),
|
|
loop(-1),
|
|
rel(-1),
|
|
vScroll(0),
|
|
vZoom(-1) {
|
|
memset(val,0,256*sizeof(int));
|
|
}
|
|
};
|
|
|
|
struct DivInstrumentSTD {
|
|
DivInstrumentMacro volMacro;
|
|
DivInstrumentMacro arpMacro;
|
|
DivInstrumentMacro dutyMacro;
|
|
DivInstrumentMacro waveMacro;
|
|
DivInstrumentMacro pitchMacro;
|
|
DivInstrumentMacro ex1Macro;
|
|
DivInstrumentMacro ex2Macro;
|
|
DivInstrumentMacro ex3Macro;
|
|
DivInstrumentMacro algMacro;
|
|
DivInstrumentMacro fbMacro;
|
|
DivInstrumentMacro fmsMacro;
|
|
DivInstrumentMacro amsMacro;
|
|
DivInstrumentMacro panLMacro;
|
|
DivInstrumentMacro panRMacro;
|
|
DivInstrumentMacro phaseResetMacro;
|
|
DivInstrumentMacro ex4Macro;
|
|
DivInstrumentMacro ex5Macro;
|
|
DivInstrumentMacro ex6Macro;
|
|
DivInstrumentMacro ex7Macro;
|
|
DivInstrumentMacro ex8Macro;
|
|
|
|
struct OpMacro {
|
|
// ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv;
|
|
DivInstrumentMacro amMacro;
|
|
DivInstrumentMacro arMacro;
|
|
DivInstrumentMacro drMacro;
|
|
DivInstrumentMacro multMacro;
|
|
DivInstrumentMacro rrMacro;
|
|
DivInstrumentMacro slMacro;
|
|
DivInstrumentMacro tlMacro;
|
|
DivInstrumentMacro dt2Macro;
|
|
DivInstrumentMacro rsMacro;
|
|
DivInstrumentMacro dtMacro;
|
|
DivInstrumentMacro d2rMacro;
|
|
DivInstrumentMacro ssgMacro;
|
|
DivInstrumentMacro damMacro;
|
|
DivInstrumentMacro dvbMacro;
|
|
DivInstrumentMacro egtMacro;
|
|
DivInstrumentMacro kslMacro;
|
|
DivInstrumentMacro susMacro;
|
|
DivInstrumentMacro vibMacro;
|
|
DivInstrumentMacro wsMacro;
|
|
DivInstrumentMacro ksrMacro;
|
|
OpMacro():
|
|
amMacro("am"), arMacro("ar"), drMacro("dr"), multMacro("mult"),
|
|
rrMacro("rr"), slMacro("sl"), tlMacro("tl",true), dt2Macro("dt2"),
|
|
rsMacro("rs"), dtMacro("dt"), d2rMacro("d2r"), ssgMacro("ssg"),
|
|
damMacro("dam"), dvbMacro("dvb"), egtMacro("egt"), kslMacro("ksl"),
|
|
susMacro("sus"), vibMacro("vib"), wsMacro("ws"), ksrMacro("ksr") {}
|
|
} opMacros[4];
|
|
DivInstrumentSTD():
|
|
volMacro("vol",true),
|
|
arpMacro("arp"),
|
|
dutyMacro("duty"),
|
|
waveMacro("wave"),
|
|
pitchMacro("pitch"),
|
|
ex1Macro("ex1"),
|
|
ex2Macro("ex2"),
|
|
ex3Macro("ex3"),
|
|
algMacro("alg"),
|
|
fbMacro("fb"),
|
|
fmsMacro("fms"),
|
|
amsMacro("ams"),
|
|
panLMacro("panL"),
|
|
panRMacro("panR"),
|
|
phaseResetMacro("phaseReset"),
|
|
ex4Macro("ex4"),
|
|
ex5Macro("ex5"),
|
|
ex6Macro("ex6"),
|
|
ex7Macro("ex7"),
|
|
ex8Macro("ex8") {}
|
|
};
|
|
|
|
struct DivInstrumentGB {
|
|
unsigned char envVol, envDir, envLen, soundLen;
|
|
DivInstrumentGB():
|
|
envVol(15),
|
|
envDir(0),
|
|
envLen(2),
|
|
soundLen(64) {}
|
|
};
|
|
|
|
struct DivInstrumentC64 {
|
|
bool triOn, sawOn, pulseOn, noiseOn;
|
|
unsigned char a, d, s, r;
|
|
unsigned short duty;
|
|
unsigned char ringMod, oscSync;
|
|
bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs, noTest;
|
|
unsigned char res;
|
|
unsigned short cut;
|
|
bool hp, lp, bp, ch3off;
|
|
|
|
DivInstrumentC64():
|
|
triOn(false),
|
|
sawOn(true),
|
|
pulseOn(false),
|
|
noiseOn(false),
|
|
a(0),
|
|
d(8),
|
|
s(0),
|
|
r(0),
|
|
duty(2048),
|
|
ringMod(0),
|
|
oscSync(0),
|
|
toFilter(false),
|
|
volIsCutoff(false),
|
|
initFilter(false),
|
|
dutyIsAbs(false),
|
|
filterIsAbs(false),
|
|
noTest(false),
|
|
res(0),
|
|
cut(0),
|
|
hp(false),
|
|
lp(false),
|
|
bp(false),
|
|
ch3off(false) {}
|
|
};
|
|
|
|
struct DivInstrumentAmiga {
|
|
struct NoteMap {
|
|
int freq;
|
|
short ind;
|
|
unsigned char reversed;
|
|
|
|
NoteMap():
|
|
freq(0),
|
|
ind(-1),
|
|
reversed(false) {}
|
|
};
|
|
|
|
struct TransWaveSlice {
|
|
// states
|
|
double sliceSize;
|
|
double sliceBound;
|
|
double sliceStart;
|
|
double sliceEnd;
|
|
|
|
// inlines
|
|
inline double updateSize(double length, double loopStart, double loopEnd) {
|
|
sliceSize=loopEnd-loopStart;
|
|
sliceBound=(length-sliceSize);
|
|
}
|
|
inline double slicePos(double slice) {
|
|
double pos=sliceBound*slice;
|
|
if (sliceStart!=pos) {
|
|
sliceStart=pos;
|
|
}
|
|
if (sliceEnd!=(sliceSize+pos)) {
|
|
sliceEnd=(sliceSize+pos);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
TransWaveSlice():
|
|
sliceSize(0),
|
|
sliceBound(0),
|
|
sliceStart(0),
|
|
sliceEnd(0) {}
|
|
};
|
|
|
|
struct TransWave: TransWaveSlice {
|
|
bool enable;
|
|
bool sliceEnable;
|
|
int ind;
|
|
unsigned short slice;
|
|
|
|
TransWave():
|
|
TransWaveSlice(),
|
|
enable(false),
|
|
sliceEnable(false),
|
|
ind(0),
|
|
slice(0) {}
|
|
};
|
|
|
|
struct TransWaveMap: TransWaveSlice {
|
|
short ind;
|
|
unsigned char reversed;
|
|
int loopStart, loopEnd;
|
|
DivSampleLoopMode loopMode;
|
|
|
|
TransWaveMap():
|
|
TransWaveSlice(),
|
|
ind(-1),
|
|
reversed(0),
|
|
loopStart(-1),
|
|
loopEnd(-1),
|
|
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT) {}
|
|
};
|
|
|
|
short initSample;
|
|
bool reversed;
|
|
bool useNoteMap;
|
|
bool useWave;
|
|
unsigned char waveLen;
|
|
NoteMap noteMap[120];
|
|
TransWave transWave;
|
|
std::vector<TransWaveMap> transWaveMap;
|
|
|
|
/**
|
|
* get the sample at specified note.
|
|
* @return the sample.
|
|
*/
|
|
inline short getSample(int note) {
|
|
if (useNoteMap) {
|
|
if (note<0) note=0;
|
|
if (note>119) note=119;
|
|
return noteMap[note].ind;
|
|
}
|
|
return initSample;
|
|
}
|
|
|
|
/**
|
|
* get the sample frequency at specified note.
|
|
* @return the frequency, or -1 if not using note map.
|
|
*/
|
|
inline int getFreq(int note) {
|
|
if (useNoteMap) {
|
|
if (note<0) note=0;
|
|
if (note>119) note=119;
|
|
return noteMap[note].freq;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* get the sample reversed flag at specified note.
|
|
* @return the reversed flag.
|
|
*/
|
|
inline bool getReversed(int note) {
|
|
if (useNoteMap) {
|
|
if (note<0) note=0;
|
|
if (note>119) note=119;
|
|
return noteMap[note].reversed;
|
|
}
|
|
return reversed;
|
|
}
|
|
|
|
DivInstrumentAmiga():
|
|
initSample(0),
|
|
reversed(false),
|
|
useNoteMap(false),
|
|
useWave(false),
|
|
waveLen(31),
|
|
transWaveMap(1) {}
|
|
};
|
|
|
|
struct DivInstrumentN163 {
|
|
int wave, wavePos, waveLen;
|
|
unsigned char waveMode;
|
|
|
|
DivInstrumentN163():
|
|
wave(-1),
|
|
wavePos(0),
|
|
waveLen(32),
|
|
waveMode(3) {}
|
|
};
|
|
|
|
struct DivInstrumentFDS {
|
|
signed char modTable[32];
|
|
int modSpeed, modDepth;
|
|
// this is here for compatibility.
|
|
bool initModTableWithFirstWave;
|
|
DivInstrumentFDS():
|
|
modSpeed(0),
|
|
modDepth(0),
|
|
initModTableWithFirstWave(false) {
|
|
memset(modTable,0,32);
|
|
}
|
|
};
|
|
|
|
struct DivInstrumentES5506 {
|
|
struct Filter {
|
|
enum FilterMode: unsigned char { // filter mode for pole 4,3
|
|
FILTER_MODE_HPK2_HPK2,
|
|
FILTER_MODE_HPK2_LPK1,
|
|
FILTER_MODE_LPK2_LPK2,
|
|
FILTER_MODE_LPK2_LPK1,
|
|
};
|
|
FilterMode mode;
|
|
unsigned short k1, k2;
|
|
Filter():
|
|
mode(FILTER_MODE_LPK2_LPK1),
|
|
k1(0xffff),
|
|
k2(0xffff) {}
|
|
};
|
|
struct Envelope {
|
|
unsigned short ecount;
|
|
signed char lVRamp, rVRamp;
|
|
signed char k1Ramp, k2Ramp;
|
|
bool k1Slow, k2Slow;
|
|
Envelope():
|
|
ecount(0),
|
|
lVRamp(0),
|
|
rVRamp(0),
|
|
k1Ramp(0),
|
|
k2Ramp(0),
|
|
k1Slow(false),
|
|
k2Slow(false) {}
|
|
};
|
|
signed int lVol, rVol;
|
|
Filter filter;
|
|
Envelope envelope;
|
|
DivInstrumentES5506():
|
|
lVol(0xffff),
|
|
rVol(0xffff) {}
|
|
};
|
|
|
|
struct DivInstrumentMultiPCM {
|
|
unsigned char ar, d1r, dl, d2r, rr, rc;
|
|
unsigned char lfo, vib, am;
|
|
|
|
DivInstrumentMultiPCM():
|
|
ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15),
|
|
lfo(0), vib(0), am(0) {
|
|
}
|
|
};
|
|
|
|
enum DivWaveSynthEffects {
|
|
DIV_WS_NONE=0,
|
|
// one waveform effects
|
|
DIV_WS_INVERT,
|
|
DIV_WS_ADD,
|
|
DIV_WS_SUBTRACT,
|
|
DIV_WS_AVERAGE,
|
|
DIV_WS_PHASE,
|
|
|
|
DIV_WS_SINGLE_MAX,
|
|
|
|
// two waveform effects
|
|
DIV_WS_NONE_DUAL=128,
|
|
DIV_WS_WIPE,
|
|
DIV_WS_FADE,
|
|
DIV_WS_PING_PONG,
|
|
DIV_WS_OVERLAY,
|
|
DIV_WS_NEGATIVE_OVERLAY,
|
|
DIV_WS_PHASE_DUAL,
|
|
|
|
DIV_WS_DUAL_MAX
|
|
};
|
|
|
|
struct DivInstrumentWaveSynth {
|
|
int wave1, wave2;
|
|
unsigned char rateDivider;
|
|
unsigned char effect;
|
|
bool oneShot, enabled, global;
|
|
unsigned char speed, param1, param2, param3, param4;
|
|
DivInstrumentWaveSynth():
|
|
wave1(0),
|
|
wave2(0),
|
|
rateDivider(1),
|
|
effect(DIV_WS_NONE),
|
|
oneShot(false),
|
|
enabled(false),
|
|
global(false),
|
|
speed(0),
|
|
param1(0),
|
|
param2(0),
|
|
param3(0),
|
|
param4(0) {}
|
|
};
|
|
|
|
struct DivInstrument {
|
|
String name;
|
|
bool mode;
|
|
DivInstrumentType type;
|
|
DivInstrumentFM fm;
|
|
DivInstrumentSTD std;
|
|
DivInstrumentGB gb;
|
|
DivInstrumentC64 c64;
|
|
DivInstrumentAmiga amiga;
|
|
DivInstrumentN163 n163;
|
|
DivInstrumentFDS fds;
|
|
DivInstrumentES5506 es5506;
|
|
DivInstrumentMultiPCM multipcm;
|
|
DivInstrumentWaveSynth ws;
|
|
|
|
/**
|
|
* save the instrument to a SafeWriter.
|
|
* @param w the SafeWriter in question.
|
|
*/
|
|
void putInsData(SafeWriter* w);
|
|
|
|
/**
|
|
* read instrument data in .fui format.
|
|
* @param reader the reader.
|
|
* @param version the format version.
|
|
* @return a DivDataErrors.
|
|
*/
|
|
DivDataErrors readInsData(SafeReader& reader, short version);
|
|
|
|
/**
|
|
* save this instrument to a file.
|
|
* @param path file path.
|
|
* @return whether it was successful.
|
|
*/
|
|
bool save(const char* path);
|
|
DivInstrument():
|
|
name(""),
|
|
mode(false),
|
|
type(DIV_INS_FM) {
|
|
}
|
|
};
|
|
#endif
|