aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVitaly Novichkov <admin@wohlnet.ru>2018-04-22 01:32:56 +0300
committerVitaly Novichkov <admin@wohlnet.ru>2018-04-22 01:32:56 +0300
commit019a6872153bc5b976f3fb77ee82868ec6c9766c (patch)
treed3c936798ae0f2d07cd787e1758f7968ab0dd866
parent3bfdba4d2339d0250226d6bac028ce51e28ec59c (diff)
parentad9e507ad8e37898ffcfa8492dbf90def964389d (diff)
downloadlibADLMIDI-019a6872153bc5b976f3fb77ee82868ec6c9766c.tar.gz
libADLMIDI-019a6872153bc5b976f3fb77ee82868ec6c9766c.tar.bz2
libADLMIDI-019a6872153bc5b976f3fb77ee82868ec6c9766c.zip
Merge branch 'master' into stable
# Conflicts: # android/jni/Android.mk
-rw-r--r--CMakeLists.txt1
-rw-r--r--README.md3
-rw-r--r--android/Android.mk7
-rw-r--r--android/jni/Android.mk30
-rw-r--r--android/jni/Application.mk2
-rw-r--r--libADLMIDI-test.pro1
-rw-r--r--src/adlmidi_load.cpp303
-rw-r--r--src/adlmidi_midiplay.cpp277
-rw-r--r--src/adlmidi_private.hpp258
-rw-r--r--src/chips/opl_chip_base.cpp4
-rw-r--r--src/wopl/wopl_file.c584
-rw-r--r--src/wopl/wopl_file.h282
12 files changed, 1383 insertions, 369 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a3229d4..bda3e0b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -173,6 +173,7 @@ list(APPEND libADLMIDI_SOURCES
${libADLMIDI_SOURCE_DIR}/src/adlmidi_midiplay.cpp
${libADLMIDI_SOURCE_DIR}/src/adlmidi_opl3.cpp
${libADLMIDI_SOURCE_DIR}/src/adlmidi_private.cpp
+ ${libADLMIDI_SOURCE_DIR}/src/wopl/wopl_file.c
)
if(WITH_MUS_SUPPORT AND WITH_MIDI_SEQUENCER)
diff --git a/README.md b/README.md
index c698de8..f6f1224 100644
--- a/README.md
+++ b/README.md
@@ -140,7 +140,8 @@ To build that example you will need to have installed SDL2 library.
* Added support for real-time switching the emulator
* Added support for CC-120 - "All sound off" on the MIDI channel
* Changed logic of CC-74 Brightness to affect sound only between 0 and 64 like real XG synthesizers. Ability to turn on a full-ranged brightness (to use full 0...127 range) is kept.
- * Added support for different output sample formats (PCM8, PCM16, PCM32, Float32, and Float64) (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
+ * Added support for different output sample formats (PCM8, PCM8U, PCM16, PCM16U, PCM32, PCM32U, Float32, and Float64) (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
+ * Reworked MIDI channels management to avoid any memory reallocations while music processing for a hard real time. (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
## 1.3.1 2017-12-16
* Added Real-Time MIDI API (MIDI event functions and adl_generate() to generate PCM between of event rows) which allows you to implement plugin for media players or even a real time MIDI playing driver.
diff --git a/android/Android.mk b/android/Android.mk
index b87ff40..2fe6760 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -3,7 +3,12 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := adlmidi_shared
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libADLMIDI.so
-LOCAL_CFLAGS := -DENABLE_BEGIN_SILENCE_SKIPPING
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../src $(LOCAL_PATH)/../include
+TARGET_PLATFORM = android-14
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := adlmidi_RT_shared
+LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libADLMIDIrt.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../src $(LOCAL_PATH)/../include
TARGET_PLATFORM = android-14
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 26bd8eb..66515fd 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -1,6 +1,6 @@
LOCAL_PATH := $(call my-dir)/../..
-include $(CLEAR_VARS)
+include $(CLEAR_VARS)
LOCAL_MODULE := ADLMIDI
LOCAL_C_INCLUDES := \
@@ -18,6 +18,34 @@ LOCAL_SRC_FILES := src/adldata.cpp \
src/adlmidi_private.cpp \
src/adlmidi_xmi2mid.c \
src/adlmidi.cpp \
+ src/wopl/wopl_file.c \
+ src/chips/dosbox_opl3.cpp \
+ src/chips/nuked_opl3_v174.cpp \
+ src/chips/nuked_opl3.cpp \
+ src/chips/opl_chip_base.cpp \
+ src/chips/dosbox/dbopl.cpp \
+ src/chips/nuked/nukedopl3_174.c \
+ src/chips/nuked/nukedopl3.c
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ADLMIDIrt
+
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/src \
+ $(LOCAL_PATH)/include
+
+LOCAL_ARM_MODE := arm
+LOCAL_CPP_FEATURES := exceptions
+LOCAL_CFLAGS := -DADLMIDI_DISABLE_MIDI_SEQUENCER -DADLMIDI_DISABLE_MUS_SUPPORT -DADLMIDI_DISABLE_XMI_SUPPORT -DDISABLE_EMBEDDED_BANKS
+LOCAL_LDLIBS := -llog
+LOCAL_SRC_FILES := src/adlmidi_load.cpp \
+ src/adlmidi_midiplay.cpp \
+ src/adlmidi_opl3.cpp \
+ src/adlmidi_private.cpp \
+ src/adlmidi.cpp \
+ src/wopl/wopl_file.c \
src/chips/dosbox_opl3.cpp \
src/chips/nuked_opl3_v174.cpp \
src/chips/nuked_opl3.cpp \
diff --git a/android/jni/Application.mk b/android/jni/Application.mk
index a0d5ce7..354e4d1 100644
--- a/android/jni/Application.mk
+++ b/android/jni/Application.mk
@@ -1,5 +1,5 @@
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
-APP_MODULES := ADLMIDI
+APP_MODULES := ADLMIDI ADLMIDIrt
APP_PLATFORM := android-14
APP_OPTIM := release
APP_CPPFLAGS += -std=c++11
diff --git a/libADLMIDI-test.pro b/libADLMIDI-test.pro
index 805474a..bf11c1a 100644
--- a/libADLMIDI-test.pro
+++ b/libADLMIDI-test.pro
@@ -50,6 +50,7 @@ SOURCES += \
src/adlmidi_opl3.cpp \
src/adlmidi_private.cpp \
src/adlmidi_xmi2mid.c \
+ src/wopl/wopl_file.c \
src/chips/dosbox/dbopl.cpp \
src/chips/dosbox_opl3.cpp \
src/chips/nuked/nukedopl3_174.c \
diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp
index 77e6f69..703f34a 100644
--- a/src/adlmidi_load.cpp
+++ b/src/adlmidi_load.cpp
@@ -22,6 +22,7 @@
*/
#include "adlmidi_private.hpp"
+#include "wopl/wopl_file.h"
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
# ifndef ADLMIDI_DISABLE_MUS_SUPPORT
@@ -32,6 +33,7 @@
# endif//XMI
#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes)
{
uint64_t result = 0;
@@ -54,6 +56,8 @@ uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes)
return result;
}
+#endif
+
bool MIDIplay::LoadBank(const std::string &filename)
{
fileReader file;
@@ -69,44 +73,6 @@ bool MIDIplay::LoadBank(const void *data, size_t size)
}
-
-/* WOPL-needed misc functions */
-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 = *reinterpret_cast<const int8_t *>(&arr[0]);
- num *= 1 << 8;
- num |= arr[1];
- return num;
-}
-
-static const char *wopl3_magic = "WOPL3-BANK\0";
-static const uint16_t wopl_latest_version = 3;
-
-#define WOPL_INST_SIZE_V2 62
-#define WOPL_INST_SIZE_V3 66
-
-enum WOPL_InstrumentFlags
-{
- WOPL_Flags_NONE = 0,
- WOPL_Flag_Enable4OP = 0x01,
- WOPL_Flag_Pseudo4OP = 0x02,
- WOPL_Flag_NoSound = 0x04
-};
-
struct WOPL_Inst
{
bool fourOps;
@@ -117,27 +83,13 @@ struct WOPL_Inst
uint16_t ms_sound_koff;
};
-static bool readInstrument(MIDIplay::fileReader &file, WOPL_Inst &ins, uint16_t &version, bool isPercussion = false)
+static void cvt_WOPLI_to_FMIns(WOPL_Inst &ins, WOPLInstrument &in)
{
- uint8_t idata[WOPL_INST_SIZE_V3];
- if(version >= 3)
- {
- if(file.read(idata, 1, WOPL_INST_SIZE_V3) != WOPL_INST_SIZE_V3)
- return false;
- }
- else
- {
- if(file.read(idata, 1, WOPL_INST_SIZE_V2) != WOPL_INST_SIZE_V2)
- return false;
- }
-
- //strncpy(ins.name, char_p(idata), 32);
- ins.op[0].finetune = (int8_t)toSint16BE(idata + 32);
- ins.op[1].finetune = (int8_t)toSint16BE(idata + 34);
- //ins.velocity_offset = int8_t(idata[36]);
+ ins.op[0].finetune = in.note_offset1;
+ ins.op[1].finetune = in.note_offset2;
ins.adlins.voice2_fine_tune = 0.0;
- int8_t voice2_fine_tune = int8_t(idata[37]);
+ int8_t voice2_fine_tune = in.second_voice_detune;
if(voice2_fine_tune != 0)
{
if(voice2_fine_tune == 1)
@@ -148,202 +100,145 @@ static bool readInstrument(MIDIplay::fileReader &file, WOPL_Inst &ins, uint16_t
ins.adlins.voice2_fine_tune = ((voice2_fine_tune * 15.625) / 1000.0);
}
- ins.adlins.tone = isPercussion ? idata[38] : 0;
+ ins.adlins.tone = in.percussion_key_number;
- uint8_t flags = idata[39];
- ins.adlins.flags = (flags & WOPL_Flag_Enable4OP) && (flags & WOPL_Flag_Pseudo4OP) ? adlinsdata::Flag_Pseudo4op : 0;
- ins.adlins.flags|= (flags & WOPL_Flag_NoSound) ? adlinsdata::Flag_NoSound : 0;
- ins.fourOps = (flags & WOPL_Flag_Enable4OP) || (flags & WOPL_Flag_Pseudo4OP);
+ ins.adlins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? adlinsdata::Flag_Pseudo4op : 0;
+ ins.adlins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? adlinsdata::Flag_NoSound : 0;
+ ins.fourOps = (in.inst_flags & WOPL_Ins_4op) || (in.inst_flags & WOPL_Ins_Pseudo4op);
- ins.op[0].feedconn = (idata[40]);
- ins.op[1].feedconn = (idata[41]);
+ ins.op[0].feedconn = in.fb_conn1_C0;
+ ins.op[1].feedconn = in.fb_conn2_C0;
for(size_t op = 0, slt = 0; op < 4; op++, slt++)
{
- size_t off = 42 + size_t(op) * 5;
- // ins.setAVEKM(op, idata[off + 0]);//AVEKM
- // ins.setAtDec(op, idata[off + 2]);//AtDec
- // ins.setSusRel(op, idata[off + 3]);//SusRel
- // ins.setWaveForm(op, idata[off + 4]);//WaveForm
- // ins.setKSLL(op, idata[off + 1]);//KSLL
ins.op[slt].carrier_E862 =
- ((static_cast<uint32_t>(idata[off + 4]) << 24) & 0xFF000000) //WaveForm
- | ((static_cast<uint32_t>(idata[off + 3]) << 16) & 0x00FF0000) //SusRel
- | ((static_cast<uint32_t>(idata[off + 2]) << 8) & 0x0000FF00) //AtDec
- | ((static_cast<uint32_t>(idata[off + 0]) << 0) & 0x000000FF); //AVEKM
- ins.op[slt].carrier_40 = idata[off + 1];//KSLL
+ ((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
+ | ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
+ | ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
+ | ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
+ ins.op[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL
op++;
- off = 42 + size_t(op) * 5;
ins.op[slt].modulator_E862 =
- ((static_cast<uint32_t>(idata[off + 4]) << 24) & 0xFF000000) //WaveForm
- | ((static_cast<uint32_t>(idata[off + 3]) << 16) & 0x00FF0000) //SusRel
- | ((static_cast<uint32_t>(idata[off + 2]) << 8) & 0x0000FF00) //AtDec
- | ((static_cast<uint32_t>(idata[off + 0]) << 0) & 0x000000FF); //AVEKM
- ins.op[slt].modulator_40 = idata[off + 1];//KSLL
+ ((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
+ | ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
+ | ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
+ | ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
+ ins.op[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL
}
- if(version >= 3)
- {
- ins.ms_sound_kon = toUint16BE(idata + 62);
- ins.ms_sound_koff = toUint16BE(idata + 64);
- }
- else
- {
- ins.ms_sound_kon = 1000;
- ins.ms_sound_koff = 500;
- }
-
- return true;
+ ins.ms_sound_kon = in.delay_on_ms;
+ ins.ms_sound_koff = in.delay_off_ms;
}
bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
{
+ int err = 0;
+ WOPLFile *wopl = NULL;
+ char *raw_file_data = NULL;
size_t fsize;
- ADL_UNUSED(fsize);
if(!fr.isValid())
{
errorStringOut = "Custom bank: Invalid data stream!";
return false;
}
- char magic[32];
- std::memset(magic, 0, 32);
-
- uint16_t version = 0;
-
- uint16_t count_melodic_banks = 1;
- uint16_t count_percusive_banks = 1;
-
- if(fr.read(magic, 1, 11) != 11)
- {
- errorStringOut = "Custom bank: Can't read magic number!";
- return false;
- }
-
- if(std::strncmp(magic, wopl3_magic, 11) != 0)
+ // Read complete bank file into the memory
+ fr.seek(0, SEEK_END);
+ fsize = fr.tell();
+ fr.seek(0, SEEK_SET);
+ // Allocate necessary memory block
+ raw_file_data = (char*)malloc(fsize);
+ if(!raw_file_data)
{
- errorStringOut = "Custom bank: Invalid magic number!";
+ errorStringOut = "Custom bank: Out of memory before of read!";
return false;
}
+ fr.read(raw_file_data, 1, fsize);
- uint8_t version_raw[2];
- if(fr.read(version_raw, 1, 2) != 2)
- {
- errorStringOut = "Custom bank: Can't read version!";
- return false;
- }
+ // Parse bank file from the memory
+ wopl = WOPL_LoadBankFromMem((void*)raw_file_data, fsize, &err);
+ //Free the buffer no more needed
+ free(raw_file_data);
- version = toUint16LE(version_raw);
- if(version > wopl_latest_version)
+ // Check for any erros
+ if(!wopl)
{
- errorStringOut = "Custom bank: Unsupported WOPL version!";
- return false;
- }
-
- uint8_t head[6];
- std::memset(head, 0, 6);
- if(fr.read(head, 1, 6) != 6)
- {
- errorStringOut = "Custom bank: Can't read header!";
- return false;
- }
-
- count_melodic_banks = toUint16BE(head);
- count_percusive_banks = toUint16BE(head + 2);
-
- if((count_melodic_banks < 1) || (count_percusive_banks < 1))
- {
- errorStringOut = "Custom bank: Too few banks in this file!";
- return false;
+ switch(err)
+ {
+ case WOPL_ERR_BAD_MAGIC:
+ errorStringOut = "Custom bank: Invalid magic!";
+ return false;
+ case WOPL_ERR_UNEXPECTED_ENDING:
+ errorStringOut = "Custom bank: Unexpected ending!";
+ return false;
+ case WOPL_ERR_INVALID_BANKS_COUNT:
+ errorStringOut = "Custom bank: Invalid banks count!";
+ return false;
+ case WOPL_ERR_NEWER_VERSION:
+ errorStringOut = "Custom bank: Version is newer than supported by this library!";
+ return false;
+ case WOPL_ERR_OUT_OF_MEMORY:
+ errorStringOut = "Custom bank: Out of memory!";
+ return false;
+ default:
+ errorStringOut = "Custom bank: Unknown error!";
+ return false;
+ }
}
- /*UNUSED YET*/
- bool default_deep_vibrato = ((head[4]>>0) & 0x01);
- bool default_deep_tremolo = ((head[4]>>1) & 0x01);
-
- //5'th byte reserved for Deep-Tremolo and Deep-Vibrato flags
- m_setup.HighTremoloMode = default_deep_tremolo;
- m_setup.HighVibratoMode = default_deep_vibrato;
- //6'th byte reserved for ADLMIDI's default volume model
- m_setup.VolumeModel = (int)head[5];
+ m_setup.HighTremoloMode = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0;
+ m_setup.HighVibratoMode = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0;
+ m_setup.VolumeModel = wopl->volume_model;
+ /* TODO: Avoid memory reallocation in nearest future! */
opl.dynamic_melodic_banks.clear();
opl.dynamic_percussion_banks.clear();
opl.setEmbeddedBank(m_setup.AdlBank);
- if(version >= 2)//Read bank meta-entries
+ OPL3::BankMap *slots_banks[2] = { &opl.dynamic_melodic_banks, &opl.dynamic_percussion_banks};
+ uint16_t slots_counts[2] = {wopl->banks_count_melodic, wopl->banks_count_percussion};
+ WOPLBank *slots_src_ins[2] = { wopl->banks_melodic, wopl->banks_percussive };
+
+ for(int ss = 0; ss < 2; ss++)
{
- for(uint16_t i = 0; i < count_melodic_banks; i++)
+ for(int i = 0; i < slots_counts[ss]; i++)
{
- uint8_t bank_meta[34];
- if(fr.read(bank_meta, 1, 34) != 34)
- {
- errorStringOut = "Custom bank: Fail to read melodic bank meta-data!";
- return false;
- }
- uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
- size_t offset = opl.dynamic_melodic_banks.size();
- opl.dynamic_melodic_banks[bank] = offset;
- //strncpy(bankMeta.name, char_p(bank_meta), 32);
- }
+ uint16_t bank = (slots_src_ins[ss][i].bank_midi_msb * 256) + slots_src_ins[ss][i].bank_midi_lsb;
+ size_t offset = slots_banks[ss]->size();
+ (*slots_banks[ss])[bank] = offset;
- for(uint16_t i = 0; i < count_percusive_banks; i++)
- {
- uint8_t bank_meta[34];
- if(fr.read(bank_meta, 1, 34) != 34)
+ for(int j = 0; j < 128; j++)
{
- errorStringOut = "Custom bank: Fail to read percussion bank meta-data!";
- return false;
+ WOPL_Inst ins;
+ std::memset(&ins, 0, sizeof(WOPL_Inst));
+ WOPLInstrument &inIns = slots_src_ins[ss][i].ins[j];
+
+ cvt_WOPLI_to_FMIns(ins, inIns);
+
+ ins.adlins.ms_sound_kon = ins.ms_sound_kon;
+ ins.adlins.ms_sound_koff = ins.ms_sound_koff;
+ ins.adlins.adlno1 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
+ opl.dynamic_instruments.push_back(ins.op[0]);
+ ins.adlins.adlno2 = ins.adlins.adlno1;
+ if(ins.fourOps)
+ {
+ ins.adlins.adlno2 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
+ opl.dynamic_instruments.push_back(ins.op[1]);
+ }
+ opl.dynamic_metainstruments.push_back(ins.adlins);
}
- uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
- size_t offset = opl.dynamic_percussion_banks.size();
- opl.dynamic_percussion_banks[bank] = offset;
- //strncpy(bankMeta.name, char_p(bank_meta), 32);
}
}
- uint16_t total = 128 * count_melodic_banks;
- bool readPercussion = false;
-
-tryAgain:
- for(uint16_t i = 0; i < total; i++)
- {
- WOPL_Inst ins;
- std::memset(&ins, 0, sizeof(WOPL_Inst));
- if(!readInstrument(fr, ins, version, readPercussion))
- {
- opl.setEmbeddedBank(m_setup.AdlBank);
- errorStringOut = "Custom bank: Fail to read instrument!";
- return false;
- }
- ins.adlins.ms_sound_kon = ins.ms_sound_kon;
- ins.adlins.ms_sound_koff = ins.ms_sound_koff;
- ins.adlins.adlno1 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
- opl.dynamic_instruments.push_back(ins.op[0]);
- ins.adlins.adlno2 = ins.adlins.adlno1;
- if(ins.fourOps)
- {
- ins.adlins.adlno2 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
- opl.dynamic_instruments.push_back(ins.op[1]);
- }
- opl.dynamic_metainstruments.push_back(ins.adlins);
- }
-
- if(!readPercussion)
- {
- total = 128 * count_percusive_banks;
- readPercussion = true;
- goto tryAgain;
- }
-
opl.AdlBank = ~0u; // Use dynamic banks!
//Percussion offset is count of instruments multipled to count of melodic banks
- opl.dynamic_percussion_offset = 128 * count_melodic_banks;
-
+ opl.dynamic_percussion_offset = 128 * wopl->banks_count_melodic;
applySetup();
+ WOPL_Free(wopl);
+
return true;
}
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp
index ed7f44a..702ebea 100644
--- a/src/adlmidi_midiplay.cpp
+++ b/src/adlmidi_midiplay.cpp
@@ -117,18 +117,18 @@ inline bool isXgPercChannel(uint8_t msb, uint8_t lsb)
void MIDIplay::AdlChannel::AddAge(int64_t ms)
{
- if(users.empty())
+ if(users_empty())
koff_time_until_neglible =
std::max(int64_t(koff_time_until_neglible - ms), static_cast<int64_t>(-0x1FFFFFFFl));
else
{
koff_time_until_neglible = 0;
- for(users_t::iterator i = users.begin(); i != users.end(); ++i)
+ for(LocationData *i = users_first; i; i = i->next)
{
- i->second.kon_time_until_neglible =
- std::max(i->second.kon_time_until_neglible - ms, static_cast<int64_t>(-0x1FFFFFFFl));
- i->second.vibdelay += ms;
+ i->kon_time_until_neglible =
+ std::max(i->kon_time_until_neglible - ms, static_cast<int64_t>(-0x1FFFFFFFl));
+ i->vibdelay += ms;
}
}
}
@@ -760,6 +760,7 @@ void MIDIplay::applySetup()
ch.resize(opl.NumChannels);
}
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
uint64_t MIDIplay::ReadVarLen(uint8_t **ptr)
{
uint64_t result = 0;
@@ -792,7 +793,6 @@ uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok)
return result;
}
-#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
double MIDIplay::Tick(double s, double granularity)
{
s *= tempoMultiplier;
@@ -967,10 +967,10 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
if((opl.m_musicMode == OPL3::MODE_RSXX) && (velocity != 0))
{
// Check if this is just a note after-touch
- MIDIchannel::activenoteiterator i = Ch[channel].activenotes.find(note);
- if(i != Ch[channel].activenotes.end())
+ MIDIchannel::activenoteiterator i = Ch[channel].activenotes_find(note);
+ if(i)
{
- i->second.vol = velocity;
+ i->vol = velocity;
NoteUpdate(channel, i, Upd_Volume);
return false;
}
@@ -1093,10 +1093,10 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
//uint16_t i[2] = { ains.adlno1, ains.adlno2 };
bool pseudo_4op = ains->flags & adlinsdata::Flag_Pseudo4op;
- MIDIchannel::NoteInfo::Phys voices[2] =
+ MIDIchannel::NoteInfo::Phys voices[MIDIchannel::NoteInfo::MaxNumPhysChans] =
{
- {ains->adlno1, false},
- {ains->adlno2, pseudo_4op}
+ {0, ains->adlno1, false},
+ {0, ains->adlno2, pseudo_4op}
};
if((opl.AdlPercussionMode == 1) && PercussionMap[midiins & 0xFF])
@@ -1112,9 +1112,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
}
// Allocate AdLib channel (the physical sound channel for the note)
- int32_t adlchannel[2] = { -1, -1 };
+ int32_t adlchannel[MIDIchannel::NoteInfo::MaxNumPhysChans] = { -1, -1 };
- for(uint32_t ccount = 0; ccount < 2; ++ccount)
+ for(uint32_t ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount)
{
if(ccount == 1)
{
@@ -1196,19 +1196,20 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
// Allocate active note for MIDI channel
std::pair<MIDIchannel::activenoteiterator, bool>
- ir = Ch[channel].activenotes.insert(std::make_pair(note, MIDIchannel::NoteInfo()));
- ir.first->second.vol = velocity;
- ir.first->second.tone = tone;
- ir.first->second.midiins = midiins;
- ir.first->second.insmeta = meta;
+ ir = Ch[channel].activenotes_insert(note);
+ ir.first->vol = velocity;
+ ir.first->tone = tone;
+ ir.first->midiins = midiins;
+ ir.first->insmeta = meta;
+ ir.first->chip_channels_count = 0;
- for(unsigned ccount = 0; ccount < 2; ++ccount)
+ for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount)
{
int32_t c = adlchannel[ccount];
if(c < 0)
continue;
uint16_t chipChan = static_cast<uint16_t>(adlchannel[ccount]);
- ir.first->second.phys[chipChan] = voices[ccount];
+ ir.first->phys_ensure_find_or_create(chipChan)->assign(voices[ccount]);
}
NoteUpdate(channel, ir.first, Upd_All | Upd_Patch);
return true;
@@ -1224,13 +1225,13 @@ void MIDIplay::realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t at
{
channel = channel % 16;
MIDIchannel::activenoteiterator
- i = Ch[channel].activenotes.find(note);
- if(i == Ch[channel].activenotes.end())
+ i = Ch[channel].activenotes_find(note);
+ if(!i)
{
// Ignore touch if note is not active
return;
}
- i->second.vol = 127 - atVal;
+ i->vol = 127 - atVal;
NoteUpdate(channel, i, Upd_Volume);
}
@@ -1239,12 +1240,10 @@ void MIDIplay::realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal)
// TODO: Verify, is this correct action?
channel = channel % 16;
for(MIDIchannel::activenoteiterator
- i = Ch[channel].activenotes.begin();
- i != Ch[channel].activenotes.end();
- ++i)
+ i = Ch[channel].activenotes_begin(); i; ++i)
{
// Set this pressure to all active notes on the channel
- i->second.vol = 127 - atVal;
+ i->vol = 127 - atVal;
}
NoteUpdate_All(channel, Upd_Volume);
@@ -1436,7 +1435,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
unsigned props_mask,
int32_t select_adlchn)
{
- MIDIchannel::NoteInfo &info = i->second;
+ MIDIchannel::NoteInfo &info = *i;
const int16_t tone = info.tone;
const uint8_t vol = info.vol;
const int midiins = info.midiins;
@@ -1444,38 +1443,32 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
const adlinsdata &ains = opl.GetAdlMetaIns(insmeta);
AdlChannel::Location my_loc;
my_loc.MidCh = MidCh;
- my_loc.note = i->first;
+ my_loc.note = info.note;
- for(MIDIchannel::NoteInfo::PhysMap::iterator
- jnext = info.phys.begin();
- jnext != info.phys.end();
- )
+ for(unsigned ccount = 0, ctotal = info.chip_channels_count; ccount < ctotal; ccount++)
{
- MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++);
- uint16_t c = j->first;
- const MIDIchannel::NoteInfo::Phys &ins = j->second;
+ const MIDIchannel::NoteInfo::Phys &ins = info.chip_channels[ccount];
+ uint16_t c = ins.chip_chan;
if(select_adlchn >= 0 && c != select_adlchn) continue;
if(props_mask & Upd_Patch)
{
opl.Patch(c, ins.insId);
- AdlChannel::LocationData &d = ch[c].users[my_loc];
- d.sustained = false; // inserts if necessary
- d.vibdelay = 0;
- d.kon_time_until_neglible = ains.ms_sound_kon;
- d.ins = ins;
+ AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc);
+ if(d) { // inserts if necessary
+ d->sustained = false;
+ d->vibdelay = 0;
+ d->kon_time_until_neglible = ains.ms_sound_kon;
+ d->ins = ins;
+ }
}
}
- for(MIDIchannel::NoteInfo::PhysMap::iterator
- jnext = info.phys.begin();
- jnext != info.phys.end();
- )
+ for(unsigned ccount = 0; ccount < info.chip_channels_count; ccount++)
{
- MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++);
- uint16_t c = j->first;
- const MIDIchannel::NoteInfo::Phys &ins = j->second;
+ const MIDIchannel::NoteInfo::Phys &ins = info.chip_channels[ccount];
+ uint16_t c = ins.chip_chan;
if(select_adlchn >= 0 && c != select_adlchn)
continue;
@@ -1484,15 +1477,15 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
{
if(Ch[MidCh].sustain == 0)
{
- AdlChannel::users_t::iterator k = ch[c].users.find(my_loc);
+ AdlChannel::LocationData *k = ch[c].users_find(my_loc);
- if(k != ch[c].users.end())
- ch[c].users.erase(k);
+ if(k)
+ ch[c].users_erase(k);
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, c, tone, midiins, 0, 0.0);
- if(ch[c].users.empty())
+ if(ch[c].users_empty())
{
opl.NoteOff(c);
if(props_mask & Upd_Mute) // Mute the note
@@ -1508,13 +1501,15 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
{
// Sustain: Forget about the note, but don't key it off.
// Also will avoid overwriting it very soon.
- AdlChannel::LocationData &d = ch[c].users[my_loc];
- d.sustained = true; // note: not erased!
+ AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc);
+ if(d)
+ d->sustained = true; // note: not erased!
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0);
}
- info.phys.erase(j);
+ info.phys_erase_at(&ins); // decrements channel count
+ --ccount; // adjusts index accordingly
continue;
}
@@ -1610,10 +1605,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
if(props_mask & Upd_Pitch)
{
- AdlChannel::LocationData &d = ch[c].users[my_loc];
+ AdlChannel::LocationData *d = ch[c].users_find(my_loc);
// Don't bend a sustained note
- if(!d.sustained)
+ if(!d || !d->sustained)
{
double bend = Ch[MidCh].bend + opl.GetAdlIns(ins.insId).finetune;
double phase = 0.0;
@@ -1623,7 +1618,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
phase = ains.voice2_fine_tune;//0.125; // Detune the note slightly (this is what Doom does)
}
- if(Ch[MidCh].vibrato && d.vibdelay >= Ch[MidCh].vibdelay)
+ if(Ch[MidCh].vibrato && d->vibdelay >= Ch[MidCh].vibdelay)
bend += Ch[MidCh].vibrato * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos);
#ifdef ADLMIDI_USE_DOSBOX_OPL
@@ -1639,8 +1634,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
}
}
- if(info.phys.empty())
- Ch[MidCh].activenotes.erase(i);
+ if(info.chip_channels_count == 0)
+ Ch[MidCh].activenotes_erase(i);
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
@@ -2138,35 +2133,32 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not
// Same midi-instrument = some stability
//if(c == MidCh) s += 4;
- for(AdlChannel::users_t::const_iterator
- j = ch[c].users.begin();
- j != ch[c].users.end();
- ++j)
+ for (AdlChannel::LocationData *j = ch[c].users_first; j; j = j->next)
{
s -= 4000;
- if(!j->second.sustained)
- s -= j->second.kon_time_until_neglible;
+ if(!j->sustained)
+ s -= j->kon_time_until_neglible;
else
- s -= (j->second.kon_time_until_neglible / 2);
+ s -= (j->kon_time_until_neglible / 2);
- MIDIchannel::activenotemap_t::const_iterator
- k = Ch[j->first.MidCh].activenotes.find(j->first.note);
+ MIDIchannel::activenoteiterator
+ k = const_cast<MIDIchannel &>(Ch[j->loc.MidCh]).activenotes_find(j->loc.note);
- if(k != Ch[j->first.MidCh].activenotes.end())
+ if(k)
{
// Same instrument = good
- if(j->second.ins == ins)
+ if(j->ins == ins)
{
s += 300;
// Arpeggio candidate = even better
- if(j->second.vibdelay < 70
- || j->second.kon_time_until_neglible > 20000)
+ if(j->vibdelay < 70
+ || j->kon_time_until_neglible > 20000)
s += 0;
}
// Percussion is inferior to melody
- s += 50 * (int64_t)(k->second.midiins / 128);
+ s += 50 * (int64_t)(k->midiins / 128);
/*
if(k->second.midiins >= 25
&& k->second.midiins < 40
@@ -2189,14 +2181,11 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not
if(opl.four_op_category[c2]
!= opl.four_op_category[c]) continue;
- for(AdlChannel::users_t::const_iterator
- m = ch[c2].users.begin();
- m != ch[c2].users.end();
- ++m)
+ for(AdlChannel::LocationData *m = ch[c2].users_first; m; m = m->next)
{
- if(m->second.sustained) continue;
- if(m->second.vibdelay >= 200) continue;
- if(m->second.ins != j->second.ins) continue;
+ if(m->sustained) continue;
+ if(m->vibdelay >= 200) continue;
+ if(m->ins != j->ins) continue;
n_evacuation_stations += 1;
}
}
@@ -2210,27 +2199,25 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not
void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins)
{
- if(ch[c].users.empty()) return; // Nothing to do
+ if(ch[c].users_empty()) return; // Nothing to do
//bool doing_arpeggio = false;
- for(AdlChannel::users_t::iterator
- jnext = ch[c].users.begin();
- jnext != ch[c].users.end();
- )
+ for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;)
{
- AdlChannel::users_t::iterator j(jnext++);
+ AdlChannel::LocationData *j = jnext;
+ jnext = jnext->next;
- if(!j->second.sustained)
+ if(!j->sustained)
{
// Collision: Kill old note,
// UNLESS we're going to do arpeggio
MIDIchannel::activenoteiterator i
- (Ch[j->first.MidCh].activenotes.find(j->first.note));
+ (Ch[j->loc.MidCh].activenotes_ensure_find(j->loc.note));
// Check if we can do arpeggio.
- if((j->second.vibdelay < 70
- || j->second.kon_time_until_neglible > 20000)
- && j->second.ins == ins)
+ if((j->vibdelay < 70
+ || j->kon_time_until_neglible > 20000)
+ && j->ins == ins)
{
// Do arpeggio together with this note.
//doing_arpeggio = true;
@@ -2249,11 +2236,13 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo
// Keyoff the channel so that it can be retriggered,
// unless the new note will be introduced as just an arpeggio.
- if(ch[c].users.empty())
+ if(ch[c].users_empty())
opl.NoteOff(c);
}
-void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator j, MIDIplay::MIDIchannel::activenoteiterator i)
+void MIDIplay::KillOrEvacuate(size_t from_channel,
+ AdlChannel::LocationData *j,
+ MIDIplay::MIDIchannel::activenoteiterator i)
{
// Before killing the note, check if it can be
// evacuated to another channel as an arpeggio
@@ -2271,32 +2260,34 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator
if(opl.four_op_category[c] != opl.four_op_category[from_channel])
continue;
- for(AdlChannel::users_t::iterator
- m = ch[c].users.begin();
- m != ch[c].users.end();
- ++m)
+ AdlChannel &adlch = ch[c];
+ if(adlch.users_size == AdlChannel::users_max)
+ continue; // no room for more arpeggio on channel
+
+ for(AdlChannel::LocationData *m = adlch.users_first; m; m = m->next)
{
- if(m->second.vibdelay >= 200
- && m->second.kon_time_until_neglible < 10000) continue;
- if(m->second.ins != j->second.ins)
+ if(m->vibdelay >= 200
+ && m->kon_time_until_neglible < 10000) continue;
+ if(m->ins != j->ins)
continue;
if(hooks.onNote)
{
hooks.onNote(hooks.onNote_userData,
(int)from_channel,
- i->second.tone,
- i->second.midiins, 0, 0.0);
+ i->tone,
+ i->midiins, 0, 0.0);
hooks.onNote(hooks.onNote_userData,
(int)c,
- i->second.tone,
- i->second.midiins,
- i->second.vol, 0.0);
+ i->tone,
+ i->midiins,
+ i->vol, 0.0);
}
- i->second.phys.erase(static_cast<uint16_t>(from_channel));
- i->second.phys[cs] = j->second.ins;
- ch[cs].users.insert(*j);
- ch[from_channel].users.erase(j);
+ i->phys_erase(static_cast<uint16_t>(from_channel));
+ i->phys_ensure_find_or_create(cs)->assign(j->ins);
+ if(!ch[cs].users_insert(*j))
+ assert(false);
+ ch[from_channel].users_erase(j);
return;
}
}
@@ -2309,7 +2300,7 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator
ins
);*/
// Kill it
- NoteUpdate(j->first.MidCh,
+ NoteUpdate(j->loc.MidCh,
i,
Upd_Off,
static_cast<int32_t>(from_channel));
@@ -2336,27 +2327,25 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
for(unsigned c = first; c < last; ++c)
{
- if(ch[c].users.empty()) continue; // Nothing to do
+ if(ch[c].users_empty()) continue; // Nothing to do
- for(AdlChannel::users_t::iterator
- jnext = ch[c].users.begin();
- jnext != ch[c].users.end();
- )
+ for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;)
{
- AdlChannel::users_t::iterator j(jnext++);
+ AdlChannel::LocationData *j = jnext;
+ jnext = jnext->next;
- if((MidCh < 0 || j->first.MidCh == MidCh)
- && j->second.sustained)
+ if((MidCh < 0 || j->loc.MidCh == MidCh)
+ && j->sustained)
{
int midiins = '?';
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, (int)c, j->first.note, midiins, 0, 0.0);
- ch[c].users.erase(j);
+ hooks.onNote(hooks.onNote_userData, (int)c, j->loc.note, midiins, 0, 0.0);
+ ch[c].users_erase(j);
}
}
// Keyoff the channel, if there are no users left.
- if(ch[c].users.empty())
+ if(ch[c].users_empty())
opl.NoteOff(c);
}
}
@@ -2402,9 +2391,7 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask)
{
for(MIDIchannel::activenoteiterator
- i = Ch[MidCh].activenotes.begin();
- i != Ch[MidCh].activenotes.end();
- )
+ i = Ch[MidCh].activenotes_begin(); i;)
{
MIDIchannel::activenoteiterator j(i++);
NoteUpdate(MidCh, j, props_mask);
@@ -2414,9 +2401,9 @@ void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask)
void MIDIplay::NoteOff(uint16_t MidCh, uint8_t note)
{
MIDIchannel::activenoteiterator
- i = Ch[MidCh].activenotes.find(note);
+ i = Ch[MidCh].activenotes_find(note);
- if(i != Ch[MidCh].activenotes.end())
+ if(i)
NoteUpdate(MidCh, i, Upd_Off);
}
@@ -2425,7 +2412,7 @@ void MIDIplay::UpdateVibrato(double amount)
{
for(size_t a = 0, b = Ch.size(); a < b; ++a)
{
- if(Ch[a].vibrato && !Ch[a].activenotes.empty())
+ if(Ch[a].vibrato && !Ch[a].activenotes_empty())
{
NoteUpdate_All(static_cast<uint16_t>(a), Upd_Pitch);
Ch[a].vibpos += amount * Ch[a].vibspeed;
@@ -2447,7 +2434,17 @@ uint64_t MIDIplay::ChooseDevice(const std::string &name)
size_t n = devices.size() * 16;
devices.insert(std::make_pair(name, n));
- Ch.resize(n + 16);
+
+ size_t channelsBefore = Ch.size();
+ size_t channels = n + 16;
+ Ch.resize(channels);
+
+ for(size_t ch = channelsBefore; ch < channels; ++ch) {
+ for(unsigned i = 0; i < 128; ++i) {
+ Ch[ch].activenotes[i].note = i;
+ Ch[ch].activenotes[i].active = false;
+ }
+ }
return n;
}
@@ -2483,11 +2480,11 @@ retry_arpeggio:
if(c > uint32_t(std::numeric_limits<int32_t>::max()))
break;
- size_t n_users = ch[c].users.size();
+ size_t n_users = ch[c].users_size;
if(n_users > 1)
{
- AdlChannel::users_t::const_iterator i = ch[c].users.begin();
+ AdlChannel::LocationData *i = ch[c].users_first;
size_t rate_reduction = 3;
if(n_users >= 3)
@@ -2496,23 +2493,25 @@ retry_arpeggio:
if(n_users >= 4)
rate_reduction = 1;
- std::advance(i, (arpeggio_counter / rate_reduction) % n_users);
+ for(unsigned count = (arpeggio_counter / rate_reduction) % n_users,
+ n = 0; n < count; ++n)
+ i = i->next;
- if(i->second.sustained == false)
+ if(i->sustained == false)
{
- if(i->second.kon_time_until_neglible <= 0l)
+ if(i->kon_time_until_neglible <= 0l)
{
NoteUpdate(
- i->first.MidCh,
- Ch[ i->first.MidCh ].activenotes.find(i->first.note),
+ i->loc.MidCh,
+ Ch[ i->loc.MidCh ].activenotes_ensure_find(i->loc.note),
Upd_Off,
static_cast<int32_t>(c));
goto retry_arpeggio;
}
NoteUpdate(
- i->first.MidCh,
- Ch[ i->first.MidCh ].activenotes.find(i->first.note),
+ i->loc.MidCh,
+ Ch[ i->loc.MidCh ].activenotes_ensure_find(i->loc.note),
Upd_Pitch | Upd_Volume | Upd_Pan,
static_cast<int32_t>(c));
}
diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp
index b60c7c0..3532126 100644
--- a/src/adlmidi_private.hpp
+++ b/src/adlmidi_private.hpp
@@ -87,6 +87,7 @@ typedef int32_t ssize_t;
#include <cmath>
#include <cstdarg>
#include <cstdio>
+#include <cassert>
#if !(defined(__APPLE__) && defined(__GLIBCXX__))
#include <cinttypes> //PRId32, PRIu32, etc.
#else
@@ -606,6 +607,8 @@ public:
bool is_xg_percussion;
struct NoteInfo
{
+ uint8_t note;
+ bool active;
// Current pressure
uint8_t vol;
char ____padding[1];
@@ -616,13 +619,25 @@ public:
size_t midiins;
// Index to physical adlib data structure, adlins[]
size_t insmeta;
+ enum
+ {
+ MaxNumPhysChans = 2,
+ MaxNumPhysItemCount = MaxNumPhysChans,
+ };
struct Phys
{
+ //! Destination chip channel
+ uint16_t chip_chan;
//! ins, inde to adl[]
size_t insId;
//! Is this voice must be detunable?
bool pseudo4op;
+ void assign(const Phys &oth)
+ {
+ insId = oth.insId;
+ pseudo4op = oth.pseudo4op;
+ }
bool operator==(const Phys &oth) const
{
return (insId == oth.insId) && (pseudo4op == oth.pseudo4op);
@@ -632,14 +647,125 @@ public:
return !operator==(oth);
}
};
- typedef std::map<uint16_t, Phys> PhysMap;
- // List of OPL3 channels it is currently occupying.
- std::map<uint16_t /*adlchn*/, Phys> phys;
+ //! List of OPL3 channels it is currently occupying.
+ Phys chip_channels[MaxNumPhysItemCount];
+ //! Count of used channels.
+ unsigned chip_channels_count;
+ //
+ Phys *phys_find(unsigned chip_chan)
+ {
+ Phys *ph = NULL;
+ for(unsigned i = 0; i < chip_channels_count && !ph; ++i)
+ if(chip_channels[i].chip_chan == chip_chan)
+ ph = &chip_channels[i];
+ return ph;
+ }
+ Phys *phys_find_or_create(unsigned chip_chan)
+ {
+ Phys *ph = phys_find(chip_chan);
+ if(!ph) {
+ if(chip_channels_count < MaxNumPhysItemCount) {
+ ph = &chip_channels[chip_channels_count++];
+ ph->chip_chan = chip_chan;
+ }
+ }
+ return ph;
+ }
+ Phys *phys_ensure_find_or_create(unsigned chip_chan)
+ {
+ Phys *ph = phys_find_or_create(chip_chan);
+ assert(ph);
+ return ph;
+ }
+ void phys_erase_at(const Phys *ph)
+ {
+ unsigned pos = ph - chip_channels;
+ assert(pos < chip_channels_count);
+ for(unsigned i = pos + 1; i < chip_channels_count; ++i)
+ chip_channels[i - 1] = chip_channels[i];
+ --chip_channels_count;
+ }
+ void phys_erase(unsigned chip_chan)
+ {
+ Phys *ph = phys_find(chip_chan);
+ if(ph)
+ phys_erase_at(ph);
+ }
};
- typedef std::map<uint8_t, NoteInfo> activenotemap_t;
- typedef activenotemap_t::iterator activenoteiterator;
char ____padding2[5];
- activenotemap_t activenotes;
+ NoteInfo activenotes[128];
+
+ struct activenoteiterator
+ {
+ explicit activenoteiterator(NoteInfo *info = 0)
+ : ptr(info) {}
+ activenoteiterator &operator++()
+ {
+ if(ptr->note == 127)
+ ptr = 0;
+ else
+ for(++ptr; ptr && !ptr->active;)
+ ptr = (ptr->note == 127) ? 0 : (ptr + 1);
+ return *this;
+ };
+ activenoteiterator operator++(int)
+ {
+ activenoteiterator pos = *this;
+ ++*this;
+ return pos;
+ }
+ NoteInfo &operator*() const
+ { return *ptr; }
+ NoteInfo *operator->() const
+ { return ptr; }
+ bool operator==(activenoteiterator other) const
+ { return ptr == other.ptr; }
+ bool operator!=(activenoteiterator other) const
+ { return ptr != other.ptr; }
+ operator NoteInfo *() const
+ { return ptr; }
+ private:
+ NoteInfo *ptr;
+ };
+
+ activenoteiterator activenotes_begin()
+ {
+ activenoteiterator it(activenotes);
+ return (it->active) ? it : ++it;
+ }
+
+ activenoteiterator activenotes_find(uint8_t note)
+ {
+ return activenoteiterator(
+ activenotes[note].active ? &activenotes[note] : 0);
+ }
+
+ activenoteiterator activenotes_ensure_find(uint8_t note)
+ {
+ activenoteiterator it = activenotes_find(note);
+ assert(it);
+ return it;
+ }
+
+ std::pair<activenoteiterator, bool> activenotes_insert(uint8_t note)
+ {
+ NoteInfo &info = activenotes[note];
+ bool inserted = !info.active;
+ if(inserted) info.active = true;
+ return std::pair<activenoteiterator, bool>(activenoteiterator(&info), inserted);
+ }
+
+ void activenotes_erase(activenoteiterator pos)
+ {
+ if(pos)
+ pos->active = false;
+ }
+
+ bool activenotes_empty()
+ {
+ return !activenotes_begin();
+ }
+
void reset()
{
resetAllControllers();
@@ -677,36 +803,113 @@ public:
// Additional information about OPL3 channels
struct AdlChannel
{
- // For collisions
struct Location
{
uint16_t MidCh;
uint8_t note;
- bool operator==(const Location &b) const
- {
- return MidCh == b.MidCh && note == b.note;
- }
- bool operator< (const Location &b) const
- {
- return MidCh < b.MidCh || (MidCh == b.MidCh && note < b.note);
- }
- char ____padding[1];
+ bool operator==(const Location &l) const
+ { return MidCh == l.MidCh && note == l.note; }
+ bool operator!=(const Location &l) const
+ { return !operator==(l); }
};
struct LocationData
{
+ LocationData *prev, *next;
+ Location loc;
bool sustained;
char ____padding[7];
MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[]
int64_t kon_time_until_neglible;
int64_t vibdelay;
};
- typedef std::map<Location, LocationData> users_t;
- users_t users;
// If the channel is keyoff'd
int64_t koff_time_until_neglible;
+
+ enum { users_max = 128 };
+ LocationData *users_first, *users_free_cells;
+ LocationData users_cells[users_max];
+ unsigned users_size;
+
+ bool users_empty() const
+ { return !users_first; }
+ LocationData *users_find(Location loc)
+ {
+ LocationData *user = NULL;
+ for(LocationData *curr = users_first; !user && curr; curr = curr->next)
+ if(curr->loc == loc)
+ user = curr;
+ return user;
+ }
+ LocationData *users_allocate()
+ {
+ // remove free cells front
+ LocationData *user = users_free_cells;
+ if(!user)
+ return NULL;
+ users_free_cells = user->next;
+ users_free_cells->prev = NULL;
+ // add to users front
+ if(users_first)
+ users_first->prev = user;
+ user->prev = NULL;
+ user->next = users_first;
+ users_first = user;
+ ++users_size;
+ return user;
+ }
+ LocationData *users_find_or_create(Location loc)
+ {
+ LocationData *user = users_find(loc);
+ if(!user) {
+ user = users_allocate();
+ if(!user)
+ return NULL;
+ LocationData *prev = user->prev, *next = user->next;
+ *user = LocationData();
+ user->prev = prev; user->next = next;
+ user->loc = loc;
+ }
+ return user;
+ }
+ LocationData *users_insert(const LocationData &x)
+ {
+ LocationData *user = users_find(x.loc);
+ if(!user)
+ {
+ user = users_allocate();
+ if(!user)
+ return NULL;
+ LocationData *prev = user->prev, *next = user->next;
+ *user = x;
+ user->prev = prev; user->next = next;
+ }
+ return user;
+ }
+ void users_erase(LocationData *user)
+ {
+ if(user->prev)
+ user->prev->next = user->next;
+ if(user->next)
+ user->next->prev = user->prev;
+ if(user == users_first)
+ users_first = user->next;
+ user->prev = NULL;
+ user->next = users_free_cells;
+ users_free_cells = user;
+ --users_size;
+ }
+
// For channel allocation:
- AdlChannel(): users(), koff_time_until_neglible(0) { }
+ AdlChannel(): koff_time_until_neglible(0), users_first(NULL), users_size(0)
+ {
+ users_free_cells = users_cells;
+ for(size_t i = 0; i < users_max; ++i)
+ {
+ users_cells[i].prev = (i > 0) ? &users_cells[i - 1] : NULL;
+ users_cells[i].next = (i + 1 < users_max) ? &users_cells[i + 1] : NULL;
+ }
+ }
void AddAge(int64_t ms);
};
@@ -964,7 +1167,21 @@ public:
Setup m_setup;
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ /**
+ * @brief Utility function to read Big-Endian integer from raw binary data
+ * @param buffer Pointer to raw binary buffer
+ * @param nbytes Count of bytes to parse integer
+ * @return Extracted unsigned integer
+ */
static uint64_t ReadBEint(const void *buffer, size_t nbytes);
+
+ /**
+ * @brief Utility function to read Little-Endian integer from raw binary data
+ * @param buffer Pointer to raw binary buffer
+ * @param nbytes Count of bytes to parse integer
+ * @return Extracted unsigned integer
+ */
static uint64_t ReadLEint(const void *buffer, size_t nbytes);
/**
@@ -981,6 +1198,7 @@ public:
* @return Unsigned integer that conains parsed variable-length value
*/
uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok);
+#endif
bool LoadBank(const std::string &filename);
bool LoadBank(const void *data, size_t size);
@@ -1104,7 +1322,7 @@ private:
void KillOrEvacuate(
size_t from_channel,
- AdlChannel::users_t::iterator j,
+ AdlChannel::LocationData *j,
MIDIchannel::activenoteiterator i);
void Panic();
void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1);
diff --git a/src/chips/opl_chip_base.cpp b/src/chips/opl_chip_base.cpp
index 4a7c4f5..670a998 100644
--- a/src/chips/opl_chip_base.cpp
+++ b/src/chips/opl_chip_base.cpp
@@ -26,7 +26,7 @@ int OPLChipBase::generate32(int32_t *output, size_t frames)
enum { maxFramesAtOnce = 256 };
int16_t temp[2 * maxFramesAtOnce];
for(size_t left = frames; left > 0;) {
- size_t count = (left < maxFramesAtOnce) ? left : maxFramesAtOnce;
+ size_t count = (left < static_cast<size_t>(maxFramesAtOnce)) ? left : static_cast<size_t>(maxFramesAtOnce);
generate(temp, count);
for(size_t i = 0; i < 2 * count; ++i)
output[i] = temp[i];
@@ -41,7 +41,7 @@ int OPLChipBase::generateAndMix32(int32_t *output, size_t frames)
enum { maxFramesAtOnce = 256 };
int16_t temp[2 * maxFramesAtOnce];
for(size_t left = frames; left > 0;) {
- size_t count = (left < maxFramesAtOnce) ? left : maxFramesAtOnce;
+ size_t count = (left < static_cast<size_t>(maxFramesAtOnce)) ? left : static_cast<size_t>(maxFramesAtOnce);
generate(temp, count);
for(size_t i = 0; i < 2 * count; ++i)
output[i] += temp[i];
diff --git a/src/wopl/wopl_file.c b/src/wopl/wopl_file.c
new file mode 100644
index 0000000..25b75be
--- /dev/null
+++ b/src/wopl/wopl_file.c
@@ -0,0 +1,584 @@
+/*
+ * 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] = ((uint16_t)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->inst_name[32] = '\0';
+ 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_name[32] = '\0';
+ 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..f5d816b
--- /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 WOPLFileFlags
+{
+ /* 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_VolumeModel
+{
+ WOPL_VM_Generic = 0,
+ WOPL_VM_Native,
+ WOPL_VM_DMX,
+ WOPL_VM_Apogee,
+ WOPL_VM_Win9x
+} WOPL_VolumeModel;
+
+typedef enum WOPL_InstrumentFlags
+{
+ /* 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 WOPL_RythmMode
+{
+ /* 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_ErrorCodes
+{
+ 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 WOPLOperator
+{
+ /* 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 WOPLInstrument
+{
+ /* 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 WOPLBank
+{
+ /* 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 WOPIFile
+{
+ /* 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 WOPLFile
+{
+ /* 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 */