Merge pull request #2083 from tildearrow/sample_banks

Sample banks backport
This commit is contained in:
tildearrow 2024-08-18 05:53:36 -05:00 committed by GitHub
commit dca10faff2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1437 additions and 68 deletions

View file

@ -681,6 +681,14 @@ src/engine/fileOps/text.cpp
src/engine/fileOps/tfm.cpp
src/engine/fileOps/xm.cpp
src/engine/fileOps/p.cpp
src/engine/fileOps/p86.cpp
src/engine/fileOps/pdx.cpp
src/engine/fileOps/ppc.cpp
src/engine/fileOps/pps.cpp
src/engine/fileOps/pvi.cpp
src/engine/fileOps/pzi.cpp
src/engine/blip_buf.c
src/engine/brrUtils.c
src/engine/safeReader.cpp

View file

@ -620,6 +620,17 @@ class DivEngine {
void loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
//sample banks
void loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
bool initAudioBackend();
@ -1034,7 +1045,8 @@ class DivEngine {
int addSamplePtr(DivSample* which);
// get sample from file
DivSample* sampleFromFile(const char* path);
//DivSample* sampleFromFile(const char* path);
std::vector<DivSample*> sampleFromFile(const char* path);
// get raw sample
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles, int rate);

View file

@ -63,3 +63,34 @@ enum DivFurVariants: int {
DIV_FUR_VARIANT_VANILLA=0,
DIV_FUR_VARIANT_B=1,
};
// MIDI-related
struct midibank_t {
String name;
uint8_t bankMsb,
bankLsb;
};
// Reused patch data structures
// SBI and some other OPL containers
struct sbi_t {
uint8_t Mcharacteristics,
Ccharacteristics,
Mscaling_output,
Cscaling_output,
Meg_AD,
Ceg_AD,
Meg_SR,
Ceg_SR,
Mwave,
Cwave,
FeedConnect;
};
//bool stringNotBlank(String& str);
// detune needs extra translation from register to furnace format
//uint8_t fmDtRegisterToFurnace(uint8_t&& dtNative);
//void readSbiOpData(sbi_t& sbi, SafeReader& reader);

124
src/engine/fileOps/p.cpp Normal file
View file

@ -0,0 +1,124 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//P VOX ADPCM sample bank
/* =======================================
Header
=======================================
0x0000 - 0x03FF 256 * {
Sample start (uint32_t)
- 0x00000000 = unused
}
=======================================
Body
=======================================
Stream of Sample Data {
MSM6258 ADPCM encoding
nibble-swapped VOX / Dialogic ADPCM
Mono
Sample rate?
16000Hz seems fine
} */
#define P_BANK_SIZE 256
#define P_SAMPLE_RATE 16000
typedef struct
{
uint32_t start_pointer;
} P_HEADER;
void DivEngine::loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
P_HEADER headers[P_BANK_SIZE];
for(int i = 0; i < P_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned int)reader.readI_BE();
}
for(int i = 0; i < P_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0)
{
DivSample* s = new DivSample;
s->rate = P_SAMPLE_RATE;
s->centerRate = P_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_VOX;
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
int sample_len = 0;
if(i < P_BANK_SIZE - 1)
{
sample_len = headers[i + 1].start_pointer - headers[i].start_pointer;
}
else
{
sample_len = (int)reader.size() - headers[i].start_pointer;
}
if(sample_len > 0)
{
s->init(sample_len * 2);
for(int j = 0; j < sample_len; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
curr_byte = (curr_byte << 4) | (curr_byte >> 4);
s->dataVOX[sample_pos] = curr_byte;
sample_pos++;
}
ret.push_back(s);
logI("p: start %d len %d", headers[i].start_pointer, sample_len);
}
else
{
delete s;
}
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

142
src/engine/fileOps/p86.cpp Normal file
View file

@ -0,0 +1,142 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//P86 8-bit PCM sample bank
/* =======================================
Header
=======================================
0x0000 Identifier (12b)
"PCM86 DATA(\n)(\0)"
0x000C Targeted P86DRV version (1b)
version <high nibble>.<low nibble>
0x000D File Length (3b)
0x0010 - 0x060F 256 * {
Pointer to Sample Data Start (3b)
Length of Sample Data (3b)
(0x000000 0x000000 -> no sample for this instrument ID)
}
=======================================
Body
=======================================
Stream of Sample Data {
8-Bit Signed
Mono
16540Hz
(above sample rate according to KAJA's documentation
any sample rate possible, for different base note & octave)
} */
#define P86_BANK_SIZE 256
#define P86_SAMPLE_RATE 16540
#define P86_FILE_SIG "PCM86 DATA\n\0"
typedef struct
{
uint32_t start_pointer;
uint32_t sample_length;
} P86_HEADER;
#define UNUSED(x) (void)(x)
uint32_t read_3bytes(SafeReader& reader)
{
unsigned char arr[3];
for (int i = 0; i < 3; i++)
{
arr[i] = (unsigned char)reader.readC();
}
return (arr[0] | (arr[1] << 8) | (arr[2] << 16));
}
void DivEngine::loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
P86_HEADER headers[P86_BANK_SIZE];
String file_sig = reader.readString(12);
if(file_sig != P86_FILE_SIG) return;
uint8_t version = reader.readC();
UNUSED(version);
uint32_t file_size = read_3bytes(reader);
UNUSED(file_size);
for(int i = 0; i < P86_BANK_SIZE; i++)
{
headers[i].start_pointer = read_3bytes(reader);
headers[i].sample_length = read_3bytes(reader);
}
for(int i = 0; i < P86_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0 && headers[i].sample_length != 0)
{
DivSample* s = new DivSample;
s->rate = P86_SAMPLE_RATE;
s->centerRate = P86_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length); //byte per sample
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(uint32_t j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
//curr_byte += 0x80;
s->data8[sample_pos] = curr_byte;
sample_pos++;
}
ret.push_back(s);
logI("p86: start %06X len %06X", headers[i].start_pointer, headers[i].sample_length);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

101
src/engine/fileOps/pdx.cpp Normal file
View file

@ -0,0 +1,101 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PDX 8-bit OKI ADPCM sample bank
/* File format
The file starts with a header with 96 8-byte pairs, with each pair containing offset and length of ADPCM data chunks for each note value, like so:
[[byte pointer to sample in file -- 32-bit unsigned integer (4 bytes)] [empty: $0000 -- 16-bit integer] [length of sample, amount in bytes -- 16-bit unsigned integer] ×96] [ADPCM data] EOF
The first sample (1) is mapped to 0x80 (= the lowest note within MXDRV) and the last sample (96) is mapped to 0xDF (= the highest note within MXDRV).
Samples are encoded in 4-bit OKI ADPCM encoded nibbles, where each byte contains 2 nibbles: [nibble 2 << 4 || nibble 1]
Unfortunately, sample rates for each samples are not defined within the .PDX file and have to be set manually with the appropriate command for that at play-time */
#define PDX_BANK_SIZE 96
#define PDX_SAMPLE_RATE 16000
typedef struct
{
unsigned int start_pointer;
unsigned short sample_length;
} PDX_HEADER;
#define UNUSED(x) (void)(x)
void DivEngine::loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
PDX_HEADER headers[PDX_BANK_SIZE];
for(int i = 0; i < PDX_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned int)reader.readI_BE();
unsigned short empty = (unsigned short)reader.readS_BE(); //skip 1st 2 bytes
UNUSED(empty);
headers[i].sample_length = (unsigned short)reader.readS_BE();
}
for(int i = 0; i < PDX_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0 && headers[i].sample_length != 0)
{
DivSample* s = new DivSample;
s->rate = PDX_SAMPLE_RATE;
s->centerRate = PDX_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_VOX;
s->init(headers[i].sample_length * 2);
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(unsigned short j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
curr_byte = (curr_byte << 4) | (curr_byte >> 4);
s->dataVOX[sample_pos] = curr_byte;
sample_pos++;
}
ret.push_back(s);
logI("pdx: start %d len %d", headers[i].start_pointer, headers[i].sample_length);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

142
src/engine/fileOps/ppc.cpp Normal file
View file

@ -0,0 +1,142 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PPC PMD's YM2608 ADPCM-B sample bank
/* ========================================
General
========================================
ADPCM RAM addresses: see docs/common.txt {
address_start = 0x0026
file_header_size = 0x0420
}
========================================
Header
========================================
0x0000 Identifier (30b)
"ADPCM DATA for PMD ver.4.4- "
0x001E Address of End of Data (32B blocks) in ADPCM RAM (1h)
File Size == address -> file offset
0x0020 - 0x041F 256 * {
Start of Sample (32b blocks) in ADPCM RAM (1h)
End of Sample (32b blocks) in ADPCM RAM (1h)
(0x0000 0x0000 -> no sample for this instrument ID)
}
========================================
Body
========================================
Stream of Sample Data {
Yamaha ADPCM-B encoding (4-Bit Signed ADPCM)
Mono
16kHz
(above sample rate according to KAJA's documentation
any sample rate possible, for different base note & octave)
} */
#define PPC_FILE_SIG "ADPCM DATA for PMD ver.4.4- "
#define PPC_BANK_SIZE 256
#define PPC_SAMPLE_RATE 16000
typedef struct
{
uint16_t start_pointer;
uint16_t end_pointer;
} PPC_HEADER;
#define UNUSED(x) (void)(x)
#define ADPCM_DATA_START 0x0420
void DivEngine::loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
String file_sig = reader.readString(30);
unsigned short end_of_data = (unsigned short)reader.readS();
UNUSED(end_of_data);
if(file_sig != PPC_FILE_SIG) return;
PPC_HEADER headers[PPC_BANK_SIZE];
for(int i = 0; i < PPC_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned short)reader.readS();
headers[i].end_pointer = (unsigned short)reader.readS();
}
for(int i = 0; i < PPC_BANK_SIZE; i++)
{
if((headers[i].start_pointer != 0 || headers[i].end_pointer != 0) && headers[i].start_pointer < headers[i].end_pointer)
{
DivSample* s = new DivSample;
s->rate = PPC_SAMPLE_RATE;
s->centerRate = PPC_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
int sample_pos = 0;
int sample_length = (headers[i].end_pointer - headers[i].start_pointer) * 32;
//reader.seek(ADPCM_DATA_START + headers[i].start_pointer * 32, SEEK_SET);
for(int j = 0; j < sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
//curr_byte=(curr_byte<<4)|(curr_byte>>4);
s->dataB[sample_pos] = curr_byte;
sample_pos++;
}
logI("ppc: start %d end %d len in bytes %d", headers[i].start_pointer, headers[i].end_pointer, (headers[i].end_pointer - headers[i].start_pointer) * 32);
ret.push_back(s);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

125
src/engine/fileOps/pps.cpp Normal file
View file

@ -0,0 +1,125 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PPS AY-3-8910 sample bank
/* =======================================
Header
=======================================
0x0000 - 0x0053 14 * {
Pointer to Sample Data Start (1h)
Length of Sample Data (1h)
"Pitch"(?) (1b)
Volume Reduction (1b)
}
(0x0000 0x0000 [0x00 0x00] -> no sample for this instrument ID)
}
=======================================
Body
=======================================
Stream of Sample Data {
4-Bit Unsigned
(afaict)
Mono
16Hz
(based on tests, maybe alternatively 8kHz)
} */
#define PPS_BANK_SIZE 14
#define PPS_SAMPLE_RATE 16000
typedef struct
{
uint16_t start_pointer;
uint16_t sample_length;
uint8_t _pitch;
uint8_t _vol;
} PPS_HEADER;
void DivEngine::loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
PPS_HEADER headers[PPS_BANK_SIZE];
for(int i = 0; i < PPS_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned short)reader.readS();
headers[i].sample_length = (unsigned short)reader.readS();
headers[i]._pitch = (unsigned char)reader.readC();
headers[i]._vol = (unsigned char)reader.readC();
}
for(int i = 0; i < PPS_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0 || headers[i].sample_length != 0
|| headers[i]._pitch != 0 || headers[i]._vol != 0)
{
DivSample* s = new DivSample;
s->rate = PPS_SAMPLE_RATE;
s->centerRate = PPS_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length * 2); //byte per sample
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(int j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
s->data8[sample_pos] = (curr_byte >> 4) | (curr_byte & 0xf0);
s->data8[sample_pos] += 0x80;
sample_pos++;
s->data8[sample_pos] = (curr_byte << 4) | (curr_byte & 0xf);
s->data8[sample_pos] += 0x80;
sample_pos++;
}
ret.push_back(s);
logI("pps: start %d len %d", headers[i].start_pointer, headers[i].sample_length);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

158
src/engine/fileOps/pvi.cpp Normal file
View file

@ -0,0 +1,158 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PVI YM2608 ADPCM-B sample bank
/* =======================================
General
=======================================
ADPCM RAM addresses: see docs/common.txt {
address_start = 0x0000
file_header_size = 0x0210
}
=======================================
Header
=======================================
0x0000 Identifier (4b)
"PVI2"
0x0004 - 0x0007 Unknown Settings (4b)
Unknown mappings PCME switches <-> Values
"0x10 0x00 0x10 0x02" in all example files
First 1h may be Start Address in ADPCM RAM?
0x0008 - 0x0009 "Delta-N" playback frequency (1h)
Default 0x49BA "== 16kHz"??
0x000A Unknown (1b)
RAM type? (Docs indicate values 0 or 8, all example files have value 0x02?)
0x000B Amount of defined Samples (1b)
0x000C - 0x000F Unknown (4b)
Padding?
"0x00 0x00 0x00 0x00" in all example files
0x0010 - 0x020F 128 * {
Start of Sample (32b blocks) in ADPCM RAM (1h)
End of Sample (32b blocks) in ADPCM RAM (1h)
(0x0000 0x0000 -> no sample for this instrument ID)
}
=======================================
Body
=======================================
Stream of Sample Data {
Yamaha ADPCM-B encoding (4-Bit Signed ADPCM)
Mono
Sample rate as specified earlier
(examples i have seems Stereo or 32kHz despite "16kHz" playback frequency setting?)
} */
#define PVIV2_FILE_SIG "PVI2"
#define PVIV1_FILE_SIG "PVI1"
#define PVI_BANK_SIZE 128
#define PVI_SAMPLE_RATE 16000
typedef struct
{
uint16_t start_pointer;
uint16_t end_pointer;
} PVI_HEADER;
#define UNUSED(x) (void)(x)
#define ADPCM_DATA_START 0x0210
void DivEngine::loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
String file_sig = reader.readString(4);
if(file_sig != PVIV1_FILE_SIG && file_sig != PVIV2_FILE_SIG) return;
unsigned int unknown_settings = (unsigned int)reader.readI();
UNUSED(unknown_settings);
unsigned short delta_n = (unsigned short)reader.readS();
UNUSED(delta_n);
unsigned char one_byte = (unsigned char)reader.readC();
UNUSED(one_byte);
unsigned char amount_of_samples = (unsigned char)reader.readC();
UNUSED(amount_of_samples);
unsigned int padding = (unsigned int)reader.readI();
UNUSED(padding);
PVI_HEADER headers[PVI_BANK_SIZE];
for(int i = 0; i < PVI_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned short)reader.readS();
headers[i].end_pointer = (unsigned short)reader.readS();
}
for(int i = 0; i < PVI_BANK_SIZE; i++)
{
if((headers[i].start_pointer != 0 || headers[i].end_pointer != 0) && headers[i].start_pointer < headers[i].end_pointer)
{
DivSample* s = new DivSample;
s->rate = PVI_SAMPLE_RATE;
s->centerRate = PVI_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
int sample_pos = 0;
int sample_length = (headers[i].end_pointer - headers[i].start_pointer) * 32;
reader.seek(ADPCM_DATA_START + headers[i].start_pointer * 32, SEEK_SET);
for(int j = 0; j < sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
//curr_byte=(curr_byte<<4)|(curr_byte>>4);
s->dataB[sample_pos] = curr_byte;
sample_pos++;
}
logI("pvi: start %d end %d len in bytes %d", headers[i].start_pointer, headers[i].end_pointer, (headers[i].end_pointer - headers[i].start_pointer) * 32);
ret.push_back(s);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

155
src/engine/fileOps/pzi.cpp Normal file
View file

@ -0,0 +1,155 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PZI 8-bit PCM sample bank
/* =======================================
Header
=======================================
0x0000 Identifier (4b)
"PZI1"
0x0004 - 0x001F Unknown (28b)
Part of identifier? Settings?
All (\0)s in all example files
0x0020 - 0x091F 128 * {
Start of Sample after header (2h)
Length of Sample (2h)
Offset of loop start from sample start (2h)
Offset of loop end from sample start (2h)
Sample rate (1h)
(0xFFFFFFFF 0xFFFFFFFF loop offsets -> no loop information)
}
=======================================
Body
=======================================
Stream of Sample Data {
Unsigned 8-Bit
Mono
Sample rate as specified in header
} */
#define PZI_BANK_SIZE 128
#define PZI_FILE_SIG "PZI1"
#define NO_LOOP (0xFFFFFFFFU)
#define SAMPLE_DATA_OFFSET 0x0920
#define MAX_SANITY_CAP 9999999
#define HEADER_JUNK_SIZE 28
typedef struct
{
uint32_t start_pointer;
uint32_t sample_length;
uint32_t loop_start;
uint32_t loop_end;
uint16_t sample_rate;
} PZI_HEADER;
#define UNUSED(x) (void)(x)
void DivEngine::loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
PZI_HEADER headers[PZI_BANK_SIZE];
String file_sig = reader.readString(4);
if(file_sig != PZI_FILE_SIG) return;
for (int i = 0; i < HEADER_JUNK_SIZE; i++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
UNUSED(curr_byte);
}
for(int i = 0; i < PZI_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned int)reader.readI();
headers[i].sample_length = (unsigned int)reader.readI();
headers[i].loop_start = (unsigned int)reader.readI();
headers[i].loop_end = (unsigned int)reader.readI();
headers[i].sample_rate = (unsigned short)reader.readS();
}
for(int i = 0; i < PZI_BANK_SIZE; i++)
{
if (headers[i].start_pointer < MAX_SANITY_CAP && headers[i].sample_length < MAX_SANITY_CAP &&
headers[i].loop_start < MAX_SANITY_CAP && headers[i].loop_end < MAX_SANITY_CAP &&
headers[i].start_pointer > 0 && headers[i].sample_length > 0)
{
DivSample* s = new DivSample;
s->rate = headers[i].sample_rate;
s->centerRate = headers[i].sample_rate;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length); //byte per sample
reader.seek((int)headers[i].start_pointer + SAMPLE_DATA_OFFSET, SEEK_SET);
int sample_pos = 0;
for (uint32_t j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
curr_byte += 0x80;
s->data8[sample_pos] = curr_byte;
sample_pos++;
}
if (headers[i].loop_start != NO_LOOP && headers[i].loop_end != NO_LOOP)
{
s->loop = true;
s->loopMode = DIV_SAMPLE_LOOP_FORWARD;
s->loopStart = headers[i].loop_start;
s->loopEnd = headers[i].loop_end;
}
ret.push_back(s);
logI("pzi: start %d len %d sample rate %d loop start %d loop end %d", headers[i].start_pointer, headers[i].sample_length,
headers[i].sample_rate, headers[i].loop_start, headers[i].loop_end);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

View file

@ -19,21 +19,6 @@
#include "fileOpsCommon.h"
// SBI and some other OPL containers
struct sbi_t {
uint8_t Mcharacteristics,
Ccharacteristics,
Mscaling_output,
Cscaling_output,
Meg_AD,
Ceg_AD,
Meg_SR,
Ceg_SR,
Mwave,
Cwave,
FeedConnect;
};
static void readSbiOpData(sbi_t& sbi, SafeReader& reader) {
sbi.Mcharacteristics = reader.readC();
sbi.Ccharacteristics = reader.readC();

View file

@ -24,10 +24,12 @@
#include "sfWrapper.h"
#endif
DivSample* DivEngine::sampleFromFile(const char* path) {
std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
std::vector<DivSample*> ret;
if (song.sample.size()>=256) {
lastError="too many samples!";
return NULL;
return ret;
}
BUSY_BEGIN;
warnings="";
@ -58,6 +60,110 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
}
extS+=i;
}
if(extS == ".pps" || extS == ".ppc" || extS == ".pvi" ||
extS == ".pdx" || extS == ".pzi" || extS == ".p86" ||
extS == ".p") //sample banks!
{
String stripPath;
const char* pathReduxEnd=strrchr(pathRedux,'.');
if (pathReduxEnd==NULL) {
stripPath=pathRedux;
} else {
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
stripPath+=*i;
}
}
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
lastError=strerror(errno);
return ret;
}
unsigned char* buf;
ssize_t len;
if (fseek(f,0,SEEK_END)!=0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
len=ftell(f);
if (len<0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (len==(SIZE_MAX>>1)) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (len==0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (fseek(f,0,SEEK_SET)!=0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire sample bank file buffer!");
lastError=_("did not read entire sample bank file!");
delete[] buf;
return ret;
}
fclose(f);
SafeReader reader = SafeReader(buf,len);
if(extS == ".pps")
{
loadPPS(reader,ret,stripPath);
}
if(extS == ".ppc")
{
loadPPC(reader,ret,stripPath);
}
if(extS == ".pvi")
{
loadPVI(reader,ret,stripPath);
}
if(extS == ".pdx")
{
loadPDX(reader,ret,stripPath);
}
if(extS == ".pzi")
{
loadPZI(reader,ret,stripPath);
}
if(extS == ".p86")
{
loadP86(reader,ret,stripPath);
}
if(extS == ".p")
{
loadP(reader,ret,stripPath);
}
if((int)ret.size() > 0)
{
int counter = 0;
for(DivSample* s: ret)
{
s->name = fmt::sprintf("%s sample %d", stripPath, counter);
counter++;
}
}
delete[] buf; //done with buffer
BUSY_END;
return ret;
}
if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr
size_t len=0;
DivSample* sample=new DivSample;
@ -68,7 +174,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
delete sample;
return NULL;
return ret;
}
if (fseek(f,0,SEEK_END)<0) {
@ -76,7 +182,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
delete sample;
return NULL;
return ret;
}
len=ftell(f);
@ -86,7 +192,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError="file is empty!";
delete sample;
return NULL;
return ret;
}
if (len==(SIZE_MAX>>1)) {
@ -94,7 +200,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError="file is invalid!";
delete sample;
return NULL;
return ret;
}
if (fseek(f,0,SEEK_SET)<0) {
@ -102,7 +208,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
delete sample;
return NULL;
return ret;
}
if (extS==".dmc") {
@ -120,7 +226,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError="wait... is that right? no I don't think so...";
delete sample;
return NULL;
return ret;
}
unsigned char* dataBuf=sample->dataDPCM;
@ -147,14 +253,14 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError="BRR sample is empty!";
delete sample;
return NULL;
return ret;
}
} else if ((len%9)!=0) {
fclose(f);
BUSY_END;
lastError="possibly corrupt BRR sample!";
delete sample;
return NULL;
return ret;
}
}
@ -163,16 +269,17 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END;
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
delete sample;
return NULL;
return ret;
}
BUSY_END;
return sample;
ret.push_back(sample);
return ret;
}
}
#ifndef HAVE_SNDFILE
lastError="Furnace was not compiled with libsndfile!";
return NULL;
return ret;
#else
SF_INFO si;
SFWrapper sfWrap;
@ -186,13 +293,13 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
} else {
lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err));
}
return NULL;
return ret;
}
if (si.frames>16777215) {
lastError="this sample is too big! max sample size is 16777215.";
sfWrap.doClose();
BUSY_END;
return NULL;
return ret;
}
void* buf=NULL;
sf_count_t sampleLen=sizeof(short);
@ -298,7 +405,8 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
if (sample->centerRate>64000) sample->centerRate=64000;
sfWrap.doClose();
BUSY_END;
return sample;
ret.push_back(sample);
return ret;
#endif
}

View file

@ -3828,8 +3828,9 @@ bool FurnaceGUI::loop() {
}
int sampleCountBefore=e->song.sampleLen;
std::vector<DivInstrument*> instruments=e->instrumentFromFile(ev.drop.file,true,settings.readInsNames);
std::vector<DivSample*> samples = e->sampleFromFile(ev.drop.file);
DivWavetable* droppedWave=NULL;
DivSample* droppedSample=NULL;
//DivSample* droppedSample=NULL;
if (!instruments.empty()) {
if (e->song.sampleLen!=sampleCountBefore) {
e->renderSamplesP();
@ -3854,10 +3855,24 @@ bool FurnaceGUI::loop() {
}
nextWindow=GUI_WINDOW_WAVE_LIST;
MARK_MODIFIED;
} else if ((droppedSample=e->sampleFromFile(ev.drop.file))!=NULL) {
}
else if (!samples.empty())
{
if (e->song.sampleLen!=sampleCountBefore) {
//e->renderSamplesP();
}
if (!e->getWarnings().empty())
{
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
int sampleCount=-1;
sampleCount=e->addSamplePtr(droppedSample);
if (sampleCount>=0 && settings.selectAssetOnLoad) {
for (DivSample* s: samples)
{
sampleCount=e->addSamplePtr(s);
}
//sampleCount=e->addSamplePtr(droppedSample);
if (sampleCount>=0 && settings.selectAssetOnLoad)
{
curSample=sampleCount;
updateSampleTex=true;
}
@ -5319,50 +5334,89 @@ bool FurnaceGUI::loop() {
String errs=_("there were some errors while loading samples:\n");
bool warn=false;
for (String i: fileDialog->getFileName()) {
DivSample* s=e->sampleFromFile(i.c_str());
if (s==NULL) {
std::vector<DivSample*> samples=e->sampleFromFile(i.c_str());
if (samples.empty()) {
if (fileDialog->getFileName().size()>1) {
warn=true;
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
} else {;
showError(e->getLastError());
}
} else {
if (e->addSamplePtr(s)==-1) {
if (fileDialog->getFileName().size()>1) {
}
else
{
if((int)samples.size() == 1)
{
if (e->addSamplePtr(samples[0]) == -1)
{
if (fileDialog->getFileName().size()>1)
{
warn=true;
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
} else {
}
else
{
showError(e->getLastError());
}
} else {
}
else
{
MARK_MODIFIED;
}
}
else
{
for (DivSample* s: samples) { //ask which samples to load!
pendingSamples.push_back(std::make_pair(s,false));
}
displayPendingSamples=true;
replacePendingSample = false;
}
}
}
if (warn) {
showWarning(errs,GUI_WARN_GENERIC);
}
break;
}
case GUI_FILE_SAMPLE_OPEN_REPLACE: {
DivSample* s=e->sampleFromFile(copyOfName.c_str());
if (s==NULL) {
case GUI_FILE_SAMPLE_OPEN_REPLACE:
{
std::vector<DivSample*> samples=e->sampleFromFile(copyOfName.c_str());
if (samples.empty())
{
showError(e->getLastError());
} else {
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
e->lockEngine([this,s]() {
}
else
{
if((int)samples.size() == 1)
{
if (curSample>=0 && curSample<(int)e->song.sample.size())
{
DivSample* s = samples[0];
e->lockEngine([this, s]()
{
// if it crashes here please tell me...
DivSample* oldSample=e->song.sample[curSample];
e->song.sample[curSample]=s;
e->song.sample[curSample]= s;
delete oldSample;
e->renderSamples();
MARK_MODIFIED;
});
updateSampleTex=true;
} else {
}
else
{
showError(_("...but you haven't selected a sample!"));
delete s;
delete samples[0];
}
}
else
{
for (DivSample* s: samples) { //ask which samples to load!
pendingSamples.push_back(std::make_pair(s,false));
}
displayPendingSamples=true;
replacePendingSample = true;
}
}
break;
@ -5741,6 +5795,11 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup(_("Select Instrument"));
}
if (displayPendingSamples) {
displayPendingSamples=false;
ImGui::OpenPopup(_("Select Sample"));
}
if (displayPendingRawSample) {
displayPendingRawSample=false;
ImGui::OpenPopup(_("Import Raw Sample"));
@ -6569,6 +6628,191 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
// TODO: fix style
centerNextWindow(_("Select Sample"),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Select Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
bool quitPlease=false;
ImGui::AlignTextToFramePadding();
ImGui::Text(_("this is a sample bank! select which ones to load:"));
ImGui::SameLine();
if (ImGui::Button(_("All"))) {
for (std::pair<DivSample*,bool>& i: pendingSamples) {
i.second=true;
}
}
ImGui::SameLine();
if (ImGui::Button(_("None"))) {
for (std::pair<DivSample*,bool>& i: pendingSamples) {
i.second=false;
}
}
bool reissueSearch=false;
bool anySelected=false;
float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingSamples.size();
if (sizeY>(canvasH-180.0*dpiScale))
{
sizeY=canvasH-180.0*dpiScale;
if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale;
}
if (ImGui::BeginTable("PendingSamplesList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY)))
{
if (sampleBankSearchQuery.empty())
{
for (size_t i=0; i<pendingSamples.size(); i++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
String id=fmt::sprintf("%d: %s",(int)i,pendingSamples[i].first->name);
if (pendingInsSingle)
{
if (ImGui::Selectable(id.c_str()))
{
pendingSamples[i].second=true;
quitPlease=true;
}
}
else
{
// TODO:fixstyle from hereonwards
ImGuiIO& io = ImGui::GetIO();
if(ImGui::Checkbox(id.c_str(),&pendingSamples[i].second) && io.KeyShift)
{
for(int jj = (int)i - 1; jj >= 0; jj--)
{
if(pendingSamples[jj].second) //pressed shift and there's selected item above
{
for(int k = jj; k < (int)i; k++)
{
pendingSamples[k].second = true;
}
break;
}
}
}
}
if (pendingSamples[i].second) anySelected=true;
}
}
else //display search results
{
if(reissueSearch)
{
String lowerCase=sampleBankSearchQuery;
for (char& ii: lowerCase)
{
if (ii>='A' && ii<='Z') ii+='a'-'A';
}
sampleBankSearchResults.clear();
for (int j=0; j < (int)pendingSamples.size(); j++)
{
String lowerCase1 = pendingSamples[j].first->name;
for (char& ii: lowerCase1)
{
if (ii>='A' && ii<='Z') ii+='a'-'A';
}
if (lowerCase1.find(lowerCase)!=String::npos)
{
sampleBankSearchResults.push_back(std::make_pair(pendingSamples[j].first, pendingSamples[j].second));
}
}
}
for (size_t i=0; i<sampleBankSearchResults.size(); i++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
String id=fmt::sprintf("%d: %s",(int)i,sampleBankSearchResults[i].first->name);
ImGuiIO& io = ImGui::GetIO();
if(ImGui::Checkbox(id.c_str(),&sampleBankSearchResults[i].second) && io.KeyShift)
{
for(int jj = (int)i - 1; jj >= 0; jj--)
{
if(sampleBankSearchResults[jj].second) //pressed shift and there's selected item above
{
for(int k = jj; k < (int)i; k++)
{
sampleBankSearchResults[k].second = true;
}
break;
}
}
}
if (sampleBankSearchResults[i].second) anySelected=true;
}
for (size_t i=0; i<pendingSamples.size(); i++)
{
if(sampleBankSearchResults.size() > 0)
{
for (size_t j=0; j<sampleBankSearchResults.size(); j++)
{
if(sampleBankSearchResults[j].first == pendingSamples[i].first && sampleBankSearchResults[j].second && pendingSamples[i].first != NULL)
{
pendingSamples[i].second = true;
if (pendingSamples[i].second) anySelected=true;
break;
}
}
}
}
}
ImGui::EndTable();
}
ImGui::BeginDisabled(!anySelected);
if (ImGui::Button(_("OK"))) {
quitPlease=true;
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button(_("Cancel")) || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
for (std::pair<DivSample*,bool>& i: pendingSamples) {
i.second=false;
}
quitPlease=true;
}
if (quitPlease)
{
ImGui::CloseCurrentPopup();
int counter = 0;
for (std::pair<DivSample*,bool>& i: pendingSamples)
{
if (!i.second)
{
delete i.first;
}
else
{
if(counter == 0 && replacePendingSample)
{
*e->song.sample[curSample]=*i.first;
replacePendingSample = false;
}
else
{
e->addSamplePtr(i.first);
}
}
counter++;
}
curSample = (int)e->song.sample.size() - 1;
pendingSamples.clear();
}
ImGui::EndPopup();
}
centerNextWindow(_("Import Raw Sample"),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Import Raw Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text(_("Data type:"));
@ -7581,7 +7825,15 @@ bool FurnaceGUI::init() {
#endif
compatFormats+="*.dmc ";
compatFormats+="*.brr";
compatFormats+="*.brr ";
compatFormats+="*.ppc ";
compatFormats+="*.pps ";
compatFormats+="*.pvi ";
compatFormats+="*.pdx ";
compatFormats+="*.pzi ";
compatFormats+="*.p86 ";
compatFormats+="*.p";
audioLoadFormats[1]=compatFormats;
audioLoadFormats.push_back(_("NES DPCM data"));
@ -7590,6 +7842,27 @@ bool FurnaceGUI::init() {
audioLoadFormats.push_back(_("SNES Bit Rate Reduction"));
audioLoadFormats.push_back("*.brr");
audioLoadFormats.push_back(_("PMD YM2608 ADPCM-B sample bank"));
audioLoadFormats.push_back("*.ppc");
audioLoadFormats.push_back(_("PDR 4-bit AY-3-8910 sample bank"));
audioLoadFormats.push_back("*.pps");
audioLoadFormats.push_back(_("FMP YM2608 ADPCM-B sample bank"));
audioLoadFormats.push_back("*.pvi");
audioLoadFormats.push_back(_("MDX OKI ADPCM sample bank"));
audioLoadFormats.push_back("*.pdx");
audioLoadFormats.push_back(_("FMP 8-bit PCM sample bank"));
audioLoadFormats.push_back("*.pzi");
audioLoadFormats.push_back(_("PMD 8-bit PCM sample bank"));
audioLoadFormats.push_back("*.p86");
audioLoadFormats.push_back(_("PMD OKI ADPCM sample bank"));
audioLoadFormats.push_back("*.p");
audioLoadFormats.push_back(_("all files"));
audioLoadFormats.push_back("*");
@ -8012,6 +8285,8 @@ FurnaceGUI::FurnaceGUI():
snesFilterHex(false),
modTableHex(false),
displayEditString(false),
displayPendingSamples(false),
replacePendingSample(false),
displayExportingROM(false),
changeCoarse(false),
mobileEdit(false),

View file

@ -1592,7 +1592,7 @@ class FurnaceGUI {
int sampleTexW, sampleTexH;
bool updateSampleTex;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport;
String workingDirFont, workingDirColors, workingDirKeybinds;
@ -1604,6 +1604,7 @@ class FurnaceGUI {
String folderString;
std::vector<DivSystem> sysSearchResults;
std::vector<std::pair<DivSample*,bool>> sampleBankSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults;
std::vector<int> paletteSearchResults;
FixedQueue<String,32> recentFile;
@ -1619,6 +1620,7 @@ class FurnaceGUI {
bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd, noteInputPoly, notifyWaveChange;
bool wantScrollListIns, wantScrollListWave, wantScrollListSample;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool displayPendingSamples, replacePendingSample;
bool displayExportingROM;
bool changeCoarse;
bool mobileEdit;
@ -2215,7 +2217,7 @@ class FurnaceGUI {
maxUndoSteps(100),
vibrationStrength(0.5f),
vibrationLength(20),
s3mOPL3(0),
s3mOPL3(1),
mainFontPath(""),
headFontPath(""),
patFontPath(""),
@ -2372,6 +2374,7 @@ class FurnaceGUI {
std::vector<DivCommand> cmdStream;
std::vector<Particle> particles;
std::vector<std::pair<DivInstrument*,bool>> pendingIns;
std::vector<std::pair<DivSample*,bool>> pendingSamples;
std::vector<FurnaceGUISysCategory> sysCategories;

View file

@ -4754,7 +4754,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f);
settings.vibrationLength=conf.getInt("vibrationLength",20);
settings.s3mOPL3=conf.getInt("s3mOPL3",0);
settings.s3mOPL3=conf.getInt("s3mOPL3",1);
settings.backupEnable=conf.getInt("backupEnable",1);
settings.backupInterval=conf.getInt("backupInterval",30);