diff --git a/src/engine/fileOps/p.cpp b/src/engine/fileOps/p.cpp new file mode 100644 index 000000000..fd309dc85 --- /dev/null +++ b/src/engine/fileOps/p.cpp @@ -0,0 +1,131 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("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..367541600 --- /dev/null +++ b/src/engine/fileOps/p86.cpp @@ -0,0 +1,149 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("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..9da099a04 --- /dev/null +++ b/src/engine/fileOps/pdx.cpp @@ -0,0 +1,108 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("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..f5180a590 --- /dev/null +++ b/src/engine/fileOps/ppc.cpp @@ -0,0 +1,149 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("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..c4156a10d --- /dev/null +++ b/src/engine/fileOps/pps.cpp @@ -0,0 +1,132 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("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..c6e7956b4 --- /dev/null +++ b/src/engine/fileOps/pvi.cpp @@ -0,0 +1,165 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("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..33161401d --- /dev/null +++ b/src/engine/fileOps/pzi.cpp @@ -0,0 +1,162 @@ +/** + * 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 "shared.h" + +#ifdef HAVE_GUI +#include "../gui/gui.h" +extern FurnaceGUI g; +#endif + +#define _LE(string) (string) + +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=_LE("premature end of file"); + logE("premature end of file"); + } +} \ No newline at end of file