Merge branch 'master' into ay_divider

This commit is contained in:
cam900 2022-05-06 19:17:34 +09:00 committed by GitHub
commit 76997fd5ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 577 additions and 27 deletions

View file

@ -13,7 +13,6 @@
- maybe YMU759 ADPCM channel
- ADPCM chips
- Game Boy envelope macro/sequence
- option to display chip names instead of "multi-system" on title bar
- rewrite the system name detection function anyway
- scroll instrument/wave/sample list when selecting item
- unified data view
@ -30,4 +29,4 @@
- Apply button in settings
- find and replace
- finish wave synth
- make compact mode multi-state (note, note/ins, note/ins/vol and note/ins/vol/effects)
- add mono/poly note preview button

View file

@ -697,6 +697,17 @@ size | description
1 | extra 8 macro mode
--- | **extra C64 data** (>=89)
1 | don't test/gate before new note
--- | **MultiPCM data** (>=93)
1 | attack rate
1 | decay 1 rate
1 | decay level
1 | decay 2 rate
1 | release rate
1 | rate correction
1 | lfo rate
1 | vib depth
1 | am depth
23 | reserved
```
# wavetable

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev92" // it breaks compatiblity
#define DIV_ENGINE_VERSION 92
#define DIV_VERSION "dev93"
#define DIV_ENGINE_VERSION 93
// for imports
#define DIV_VERSION_MOD 0xff01
@ -548,7 +548,7 @@ class DivEngine {
DivInstrumentType getPreferInsSecondType(int ch);
// get song system name
const char* getSongSystemName();
String getSongSystemName(bool isMultiSystemAcceptable=true);
// get sys name
const char* getSystemName(DivSystem sys);

View file

@ -19,6 +19,7 @@
#include "engine.h"
#include "../ta-log.h"
#include "instrument.h"
#include "song.h"
#include <zlib.h>
#include <fmt/printf.h>
@ -2034,11 +2035,28 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
return success;
}
#define CHECK_BLOCK_VERSION(x) \
if (blockVersion>x) { \
logE("incompatible block version %d for %s!",blockVersion,blockName); \
lastError="incompatible block version"; \
delete[] file; \
return false; \
}
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
SafeReader reader=SafeReader(file,len);
warnings="";
try {
DivSong ds;
String blockName;
unsigned char expansions=0;
unsigned int tchans=0;
unsigned int n163Chans=0;
bool hasSequence[256][8];
unsigned char sequenceIndex[256][8];
memset(hasSequence,0,256*8*sizeof(bool));
memset(sequenceIndex,0,256*8);
if (!reader.seek(18,SEEK_SET)) {
logE("premature end of file!");
@ -2046,17 +2064,327 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
delete[] file;
return false;
}
ds.version=(unsigned short)reader.readS();
ds.version=(unsigned short)reader.readI();
logI("module version %d (0x%.4x)",ds.version,ds.version);
if (ds.version>0x0440) {
if (ds.version>0x0450) {
logE("incompatible version %x!",ds.version);
lastError="incompatible version";
delete[] file;
return false;
}
while (true) {
blockName=reader.readString(3);
if (blockName=="END") {
// end of module
logD("end of data");
break;
}
// not the end
reader.seek(-3,SEEK_CUR);
blockName=reader.readString(16);
unsigned int blockVersion=(unsigned int)reader.readI();
unsigned int blockSize=(unsigned int)reader.readI();
size_t blockStart=reader.tell();
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
if (blockName=="PARAMS") {
CHECK_BLOCK_VERSION(6);
unsigned int oldSpeedTempo=0;
if (blockVersion<=1) {
oldSpeedTempo=reader.readI();
}
if (blockVersion>=2) {
expansions=reader.readC();
}
tchans=reader.readI();
unsigned int pal=reader.readI();
unsigned int customHz=reader.readI();
unsigned int newVibrato=0;
unsigned int speedSplitPoint=0;
if (blockVersion>=3) {
newVibrato=reader.readI();
}
if (blockVersion>=4) {
ds.hilightA=reader.readI();
ds.hilightB=reader.readI();
}
if (expansions&8) if (blockVersion>=5) { // N163 channels
n163Chans=reader.readI();
}
if (blockVersion>=6) {
speedSplitPoint=reader.readI();
}
logV("old speed/tempo: %d",oldSpeedTempo);
logV("expansions: %x",expansions);
logV("channels: %d",tchans);
logV("PAL: %d",pal);
logV("custom Hz: %d",customHz);
logV("new vibrato: %d",newVibrato);
logV("N163 channels: %d",n163Chans);
logV("highlight 1: %d",ds.hilightA);
logV("highlight 2: %d",ds.hilightB);
logV("split point: %d",speedSplitPoint);
if (customHz!=0) {
ds.hz=customHz;
}
// initialize channels
int systemID=0;
ds.system[systemID++]=DIV_SYSTEM_NES;
if (expansions&1) {
ds.system[systemID++]=DIV_SYSTEM_VRC6;
}
if (expansions&2) {
ds.system[systemID++]=DIV_SYSTEM_VRC7;
}
if (expansions&4) {
ds.system[systemID++]=DIV_SYSTEM_FDS;
}
if (expansions&8) {
ds.system[systemID++]=DIV_SYSTEM_MMC5;
}
if (expansions&16) {
ds.system[systemID]=DIV_SYSTEM_N163;
ds.systemFlags[systemID++]=n163Chans;
}
if (expansions&32) {
ds.system[systemID]=DIV_SYSTEM_AY8910;
ds.systemFlags[systemID++]=38; // Sunsoft 5B
}
ds.systemLen=systemID;
unsigned int calcChans=0;
for (int i=0; i<ds.systemLen; i++) {
calcChans+=getChannelCount(ds.system[i]);
}
if (calcChans!=tchans) {
logE("channel counts do not match! %d != %d",tchans,calcChans);
lastError="channel counts do not match";
delete[] file;
return false;
}
if (tchans>DIV_MAX_CHANS) {
tchans=DIV_MAX_CHANS;
logW("too many channels!");
}
} else if (blockName=="INFO") {
CHECK_BLOCK_VERSION(1);
ds.name=reader.readString(32);
ds.author=reader.readString(32);
ds.copyright=reader.readString(32);
} else if (blockName=="HEADER") {
CHECK_BLOCK_VERSION(3);
unsigned char totalSongs=reader.readC();
logV("%d songs:",totalSongs+1);
for (int i=0; i<=totalSongs; i++) {
String subSongName=reader.readString();
logV("- %s",subSongName);
}
for (unsigned int i=0; i<tchans; i++) {
unsigned char chID=reader.readC();
logV("for channel ID %d",chID);
for (int j=0; j<=totalSongs; j++) {
unsigned char effectCols=reader.readC();
if (j==0) {
ds.pat[i].effectCols=effectCols+1;
}
logV("- song %d has %d effect columns",j,effectCols);
}
}
} else if (blockName=="INSTRUMENTS") {
CHECK_BLOCK_VERSION(6);
ds.insLen=reader.readI();
if (ds.insLen<0 || ds.insLen>256) {
logE("too many instruments/out of range!");
lastError="too many instruments/out of range";
delete[] file;
return false;
}
for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
ds.ins.push_back(ins);
}
logV("instruments:");
for (int i=0; i<ds.insLen; i++) {
unsigned int insIndex=reader.readI();
if (insIndex>=ds.ins.size()) {
logE("instrument index %d is out of range!",insIndex);
lastError="instrument index out of range";
delete[] file;
return false;
}
DivInstrument* ins=ds.ins[insIndex];
unsigned char insType=reader.readC();
switch (insType) {
case 1:
ins->type=DIV_INS_STD;
break;
case 2: // TODO: tell VRC6 and VRC6 saw instruments apart
ins->type=DIV_INS_VRC6;
break;
case 3:
ins->type=DIV_INS_OPLL;
break;
case 4:
ins->type=DIV_INS_FDS;
break;
case 5:
ins->type=DIV_INS_N163;
break;
case 6: // 5B?
ins->type=DIV_INS_AY;
break;
default: {
logE("%d: invalid instrument type %d",insIndex,insType);
lastError="invalid instrument type";
delete[] file;
return false;
}
}
// instrument data
switch (ins->type) {
case DIV_INS_STD: {
unsigned int totalSeqs=reader.readI();
if (totalSeqs>5) {
logE("%d: too many sequences!",insIndex);
lastError="too many sequences";
delete[] file;
return false;
}
for (unsigned int j=0; j<totalSeqs; j++) {
hasSequence[insIndex][j]=reader.readC();
sequenceIndex[insIndex][j]=reader.readC();
}
const int dpcmNotes=(blockVersion>=2)?96:72;
for (int j=0; j<dpcmNotes; j++) {
ins->amiga.noteMap[j]=(short)((unsigned char)reader.readC())-1;
ins->amiga.noteFreq[j]=(unsigned char)reader.readC();
if (blockVersion>=6) {
reader.readC(); // DMC value
}
}
break;
}
case DIV_INS_VRC6: {
unsigned int totalSeqs=reader.readI();
if (totalSeqs>4) {
logE("%d: too many sequences!",insIndex);
lastError="too many sequences";
delete[] file;
return false;
}
for (unsigned int j=0; j<totalSeqs; j++) {
hasSequence[insIndex][j]=reader.readC();
sequenceIndex[insIndex][j]=reader.readC();
}
break;
}
case DIV_INS_OPLL: {
ins->fm.opllPreset=(unsigned int)reader.readI();
// TODO
break;
}
case DIV_INS_FDS: {
DivWavetable* wave=new DivWavetable;
wave->len=64;
wave->max=64;
for (int j=0; j<64; j++) {
wave->data[j]=reader.readC();
}
ins->std.waveMacro.len=1;
ins->std.waveMacro.val[0]=ds.wave.size();
for (int j=0; j<32; j++) {
ins->fds.modTable[j]=reader.readC()-3;
}
ins->fds.modSpeed=reader.readI();
ins->fds.modDepth=reader.readI();
reader.readI(); // this is delay. currently ignored. TODO.
ds.wave.push_back(wave);
ins->std.volMacro.len=reader.readC();
ins->std.volMacro.loop=reader.readI();
ins->std.volMacro.rel=reader.readI();
reader.readI(); // arp mode does not apply here
for (int j=0; j<ins->std.volMacro.len; j++) {
ins->std.volMacro.val[j]=reader.readC();
}
ins->std.arpMacro.len=reader.readC();
ins->std.arpMacro.loop=reader.readI();
ins->std.arpMacro.rel=reader.readI();
ins->std.arpMacro.mode=reader.readI();
for (int j=0; j<ins->std.arpMacro.len; j++) {
ins->std.arpMacro.val[j]=reader.readC();
}
ins->std.pitchMacro.len=reader.readC();
ins->std.pitchMacro.loop=reader.readI();
ins->std.pitchMacro.rel=reader.readI();
reader.readI(); // arp mode does not apply here
for (int j=0; j<ins->std.pitchMacro.len; j++) {
ins->std.pitchMacro.val[j]=reader.readC();
}
break;
}
case DIV_INS_N163: {
// TODO!
break;
}
// TODO: 5B!
default: {
logE("%d: what's going on here?",insIndex);
lastError="invalid instrument type";
delete[] file;
return false;
}
}
// name
ins->name=reader.readString((unsigned int)reader.readI());
logV("- %d: %s",insIndex,ins->name);
}
} else if (blockName=="SEQUENCES") {
CHECK_BLOCK_VERSION(6);
} else if (blockName=="FRAMES") {
CHECK_BLOCK_VERSION(3);
} else if (blockName=="PATTERNS") {
CHECK_BLOCK_VERSION(5);
} else if (blockName=="DPCM SAMPLES") {
CHECK_BLOCK_VERSION(1);
} else if (blockName=="SEQUENCES_VRC6") {
// where are the 5B and FDS sequences?
CHECK_BLOCK_VERSION(6);
} else if (blockName=="SEQUENCES_N163") {
CHECK_BLOCK_VERSION(1);
} else if (blockName=="COMMENTS") {
CHECK_BLOCK_VERSION(1);
} else {
logE("block %s is unknown!",blockName);
lastError="unknown block "+blockName;
delete[] file;
return false;
}
if ((reader.tell()-blockStart)!=blockSize) {
logE("block %s is incomplete!",blockName);
lastError="incomplete block "+blockName;
delete[] file;
return false;
}
}
} catch (EndOfFileException& e) {
logE("premature end of file!");
lastError="incomplete file";

View file

@ -506,6 +506,20 @@ void DivInstrument::putInsData(SafeWriter* w) {
// C64 no test
w->writeC(c64.noTest);
// MultiPCM
w->writeC(multipcm.ar);
w->writeC(multipcm.d1r);
w->writeC(multipcm.dl);
w->writeC(multipcm.d2r);
w->writeC(multipcm.rr);
w->writeC(multipcm.rc);
w->writeC(multipcm.lfo);
w->writeC(multipcm.vib);
w->writeC(multipcm.am);
for (int j=0; j<23; j++) { // reserved
w->writeC(0);
}
}
DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
@ -1014,6 +1028,21 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
c64.noTest=reader.readC();
}
// MultiPCM
if (version>=93) {
multipcm.ar=reader.readC();
multipcm.d1r=reader.readC();
multipcm.dl=reader.readC();
multipcm.d2r=reader.readC();
multipcm.rr=reader.readC();
multipcm.rc=reader.readC();
multipcm.lfo=reader.readC();
multipcm.vib=reader.readC();
multipcm.am=reader.readC();
// reserved
for (int k=0; k<23; k++) reader.readC();
}
return DIV_DATA_SUCCESS;
}

View file

@ -338,6 +338,16 @@ struct DivInstrumentFDS {
}
};
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
@ -393,6 +403,7 @@ struct DivInstrument {
DivInstrumentAmiga amiga;
DivInstrumentN163 n163;
DivInstrumentFDS fds;
DivInstrumentMultiPCM multipcm;
DivInstrumentWaveSynth ws;
/**

View file

@ -480,7 +480,7 @@ if (m_choffs == 0)
#endif
// early out if the envelope is effectively off
if (m_env_attenuation > EG_QUIET)
if (m_env_attenuation > EG_QUIET && m_cache.eg_shift == 0)
return 0;
// get the absolute value of the sin, as attenuation, as a 4.8 fixed point value

View file

@ -70,16 +70,24 @@
// OPZ supports a "fixed frequency" mode for each operator, with a 3-bit
// range and 4-bit frequency value, plus a 1-bit enable. Not sure how that
// works at all, so it's not implemented.
// note by tildearrow:
// - I have verified behavior of this mode against real hardware.
// after applying a small fix on the existing early implementation, it matches hardware.
// this means fixed frequency is fully implemented and working.
//
// There are also several mystery fields in the operators which I have no
// clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits).
// eg_shift is some kind of envelope generator effect, but how it works is
// unknown.
// note by tildearrow:
// - behavior of "fine" is now confirmed and matches hardware.
//
// Also, according to the site above, the panning controls are changed from
// OPM, with a "mono" bit and only one control bit for the right channel.
// Current implementation is just a guess.
//
// additional modifications by tildearrow for Furnace
//
namespace ymfm
{
@ -409,9 +417,6 @@ uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const
void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
{
// TODO: how does fixed frequency mode work? appears to be enabled by
// op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency()
// TODO: what is op_rev()?
// set up the easy stuff
@ -467,8 +472,8 @@ void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata
if (reverb != 0)
cache.eg_rate[EG_REVERB] = std::min<uint32_t>(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]);
// set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off"
cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs);
// set the envelope shift; TX81Z manual says operator 1 (actually operator 4) shift is fixed at "off"
cache.eg_shift = ((opoffs & 0x18) == 0x18) ? 0 : op_eg_shift(opoffs);
}

View file

@ -919,12 +919,13 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
}
case DIV_CMD_FM_FIXFREQ: {
if (c.value<0 || c.value>3) break;
printf("fixfreq %x\n",c.value2);
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
op.egt=(c.value2>0);
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
if (op.egt) {
rWrite(baseAddr+ADDR_MULT_DT,((c.value2>>4)&15)|((c.value2>>8)&7));
rWrite(baseAddr+ADDR_MULT_DT,((c.value2>>4)&15)|(((c.value2>>8)&7)<<4));
rWrite(baseAddr+ADDR_WS_FINE,(c.value2&15)|(op.ws<<4));
} else {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));

View file

@ -53,7 +53,7 @@ std::vector<DivInstrumentType>& DivEngine::getPossibleInsTypes() {
}
// TODO: rewrite this function (again). it's an unreliable mess.
const char* DivEngine::getSongSystemName() {
String DivEngine::getSongSystemName(bool isMultiSystemAcceptable) {
switch (song.systemLen) {
case 0:
return "help! what's going on!";
@ -198,7 +198,15 @@ const char* DivEngine::getSongSystemName() {
}
break;
}
return "multi-system";
if (isMultiSystemAcceptable) return "multi-system";
String ret="";
for (int i=0; i<song.systemLen; i++) {
if (i>0) ret+=" + ";
ret+=getSystemName(song.system[i]);
}
return ret;
}
const char* DivEngine::getSystemName(DivSystem sys) {

View file

@ -23,6 +23,7 @@
#include "plot_nolerp.h"
#include "guiConst.h"
#include <fmt/printf.h>
#include <imgui.h>
const char* sampleNote[12]={
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
@ -36,6 +37,7 @@ void FurnaceGUI::drawInsList() {
}
if (!insListOpen) return;
if (ImGui::Begin("Instruments",&insListOpen)) {
if (settings.unifiedDataView) settings.horizontalDataView=0;
if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) {
doAction(GUI_ACTION_INS_LIST_ADD);
}
@ -45,7 +47,7 @@ void FurnaceGUI::drawInsList() {
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) {
doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<=(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) {
@ -64,7 +66,12 @@ void FurnaceGUI::drawInsList() {
doAction(GUI_ACTION_INS_LIST_DELETE);
}
ImGui::Separator();
if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) {
int availableRows=ImGui::GetContentRegionAvail().y/ImGui::GetFrameHeight();
if (availableRows<1) availableRows=1;
int columns=settings.horizontalDataView?(int)(ceil((double)(e->song.ins.size()+1)/(double)availableRows)):1;
if (columns<1) columns=1;
if (columns>64) columns=64;
if (ImGui::BeginTable("InsListScroll",columns,(settings.horizontalDataView?ImGuiTableFlags_ScrollX:0)|ImGuiTableFlags_ScrollY)) {
if (settings.unifiedDataView) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -72,6 +79,11 @@ void FurnaceGUI::drawInsList() {
ImGui::Indent();
}
if (settings.horizontalDataView) {
ImGui::TableNextRow();
}
int curRow=0;
for (int i=-1; i<(int)e->song.ins.size(); i++) {
String name=ICON_FA_CIRCLE_O " - None -";
const char* insType="Bug!";
@ -211,8 +223,12 @@ void FurnaceGUI::drawInsList() {
} else {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (!settings.horizontalDataView) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
} else if (curRow==0) {
ImGui::TableNextColumn();
}
if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) {
curIns=i;
}
@ -228,6 +244,9 @@ void FurnaceGUI::drawInsList() {
nextWindow=GUI_WINDOW_INS_EDIT;
}
}
if (settings.horizontalDataView) {
if (++curRow>=availableRows) curRow=0;
}
}
if (settings.unifiedDataView) {

View file

@ -465,7 +465,11 @@ void FurnaceGUI::doAction(int what) {
break;
case GUI_ACTION_PAT_COLLAPSE:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse];
if (e->song.chanCollapse[cursor.xCoarse]==0) {
e->song.chanCollapse[cursor.xCoarse]=3;
} else if (e->song.chanCollapse[cursor.xCoarse]>0) {
e->song.chanCollapse[cursor.xCoarse]--;
}
break;
case GUI_ACTION_PAT_INCREASE_COLUMNS:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;

View file

@ -588,7 +588,7 @@ void FurnaceGUI::updateWindowTitle() {
}
if (settings.titleBarSys) {
title+=fmt::sprintf(" (%s)",e->getSongSystemName());
title+=fmt::sprintf(" (%s)",e->getSongSystemName(!settings.noMultiSystem));
}
if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str());
@ -2091,7 +2091,7 @@ void FurnaceGUI::editOptions(bool topMenu) {
}
ImGui::SameLine();
if (ImGui::Button("Values")) {
doTranspose(transposeAmount,opMaskTransposeNote);
doTranspose(transposeAmount,opMaskTransposeValue);
ImGui::CloseCurrentPopup();
}
@ -3133,7 +3133,7 @@ bool FurnaceGUI::loop() {
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
*e->song.ins[curIns]=*instruments[curIns];
*e->song.ins[curIns]=*instruments[0];
} else {
showError("...but you haven't selected an instrument!");
}

View file

@ -858,6 +858,8 @@ class FurnaceGUI {
int moveWindowTitle;
int hiddenSystems;
int insLoadAlwaysReplace;
int horizontalDataView;
int noMultiSystem;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -941,6 +943,8 @@ class FurnaceGUI {
moveWindowTitle(0),
hiddenSystems(0),
insLoadAlwaysReplace(1),
horizontalDataView(0),
noMultiSystem(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),

View file

@ -2587,6 +2587,108 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_MULTIPCM) {
if (ImGui::BeginTabItem("MultiPCM")) {
String sName;
if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) {
sName="none selected";
} else {
sName=e->song.sample[ins->amiga.initSample]->name;
}
if (ImGui::BeginCombo("Initial Sample",sName.c_str())) {
String id;
for (int i=0; i<e->song.sampleLen; i++) {
id=fmt::sprintf("%d: %s",i,e->song.sample[i]->name);
if (ImGui::Selectable(id.c_str(),ins->amiga.initSample==i)) {
ins->amiga.initSample=i;
PARAMETER
}
}
ImGui::EndCombo();
}
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableNextColumn();
CENTER_TEXT("AR");
ImGui::TextUnformatted("AR");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Attack Rate");
}
ImGui::TableNextColumn();
CENTER_TEXT("D1R");
ImGui::TextUnformatted("D1R");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Decay 1 Rate");
}
ImGui::TableNextColumn();
CENTER_TEXT("DL");
ImGui::TextUnformatted("DL");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Decay Level");
}
ImGui::TableNextColumn();
CENTER_TEXT("D2R");
ImGui::TextUnformatted("D2R");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Decay 2 Rate");
}
ImGui::TableNextColumn();
CENTER_TEXT("RR");
ImGui::TextUnformatted("RR");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Release Rate");
}
ImGui::TableNextColumn();
CENTER_TEXT("RC");
ImGui::TextUnformatted("RC");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Rate Correction");
}
ImGui::TableNextColumn();
CENTER_TEXT("Envelope");
ImGui::TextUnformatted("Envelope");
ImGui::TableNextRow();
ImGui::TableNextColumn();
P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN));
ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN));
ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN));
ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN));
ImGui::TableNextColumn();
P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN));
ImGui::TableNextColumn();
P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN));
ImGui::TableNextColumn();
drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type);
ImGui::EndTable();
}
if (ImGui::BeginTable("MultiPCMLFOParams",3,ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableNextColumn();
P(CWSliderScalar("LFO Rate",ImGuiDataType_U8,&ins->multipcm.lfo,&_ZERO,&_SEVEN)); rightClickable
ImGui::TableNextColumn();
P(CWSliderScalar("PM Depth",ImGuiDataType_U8,&ins->multipcm.vib,&_ZERO,&_SEVEN)); rightClickable
ImGui::TableNextColumn();
P(CWSliderScalar("AM Depth",ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable
ImGui::EndTable();
}
ImGui::EndTabItem();
}
}
if (ins->type==DIV_INS_GB ||
(ins->type==DIV_INS_AMIGA && ins->amiga.useWave) ||
ins->type==DIV_INS_X1_010 ||
@ -2718,7 +2820,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_AMIGA) {
volMax=64;
}
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SU) {
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) {
volMax=127;
}
if (ins->type==DIV_INS_GB) {
@ -2771,7 +2873,7 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Noise";
dutyMax=8;
}
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS) {
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) {
dutyMax=0;
}
if (ins->type==DIV_INS_VERA) {
@ -2803,6 +2905,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_SAA1099) waveMax=2;
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0;
if (ins->type==DIV_INS_MIKEY) waveMax=0;
if (ins->type==DIV_INS_MULTIPCM) waveMax=0;
if (ins->type==DIV_INS_SU) waveMax=7;
if (ins->type==DIV_INS_PET) {
waveMax=8;
@ -2863,6 +2966,11 @@ void FurnaceGUI::drawInsEdit() {
panMin=-16;
panMax=16;
}
if (ins->type==DIV_INS_MULTIPCM) {
panMin=-7;
panMax=7;
panSingleNoBit=true;
}
if (ins->type==DIV_INS_SU) {
panMin=-127;
panMax=127;
@ -2915,6 +3023,7 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_AY ||
ins->type==DIV_INS_AY8930 ||
ins->type==DIV_INS_SWAN ||
ins->type==DIV_INS_MULTIPCM ||
ins->type==DIV_INS_SU) {
NORMAL_MACRO(ins->std.phaseResetMacro,0,1,"phaseReset","Phase Reset",32,ins->std.phaseResetMacro.open,true,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[16],0,1,NULL,false);
}

View file

@ -1006,6 +1006,12 @@ void FurnaceGUI::drawSettings() {
updateWindowTitle();
}
bool noMultiSystemB=settings.noMultiSystem;
if (ImGui::Checkbox("Display chip names instead of \"multi-system\" in title bar",&noMultiSystemB)) {
settings.noMultiSystem=noMultiSystemB;
updateWindowTitle();
}
ImGui::Text("Status bar:");
if (ImGui::RadioButton("Cursor details##sbar0",settings.statusDisplay==0)) {
settings.statusDisplay=0;
@ -1077,6 +1083,16 @@ void FurnaceGUI::drawSettings() {
if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) {
settings.unifiedDataView=unifiedDataViewB;
}
if (settings.unifiedDataView) {
settings.horizontalDataView=0;
}
ImGui::BeginDisabled(settings.unifiedDataView);
bool horizontalDataViewB=settings.horizontalDataView;
if (ImGui::Checkbox("Horizontal instrument list",&horizontalDataViewB)) {
settings.horizontalDataView=horizontalDataViewB;
}
ImGui::EndDisabled();
bool chipNamesB=settings.chipNames;
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
@ -1837,6 +1853,8 @@ void FurnaceGUI::syncSettings() {
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0);
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1);
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -1908,6 +1926,8 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.moveWindowTitle,0,1);
clampSetting(settings.hiddenSystems,0,1);
clampSetting(settings.insLoadAlwaysReplace,0,1);
clampSetting(settings.horizontalDataView,0,1);
clampSetting(settings.noMultiSystem,0,1)
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
if (settings.initialSys.size()<4) {
@ -2020,6 +2040,8 @@ void FurnaceGUI::commitSettings() {
e->setConf("hiddenSystems",settings.hiddenSystems);
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace);
e->setConf("horizontalDataView",settings.horizontalDataView);
e->setConf("noMultiSystem",settings.noMultiSystem);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {