aboutsummaryrefslogtreecommitdiff
path: root/src/wopl
diff options
context:
space:
mode:
authorVitaly Novichkov <admin@wohlnet.ru>2018-04-20 02:00:06 +0300
committerVitaly Novichkov <admin@wohlnet.ru>2018-04-20 02:00:06 +0300
commitf1ea9ba9fee23d80b8c662ba9f4762c6ba0746ad (patch)
treea1463ec5cfba274ae3abb77b56849f4a2514c825 /src/wopl
parent58e055fc0a43d583a4638b7bd6a5e20c536fd49c (diff)
downloadlibADLMIDI-f1ea9ba9fee23d80b8c662ba9f4762c6ba0746ad.tar.gz
libADLMIDI-f1ea9ba9fee23d80b8c662ba9f4762c6ba0746ad.tar.bz2
libADLMIDI-f1ea9ba9fee23d80b8c662ba9f4762c6ba0746ad.zip
Use the same WOPL_File parser from OPL3 Bank Editor in libADLMIDI
TODO: port this into Gen-ADLDATA too! No more need to update same file parser twice. Just, copy-paste the same WOPL parser between of OPL3 Bank Editor and libADLMIDI.
Diffstat (limited to 'src/wopl')
-rw-r--r--src/wopl/wopl_file.c582
-rw-r--r--src/wopl/wopl_file.h282
2 files changed, 864 insertions, 0 deletions
diff --git a/src/wopl/wopl_file.c b/src/wopl/wopl_file.c
new file mode 100644
index 0000000..1d4ceeb
--- /dev/null
+++ b/src/wopl/wopl_file.c
@@ -0,0 +1,582 @@
+/*
+ * Wohlstand's OPL3 Bank File - a bank format to store OPL3 timbre data and setup
+ *
+ * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "wopl_file.h"
+#include <string.h>
+#include <stdlib.h>
+
+static const char *wopl3_magic = "WOPL3-BANK\0";
+static const char *wopli_magic = "WOPL3-INST\0";
+
+static const uint16_t wopl_latest_version = 3;
+
+#define WOPL_INST_SIZE_V2 62
+#define WOPL_INST_SIZE_V3 66
+
+static uint16_t toUint16LE(const uint8_t *arr)
+{
+ uint16_t num = arr[0];
+ num |= ((arr[1] << 8) & 0xFF00);
+ return num;
+}
+
+static uint16_t toUint16BE(const uint8_t *arr)
+{
+ uint16_t num = arr[1];
+ num |= ((arr[0] << 8) & 0xFF00);
+ return num;
+}
+
+static int16_t toSint16BE(const uint8_t *arr)
+{
+ int16_t num = *(const int8_t *)(&arr[0]);
+ num *= 1 << 8;
+ num |= arr[1];
+ return num;
+}
+
+static void fromUint16LE(uint16_t in, uint8_t *arr)
+{
+ arr[0] = in & 0x00FF;
+ arr[1] = (in >> 8) & 0x00FF;
+}
+
+static void fromUint16BE(uint16_t in, uint8_t *arr)
+{
+ arr[1] = in & 0x00FF;
+ arr[0] = (in >> 8) & 0x00FF;
+}
+
+static void fromSint16BE(int16_t in, uint8_t *arr)
+{
+ arr[1] = in & 0x00FF;
+ arr[0] = (in >> 8) & 0x00FF;
+}
+
+
+WOPLFile *WOPL_Init(uint16_t melodic_banks, uint16_t percussive_banks)
+{
+ WOPLFile *file = NULL;
+ if(melodic_banks == 0)
+ return NULL;
+ if(percussive_banks == 0)
+ return NULL;
+ file = (WOPLFile*)calloc(1, sizeof(WOPLFile));
+ if(!file)
+ return NULL;
+ file->banks_count_melodic = melodic_banks;
+ file->banks_count_percussion = percussive_banks;
+ file->banks_melodic = (WOPLBank*)calloc(1, sizeof(WOPLBank) * melodic_banks );
+ file->banks_percussive = (WOPLBank*)calloc(1, sizeof(WOPLBank) * percussive_banks );
+ return file;
+}
+
+void WOPL_Free(WOPLFile *file)
+{
+ if(file)
+ {
+ if(file->banks_melodic)
+ free(file->banks_melodic);
+ if(file->banks_percussive)
+ free(file->banks_percussive);
+ free(file);
+ }
+}
+
+int WOPL_BanksCmp(const WOPLFile *bank1, const WOPLFile *bank2)
+{
+ int res = 1;
+
+ res &= (bank1->version == bank2->version);
+ res &= (bank1->opl_flags == bank2->opl_flags);
+ res &= (bank1->volume_model == bank2->volume_model);
+ res &= (bank1->banks_count_melodic == bank2->banks_count_melodic);
+ res &= (bank1->banks_count_percussion == bank2->banks_count_percussion);
+
+ if(res)
+ {
+ int i;
+ for(i = 0; i < bank1->banks_count_melodic; i++)
+ res &= (memcmp(&bank1->banks_melodic[i], &bank2->banks_melodic[i], sizeof(WOPLBank)) == 0);
+ if(res)
+ {
+ for(i = 0; i < bank1->banks_count_percussion; i++)
+ res &= (memcmp(&bank1->banks_percussive[i], &bank2->banks_percussive[i], sizeof(WOPLBank)) == 0);
+ }
+ }
+
+ return res;
+}
+
+static void WOPL_parseInstrument(WOPLInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays)
+{
+ int l;
+ strncpy(ins->inst_name, (const char*)cursor, 32);
+ ins->note_offset1 = toSint16BE(cursor + 32);
+ ins->note_offset2 = toSint16BE(cursor + 34);
+ ins->midi_velocity_offset = (int8_t)cursor[36];
+ ins->second_voice_detune = (int8_t)cursor[37];
+ ins->percussion_key_number = cursor[38];
+ ins->inst_flags = cursor[39];
+ ins->fb_conn1_C0 = cursor[40];
+ ins->fb_conn2_C0 = cursor[41];
+ for(l = 0; l < 4; l++)
+ {
+ size_t off = 42 + (size_t)(l) * 5;
+ ins->operators[l].avekf_20 = cursor[off + 0];
+ ins->operators[l].ksl_l_40 = cursor[off + 1];
+ ins->operators[l].atdec_60 = cursor[off + 2];
+ ins->operators[l].susrel_80 = cursor[off + 3];
+ ins->operators[l].waveform_E0 = cursor[off + 4];
+ }
+ if((version >= 3) && has_sounding_delays)
+ {
+ ins->delay_on_ms = toUint16BE(cursor + 62);
+ ins->delay_off_ms = toUint16BE(cursor + 64);
+ }
+}
+
+static void WOPL_writeInstrument(WOPLInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays)
+{
+ int l;
+ strncpy((char*)cursor, ins->inst_name, 32);
+ fromSint16BE(ins->note_offset1, cursor + 32);
+ fromSint16BE(ins->note_offset2, cursor + 34);
+ cursor[36] = (uint8_t)ins->midi_velocity_offset;
+ cursor[37] = (uint8_t)ins->second_voice_detune;
+ cursor[38] = ins->percussion_key_number;
+ cursor[39] = ins->inst_flags;
+ cursor[40] = ins->fb_conn1_C0;
+ cursor[41] = ins->fb_conn2_C0;
+ for(l = 0; l < 4; l++)
+ {
+ size_t off = 42 + (size_t)(l) * 5;
+ cursor[off + 0] = ins->operators[l].avekf_20;
+ cursor[off + 1] = ins->operators[l].ksl_l_40;
+ cursor[off + 2] = ins->operators[l].atdec_60;
+ cursor[off + 3] = ins->operators[l].susrel_80;
+ cursor[off + 4] = ins->operators[l].waveform_E0;
+ }
+ if((version >= 3) && has_sounding_delays)
+ {
+ fromUint16BE(ins->delay_on_ms, cursor + 62);
+ fromUint16BE(ins->delay_off_ms, cursor + 64);
+ }
+}
+
+WOPLFile *WOPL_LoadBankFromMem(void *mem, size_t length, int *error)
+{
+ WOPLFile *outFile = NULL;
+ uint16_t i = 0, j = 0, k = 0;
+ uint16_t version = 0;
+ uint16_t count_melodic_banks = 1;
+ uint16_t count_percusive_banks = 1;
+ uint8_t *cursor = (uint8_t *)mem;
+
+ WOPLBank *bankslots[2];
+ uint16_t bankslots_sizes[2];
+
+#define SET_ERROR(err) \
+{\
+ WOPL_Free(outFile);\
+ if(error)\
+ {\
+ *error = err;\
+ }\
+}
+
+#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
+
+ if(!cursor)
+ {
+ SET_ERROR(WOPL_ERR_NULL_POINTER);
+ return NULL;
+ }
+
+ {/* Magic number */
+ if(length < 11)
+ {
+ SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING);
+ return NULL;
+ }
+ if(memcmp(cursor, wopl3_magic, 11) != 0)
+ {
+ SET_ERROR(WOPL_ERR_BAD_MAGIC);
+ return NULL;
+ }
+ GO_FORWARD(11);
+ }
+
+ {/* Version code */
+ if(length < 2)
+ {
+ SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING);
+ return NULL;
+ }
+ version = toUint16LE(cursor);
+ if(version > wopl_latest_version)
+ {
+ SET_ERROR(WOPL_ERR_NEWER_VERSION);
+ return NULL;
+ }
+ GO_FORWARD(2);
+ }
+
+ {/* Header of WOPL */
+ uint8_t head[6];
+ if(length < 6)
+ {
+ SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING);
+ return NULL;
+ }
+ memcpy(head, cursor, 6);
+ count_melodic_banks = toUint16BE(head);
+ count_percusive_banks = toUint16BE(head + 2);
+ GO_FORWARD(6);
+
+ outFile = WOPL_Init(count_melodic_banks, count_percusive_banks);
+ if(!outFile)
+ {
+ SET_ERROR(WOPL_ERR_OUT_OF_MEMORY);
+ return NULL;
+ }
+
+ outFile->version = version;
+ outFile->opl_flags = head[4];
+ outFile->volume_model = head[5];
+ }
+
+ bankslots_sizes[0] = count_melodic_banks;
+ bankslots[0] = outFile->banks_melodic;
+ bankslots_sizes[1] = count_percusive_banks;
+ bankslots[1] = outFile->banks_percussive;
+
+ if(version >= 2) /* Bank names and LSB/MSB titles */
+ {
+ for(i = 0; i < 2; i++)
+ {
+ for(j = 0; j < bankslots_sizes[i]; j++)
+ {
+ if(length < 34)
+ {
+ SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING);
+ return NULL;
+ }
+ strncpy(bankslots[i][j].bank_name, (const char*)cursor, 32);
+ bankslots[i][j].bank_midi_lsb = cursor[32];
+ bankslots[i][j].bank_midi_msb = cursor[33];
+ GO_FORWARD(34);
+ }
+ }
+ }
+
+ {/* Read instruments data */
+ uint16_t insSize = 0;
+ if(version > 2)
+ insSize = WOPL_INST_SIZE_V3;
+ else
+ insSize = WOPL_INST_SIZE_V2;
+ for(i = 0; i < 2; i++)
+ {
+ if(length < (insSize * 128) * (size_t)bankslots_sizes[i])
+ {
+ SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING);
+ return NULL;
+ }
+
+ for(j = 0; j < bankslots_sizes[i]; j++)
+ {
+ for(k = 0; k < 128; k++)
+ {
+ WOPLInstrument *ins = &bankslots[i][j].ins[k];
+ WOPL_parseInstrument(ins, cursor, version, 1);
+ GO_FORWARD(insSize);
+ }
+ }
+ }
+ }
+
+#undef GO_FORWARD
+#undef SET_ERROR
+
+ return outFile;
+}
+
+int WOPL_LoadInstFromMem(WOPIFile *file, void *mem, size_t length)
+{
+ uint16_t version = 0;
+ uint8_t *cursor = (uint8_t *)mem;
+ uint16_t ins_size;
+
+ if(!cursor)
+ return WOPL_ERR_NULL_POINTER;
+
+#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
+
+ {/* Magic number */
+ if(length < 11)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ if(memcmp(cursor, wopli_magic, 11) != 0)
+ return WOPL_ERR_BAD_MAGIC;
+ GO_FORWARD(11);
+ }
+
+ {/* Version code */
+ if(length < 2)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ version = toUint16LE(cursor);
+ if(version > wopl_latest_version)
+ return WOPL_ERR_NEWER_VERSION;
+ GO_FORWARD(2);
+ }
+
+ {/* is drum flag */
+ if(length < 1)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ file->is_drum = *cursor;
+ GO_FORWARD(1);
+ }
+
+ if(version > 2)
+ /* Skip sounding delays are not part of single-instrument file
+ * two sizes of uint16_t will be subtracted */
+ ins_size = WOPL_INST_SIZE_V3 - (sizeof(uint16_t) * 2);
+ else
+ ins_size = WOPL_INST_SIZE_V2;
+
+ if(length < ins_size)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+
+ WOPL_parseInstrument(&file->inst, cursor, version, 0);
+ GO_FORWARD(ins_size);
+
+ return WOPL_ERR_OK;
+#undef GO_FORWARD
+}
+
+size_t WOPL_CalculateBankFileSize(WOPLFile *file, uint16_t version)
+{
+ size_t final_size = 0;
+ size_t ins_size = 0;
+
+ if(version == 0)
+ version = wopl_latest_version;
+
+ if(!file)
+ return 0;
+ final_size += 11 + 2 + 2 + 2 + 1 + 1;
+ /*
+ * Magic number,
+ * Version,
+ * Count of melodic banks,
+ * Count of percussive banks,
+ * Chip specific flags
+ * Volume Model
+ */
+
+ if(version >= 2)
+ {
+ /* Melodic banks meta-data */
+ final_size += (32 + 1 + 1) * file->banks_count_melodic;
+ /* Percussive banks meta-data */
+ final_size += (32 + 1 + 1) * file->banks_count_percussion;
+ }
+
+ if(version >= 3)
+ ins_size = WOPL_INST_SIZE_V3;
+ else
+ ins_size = WOPL_INST_SIZE_V2;
+ /* Melodic instruments */
+ final_size += (ins_size * 128) * file->banks_count_melodic;
+ /* Percusive instruments */
+ final_size += (ins_size * 128) * file->banks_count_percussion;
+
+ return final_size;
+}
+
+size_t WOPL_CalculateInstFileSize(WOPIFile *file, uint16_t version)
+{
+ size_t final_size = 0;
+ size_t ins_size = 0;
+
+ if(version == 0)
+ version = wopl_latest_version;
+
+ if(!file)
+ return 0;
+ final_size += 11 + 2 + 1;
+ /*
+ * Magic number,
+ * version,
+ * is percussive instrument
+ */
+
+ if(version >= 3)
+ ins_size = WOPL_INST_SIZE_V3;
+ else
+ ins_size = WOPL_INST_SIZE_V2;
+ final_size += ins_size * 128;
+
+ return final_size;
+}
+
+int WOPL_SaveBankToMem(WOPLFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm)
+{
+ uint8_t *cursor = (uint8_t *)dest_mem;
+ uint16_t ins_size = 0;
+ uint16_t i, j, k;
+ uint16_t banks_melodic = force_gm ? 1 : file->banks_count_melodic;
+ uint16_t banks_percusive = force_gm ? 1 : file->banks_count_percussion;
+
+ WOPLBank *bankslots[2];
+ uint16_t bankslots_sizes[2];
+
+ if(version == 0)
+ version = wopl_latest_version;
+
+#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
+
+ if(length < 11)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ memcpy(cursor, wopl3_magic, 11);
+ GO_FORWARD(11);
+
+ if(length < 2)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ fromUint16LE(version, cursor);
+ GO_FORWARD(2);
+
+ if(length < 2)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ fromUint16BE(banks_melodic, cursor);
+ GO_FORWARD(2);
+
+ if(length < 2)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ fromUint16BE(banks_percusive, cursor);
+ GO_FORWARD(2);
+
+ if(length < 2)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ cursor[0] = file->opl_flags;
+ cursor[1] = file->volume_model;
+ GO_FORWARD(2);
+
+ bankslots[0] = file->banks_melodic;
+ bankslots_sizes[0] = banks_melodic;
+ bankslots[1] = file->banks_percussive;
+ bankslots_sizes[1] = banks_percusive;
+
+ if(version >= 2)
+ {
+ for(i = 0; i < 2; i++)
+ {
+ for(j = 0; j < bankslots_sizes[i]; j++)
+ {
+ if(length < 34)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ strncpy((char*)cursor, bankslots[i][j].bank_name, 32);
+ cursor[32] = bankslots[i][j].bank_midi_lsb;
+ cursor[33] = bankslots[i][j].bank_midi_msb;
+ GO_FORWARD(34);
+ }
+ }
+ }
+
+ {/* Write instruments data */
+ if(version >= 3)
+ ins_size = WOPL_INST_SIZE_V3;
+ else
+ ins_size = WOPL_INST_SIZE_V2;
+ for(i = 0; i < 2; i++)
+ {
+ if(length < (ins_size * 128) * (size_t)bankslots_sizes[i])
+ return WOPL_ERR_UNEXPECTED_ENDING;
+
+ for(j = 0; j < bankslots_sizes[i]; j++)
+ {
+ for(k = 0; k < 128; k++)
+ {
+ WOPLInstrument *ins = &bankslots[i][j].ins[k];
+ WOPL_writeInstrument(ins, cursor, version, 1);
+ GO_FORWARD(ins_size);
+ }
+ }
+ }
+ }
+
+ return WOPL_ERR_OK;
+#undef GO_FORWARD
+}
+
+int WOPL_SaveInstToMem(WOPIFile *file, void *dest_mem, size_t length, uint16_t version)
+{
+ uint8_t *cursor = (uint8_t *)dest_mem;
+ uint16_t ins_size;
+
+ if(!cursor)
+ return WOPL_ERR_NULL_POINTER;
+
+ if(version == 0)
+ version = wopl_latest_version;
+
+#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
+
+ {/* Magic number */
+ if(length < 11)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ memcpy(cursor, wopli_magic, 11);
+ GO_FORWARD(11);
+ }
+
+ {/* Version code */
+ if(length < 2)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ fromUint16LE(version, cursor);
+ GO_FORWARD(2);
+ }
+
+ {/* is drum flag */
+ if(length < 1)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+ *cursor = file->is_drum;
+ GO_FORWARD(1);
+ }
+
+ if(version > 2)
+ /* Skip sounding delays are not part of single-instrument file
+ * two sizes of uint16_t will be subtracted */
+ ins_size = WOPL_INST_SIZE_V3 - (sizeof(uint16_t) * 2);
+ else
+ ins_size = WOPL_INST_SIZE_V2;
+
+ if(length < ins_size)
+ return WOPL_ERR_UNEXPECTED_ENDING;
+
+ WOPL_writeInstrument(&file->inst, cursor, version, 0);
+ GO_FORWARD(ins_size);
+
+ return WOPL_ERR_OK;
+#undef GO_FORWARD
+}
diff --git a/src/wopl/wopl_file.h b/src/wopl/wopl_file.h
new file mode 100644
index 0000000..bf88a50
--- /dev/null
+++ b/src/wopl/wopl_file.h
@@ -0,0 +1,282 @@
+/*
+ * Wohlstand's OPL3 Bank File - a bank format to store OPL3 timbre data and setup
+ *
+ * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef WOPL_FILE_H
+#define WOPL_FILE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Global OPL flags */
+typedef enum
+{
+ /* Enable Deep-Tremolo flag */
+ WOPL_FLAG_DEEP_TREMOLO = 0x01,
+ /* Enable Deep-Vibrato flag */
+ WOPL_FLAG_DEEP_VIBRATO = 0x02
+} WOPLFileFlags;
+
+/* Volume scaling model implemented in the libADLMIDI */
+typedef enum
+{
+ WOPL_VM_Generic = 0,
+ WOPL_VM_Native,
+ WOPL_VM_DMX,
+ WOPL_VM_Apogee,
+ WOPL_VM_Win9x
+} WOPL_VolumeModel;
+
+typedef enum
+{
+ /* Is two-operator single-voice instrument (no flags) */
+ WOPL_Ins_2op = 0x00,
+ /* Is true four-operator instrument */
+ WOPL_Ins_4op = 0x01,
+ /* Is pseudo four-operator (two 2-operator voices) instrument */
+ WOPL_Ins_Pseudo4op = 0x02,
+ /* Is a blank instrument entry */
+ WOPL_Ins_IsBlank = 0x04,
+
+ /* RythmMode flags mask */
+ WOPL_RythmModeMask = 0x38,
+
+ /* Mask of the flags range */
+ WOPL_Ins_ALL_MASK = 0x07
+} WOPL_InstrumentFlags;
+
+typedef enum
+{
+ /* RythmMode: BassDrum */
+ WOPL_RM_BassDrum = 0x08,
+ /* RythmMode: Snare */
+ WOPL_RM_Snare = 0x10,
+ /* RythmMode: TomTom */
+ WOPL_RM_TomTom = 0x18,
+ /* RythmMode: Cymbell */
+ WOPL_RM_Cymball = 0x20,
+ /* RythmMode: HiHat */
+ WOPL_RM_HiHat = 0x28
+} WOPL_RythmMode;
+
+/* Error codes */
+typedef enum
+{
+ WOPL_ERR_OK = 0,
+ /* Magic number is not maching */
+ WOPL_ERR_BAD_MAGIC,
+ /* Too short file */
+ WOPL_ERR_UNEXPECTED_ENDING,
+ /* Zero banks count */
+ WOPL_ERR_INVALID_BANKS_COUNT,
+ /* Version of file is newer than supported by current version of library */
+ WOPL_ERR_NEWER_VERSION,
+ /* Out of memory */
+ WOPL_ERR_OUT_OF_MEMORY,
+ /* Given null pointer memory data */
+ WOPL_ERR_NULL_POINTER
+} WOPL_ErrorCodes;
+
+/* Operator indeces inside of Instrument Entry */
+#define WOPL_OP_CARRIER1 0
+#define WOPL_OP_MODULATOR1 1
+#define WOPL_OP_CARRIER2 2
+#define WOPL_OP_MODULATOR2 3
+
+/* OPL3 Oerators data */
+typedef struct
+{
+ /* AM/Vib/Env/Ksr/FMult characteristics */
+ uint8_t avekf_20;
+ /* Key Scale Level / Total level register data */
+ uint8_t ksl_l_40;
+ /* Attack / Decay */
+ uint8_t atdec_60;
+ /* Systain and Release register data */
+ uint8_t susrel_80;
+ /* Wave form */
+ uint8_t waveform_E0;
+} WOPLOperator;
+
+/* Instrument entry */
+typedef struct
+{
+ /* Title of the instrument */
+ char inst_name[34];
+ /* MIDI note key (half-tone) offset for an instrument (or a first voice in pseudo-4-op mode) */
+ int16_t note_offset1;
+ /* MIDI note key (half-tone) offset for a second voice in pseudo-4-op mode */
+ int16_t note_offset2;
+ /* MIDI note velocity offset (taken from Apogee TMB format) */
+ int8_t midi_velocity_offset;
+ /* Second voice detune level (taken from DMX OP2) */
+ int8_t second_voice_detune;
+ /* Percussion MIDI base tone number at which this drum will be played */
+ uint8_t percussion_key_number;
+ /* Enum WOPL_InstrumentFlags */
+ uint8_t inst_flags;
+ /* Feedback&Connection register for first and second operators */
+ uint8_t fb_conn1_C0;
+ /* Feedback&Connection register for third and fourth operators */
+ uint8_t fb_conn2_C0;
+ /* Operators register data */
+ WOPLOperator operators[4];
+ /* Millisecond delay of sounding while key is on */
+ uint16_t delay_on_ms;
+ /* Millisecond delay of sounding after key off */
+ uint16_t delay_off_ms;
+} WOPLInstrument;
+
+/* Bank entry */
+typedef struct
+{
+ /* Name of bank */
+ char bank_name[33];
+ /* MIDI Bank LSB code */
+ uint8_t bank_midi_lsb;
+ /* MIDI Bank MSB code */
+ uint8_t bank_midi_msb;
+ /* Instruments data of this bank */
+ WOPLInstrument ins[128];
+} WOPLBank;
+
+/* Instrument data file */
+typedef struct
+{
+ /* Version of instrument file */
+ uint16_t version;
+ /* Is this a percussion instrument */
+ uint8_t is_drum;
+ /* Instrument data */
+ WOPLInstrument inst;
+} WOPIFile;
+
+/* Bank data file */
+typedef struct
+{
+ /* Version of bank file */
+ uint16_t version;
+ /* Count of melodic banks in this file */
+ uint16_t banks_count_melodic;
+ /* Count of percussion banks in this file */
+ uint16_t banks_count_percussion;
+ /* Enum WOPLFileFlags */
+ uint8_t opl_flags;
+ /* Enum WOPL_VolumeModel */
+ uint8_t volume_model;
+ /* dynamically allocated data Melodic banks array */
+ WOPLBank *banks_melodic;
+ /* dynamically allocated data Percussive banks array */
+ WOPLBank *banks_percussive;
+} WOPLFile;
+
+
+/**
+ * @brief Initialize blank WOPL data structure with allocated bank data
+ * @param melodic_banks Count of melodic banks
+ * @param percussive_banks Count of percussive banks
+ * @return pointer to heap-allocated WOPL data structure or NULL when out of memory or incorrectly given banks counts
+ */
+extern WOPLFile *WOPL_Init(uint16_t melodic_banks, uint16_t percussive_banks);
+
+/**
+ * @brief Clean up WOPL data file (all allocated bank arrays will be fried too)
+ * @param file pointer to heap-allocated WOPL data structure
+ */
+extern void WOPL_Free(WOPLFile *file);
+
+/**
+ * @brief Compare two bank entries
+ * @param bank1 First bank
+ * @param bank2 Second bank
+ * @return 1 if banks are equal or 0 if there are different
+ */
+extern int WOPL_BanksCmp(const WOPLFile *bank1, const WOPLFile *bank2);
+
+
+/**
+ * @brief Load WOPL bank file from the memory.
+ * WOPL data structure will be allocated. (don't forget to clear it with WOPL_Free() after use!)
+ * @param mem Pointer to memory block contains raw WOPL bank file data
+ * @param length Length of given memory block
+ * @param error pointer to integer to return an error code. Pass NULL if you don't want to use error codes.
+ * @return Heap-allocated WOPL file data structure or NULL if any error has occouped
+ */
+extern WOPLFile *WOPL_LoadBankFromMem(void *mem, size_t length, int *error);
+
+/**
+ * @brief Load WOPI instrument file from the memory.
+ * You must allocate WOPIFile structure by yourself and give the pointer to it.
+ * @param file Pointer to destinition WOPIFile structure to fill it with parsed data.
+ * @param mem Pointer to memory block contains raw WOPI instrument file data
+ * @param length Length of given memory block
+ * @return 0 if no errors occouped, or an error code of WOPL_ErrorCodes enumeration
+ */
+extern int WOPL_LoadInstFromMem(WOPIFile *file, void *mem, size_t length);
+
+/**
+ * @brief Calculate the size of the output memory block
+ * @param file Heap-allocated WOPL file data structure
+ * @param version Destinition version of the file
+ * @return Size of the raw WOPL file data
+ */
+extern size_t WOPL_CalculateBankFileSize(WOPLFile *file, uint16_t version);
+
+/**
+ * @brief Calculate the size of the output memory block
+ * @param file Pointer to WOPI file data structure
+ * @param version Destinition version of the file
+ * @return Size of the raw WOPI file data
+ */
+extern size_t WOPL_CalculateInstFileSize(WOPIFile *file, uint16_t version);
+
+/**
+ * @brief Write raw WOPL into given memory block
+ * @param file Heap-allocated WOPL file data structure
+ * @param dest_mem Destinition memory block pointer
+ * @param length Length of destinition memory block
+ * @param version Wanted WOPL version
+ * @param force_gm Force GM set in saved bank file
+ * @return Error code or 0 on success
+ */
+extern int WOPL_SaveBankToMem(WOPLFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm);
+
+/**
+ * @brief Write raw WOPI into given memory block
+ * @param file Pointer to WOPI file data structure
+ * @param dest_mem Destinition memory block pointer
+ * @param length Length of destinition memory block
+ * @param version Wanted WOPI version
+ * @return Error code or 0 on success
+ */
+extern int WOPL_SaveInstToMem(WOPIFile *file, void *dest_mem, size_t length, uint16_t version);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WOPL_FILE_H */