diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e7e09a98..7fdaad377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -683,6 +683,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 diff --git a/demos/arcade/U.N. Owen Was Her (NS2).fur b/demos/arcade/U.N. Owen Was Her (NS2).fur new file mode 100644 index 000000000..1d5d9e350 Binary files /dev/null and b/demos/arcade/U.N. Owen Was Her (NS2).fur differ diff --git a/src/engine/engine.h b/src/engine/engine.h index cfa958809..6d56f1ad2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -619,6 +619,17 @@ class DivEngine { void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); void loadWOPL(SafeReader& reader, std::vector& ret, String& stripPath); void loadWOPN(SafeReader& reader, std::vector& ret, String& stripPath); + + //sample banks + void loadP(SafeReader& reader, std::vector& ret, String& stripPath); + void loadPPC(SafeReader& reader, std::vector& ret, String& stripPath); + void loadPPS(SafeReader& reader, std::vector& ret, String& stripPath); + void loadPVI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadPDX(SafeReader& reader, std::vector& ret, String& stripPath); + void loadPZI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadP86(SafeReader& reader, std::vector& ret, String& stripPath); + + int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); @@ -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 sampleFromFile(const char* path); // get raw sample DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles, int rate); diff --git a/src/engine/fileOps/fileOpsCommon.h b/src/engine/fileOps/fileOpsCommon.h index 306166c55..d3821bf3b 100644 --- a/src/engine/fileOps/fileOpsCommon.h +++ b/src/engine/fileOps/fileOpsCommon.h @@ -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); diff --git a/src/engine/fileOps/p.cpp b/src/engine/fileOps/p.cpp new file mode 100644 index 000000000..9ba650c0e --- /dev/null +++ b/src/engine/fileOps/p.cpp @@ -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& 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"); + } +} \ No newline at end of file diff --git a/src/engine/fileOps/p86.cpp b/src/engine/fileOps/p86.cpp new file mode 100644 index 000000000..560867007 --- /dev/null +++ b/src/engine/fileOps/p86.cpp @@ -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 . +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& 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"); + } +} \ No newline at end of file diff --git a/src/engine/fileOps/pdx.cpp b/src/engine/fileOps/pdx.cpp new file mode 100644 index 000000000..cd03369a7 --- /dev/null +++ b/src/engine/fileOps/pdx.cpp @@ -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& 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"); + } +} \ No newline at end of file diff --git a/src/engine/fileOps/ppc.cpp b/src/engine/fileOps/ppc.cpp new file mode 100644 index 000000000..3c5144dbd --- /dev/null +++ b/src/engine/fileOps/ppc.cpp @@ -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& 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"); + } +} \ No newline at end of file diff --git a/src/engine/fileOps/pps.cpp b/src/engine/fileOps/pps.cpp new file mode 100644 index 000000000..39c573342 --- /dev/null +++ b/src/engine/fileOps/pps.cpp @@ -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& 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"); + } +} diff --git a/src/engine/fileOps/pvi.cpp b/src/engine/fileOps/pvi.cpp new file mode 100644 index 000000000..4181ea60d --- /dev/null +++ b/src/engine/fileOps/pvi.cpp @@ -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& 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"); + } +} \ No newline at end of file diff --git a/src/engine/fileOps/pzi.cpp b/src/engine/fileOps/pzi.cpp new file mode 100644 index 000000000..906e1f43b --- /dev/null +++ b/src/engine/fileOps/pzi.cpp @@ -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& 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"); + } +} \ No newline at end of file diff --git a/src/engine/fileOps/s3m.cpp b/src/engine/fileOps/s3m.cpp index 9e212d8d3..11d0f4fb4 100644 --- a/src/engine/fileOps/s3m.cpp +++ b/src/engine/fileOps/s3m.cpp @@ -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(); diff --git a/src/engine/fileOpsSample.cpp b/src/engine/fileOpsSample.cpp index 26d921ac8..5008392f8 100644 --- a/src/engine/fileOpsSample.cpp +++ b/src/engine/fileOpsSample.cpp @@ -24,10 +24,12 @@ #include "sfWrapper.h" #endif -DivSample* DivEngine::sampleFromFile(const char* path) { +std::vector DivEngine::sampleFromFile(const char* path) { + std::vector 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 } diff --git a/src/engine/platform/oplAInterface.cpp b/src/engine/platform/oplAInterface.cpp index 37ebda7f0..9643b80c0 100644 --- a/src/engine/platform/oplAInterface.cpp +++ b/src/engine/platform/oplAInterface.cpp @@ -27,7 +27,7 @@ uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t a if (adpcmBMem==NULL) { return 0; } - return adpcmBMem[address&0xffffff]; + return adpcmBMem[address&0x3ffff]; default: return 0; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 37443c46e..95e8ffd36 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3828,8 +3828,9 @@ bool FurnaceGUI::loop() { } int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(ev.drop.file,true,settings.readInsNames); + std::vector 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,24 +5334,43 @@ 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 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) { - warn=true; - errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); - } else { - showError(e->getLastError()); + } + 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 + { + showError(e->getLastError()); + } + } + else + { + MARK_MODIFIED; } - } 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; } } } @@ -5345,24 +5379,44 @@ bool FurnaceGUI::loop() { } 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 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]() { - // if it crashes here please tell me... - DivSample* oldSample=e->song.sample[curSample]; - e->song.sample[curSample]=s; - delete oldSample; - e->renderSamples(); - MARK_MODIFIED; - }); - updateSampleTex=true; - } else { - showError(_("...but you haven't selected a sample!")); - delete 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; + delete oldSample; + e->renderSamples(); + MARK_MODIFIED; + }); + updateSampleTex=true; + } + else + { + showError(_("...but you haven't selected a sample!")); + 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& i: pendingSamples) { + i.second=true; + } + } + ImGui::SameLine(); + if (ImGui::Button(_("None"))) { + for (std::pair& 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; iname); + 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; iname); + + 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 0) + { + for (size_t j=0; j& i: pendingSamples) { + i.second=false; + } + quitPlease=true; + } + if (quitPlease) + { + ImGui::CloseCurrentPopup(); + int counter = 0; + for (std::pair& 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), diff --git a/src/gui/gui.h b/src/gui/gui.h index 86a68a8f5..03ccb7cbd 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1593,7 +1593,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; @@ -1605,6 +1605,7 @@ class FurnaceGUI { String folderString; std::vector sysSearchResults; + std::vector> sampleBankSearchResults; std::vector newSongSearchResults; std::vector paletteSearchResults; FixedQueue recentFile; @@ -1620,6 +1621,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; @@ -2216,7 +2218,7 @@ class FurnaceGUI { maxUndoSteps(100), vibrationStrength(0.5f), vibrationLength(20), - s3mOPL3(0), + s3mOPL3(1), mainFontPath(""), headFontPath(""), patFontPath(""), @@ -2373,6 +2375,7 @@ class FurnaceGUI { std::vector cmdStream; std::vector particles; std::vector> pendingIns; + std::vector> pendingSamples; std::vector sysCategories; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index e2b85a709..487352206 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1203,28 +1203,63 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") } ); + SUB_ENTRY( + "Sega TeraDrive", { + CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") + } + ); + SUB_SUB_ENTRY( + "Sega TeraDrive (extended channel 3)", { + CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") + } + ); + SUB_SUB_ENTRY( + "Sega TeraDrive (CSM)", { + CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") + } + ); + SUB_SUB_ENTRY( + "Sega TeraDrive (DualPCM)", { + CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") + } + ); + SUB_SUB_ENTRY( + "Sega TeraDrive (DualPCM, extended channel 3)", { + CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""), + CH(DIV_SYSTEM_SMS, 0.5f, 0, ""), + CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") + } + ); ENTRY( _("Sharp X1"), { CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3") - } + } ); SUB_ENTRY( _("Sharp X1 + FM Addon"), { CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3"), CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2") - } + } ); ENTRY( _("Sharp X68000"), { CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"), CH(DIV_SYSTEM_MSM6258, 1.0f, 0, "clockSel=2") - } + } ); ENTRY( _("FM-7"), { CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=12"), CH(DIV_SYSTEM_YM2203, 1.0f, 0, "clockSel=5") - } + } ); SUB_ENTRY( _("FM-7 (extended channel 3)"), { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c4a8c657a..ab3523bc8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4755,7 +4755,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);