mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-26 14:33:01 +00:00
Merge pull request #2083 from tildearrow/sample_banks
Sample banks backport
This commit is contained in:
commit
dca10faff2
15 changed files with 1437 additions and 68 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
124
src/engine/fileOps/p.cpp
Normal 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
142
src/engine/fileOps/p86.cpp
Normal 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
101
src/engine/fileOps/pdx.cpp
Normal 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
142
src/engine/fileOps/ppc.cpp
Normal 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
125
src/engine/fileOps/pps.cpp
Normal 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
158
src/engine/fileOps/pvi.cpp
Normal 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
155
src/engine/fileOps/pzi.cpp
Normal 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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
317
src/gui/gui.cpp
317
src/gui/gui.cpp
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue