mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-10 06:42:08 +00:00
Upgrade libADLMIDI and libOPNMIDI
Added full-panning stereo, improvement of channel management, and many other things. Also, I have implemented an ability to use custom WOPL (for libADLMIDI) and WOPN (for libOPNMIDI) banks from the same path as "soundfonts", but also, in the same environment, the "fm_banks" folder was added for WOPL/WOPN storing purposes. To toggle usage of embedded or custom bank, I have added togglable booleans. When bank fails to be loaded, the default embedded bank is getting to be used as fallback. ADLMIDI 1.4.0 2018-10-01 * Implemented a full support for Portamento! (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added support for SysEx event handling! (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added support for GS way of custom drum channels (through SysEx events) * Ignore some NRPN events and lsb bank number when using GS standard (after catching of GS Reset SysEx call) * Added support for CC66-Sostenuto controller (Pedal hold of currently-pressed notes only while CC64 holds also all next notes) * Added support for CC67-SoftPedal controller (SoftPedal lowers the volume of notes played) * Fixed correctness of CMF files playing * Fixed unnecessary overuse of chip channels by blank notes * Added API to disable specific MIDI tracks or play one of MIDI tracks solo * Added support for more complex loop (loopStart=XX, loopEnd=0). Where XX - count of loops, or 0 - infinite. Nested loops are supported without of any limits. * Added working implementation of TMB's velocity offset * Added support for full-panning stereo option (Thanks to [Christopher Snowhill](https://github.com/kode54) for a work!) * Fixed inability to play high notes due physical tone frequency out of range on the OPL3 chip OPNMIDI 1.4.0 2018-10-01 * Implemented a full support for Portamento! (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added support for SysEx event handling! (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!) * Added support for GS way of custom drum channels (through SysEx events) * Ignore some NRPN events and lsb bank number when using GS standard (after catching of GS Reset SysEx call) * Added support for CC66-Sostenuto controller (Pedal hold of currently-pressed notes only while CC64 holds also all next notes) * Added support for CC67-SoftPedal controller (SoftPedal lowers the volume of notes played) * Resolved a trouble which sometimes makes a junk noise sound and unnecessary overuse of chip channels * Volume models support taken from libADLMIDI has been adapted to OPN2's chip speficis * Fixed inability to play high notes due physical tone frequency out of range on the OPN2 chip * Added support for full-panning stereo option ADL&OPN Hotfix: re-calculated default banks The fix on side of measurer of OPL3-BE and OPN2-BE where some instruments getting zero releasing time.
This commit is contained in:
parent
2fff5c4c39
commit
59c8d8ff64
72 changed files with 15167 additions and 12336 deletions
BIN
fm_banks/GENMIDI.GS.wopl
Normal file
BIN
fm_banks/GENMIDI.GS.wopl
Normal file
Binary file not shown.
BIN
fm_banks/gs-by-papiezak-and-sneakernets.wopn
Normal file
BIN
fm_banks/gs-by-papiezak-and-sneakernets.wopn
Normal file
Binary file not shown.
|
@ -633,12 +633,9 @@ if( NOT SEND_ANON_STATS )
|
|||
add_definitions( -DNO_SEND_STATS )
|
||||
endif()
|
||||
|
||||
# OPLMIDI needs for USE_LEGACY_EMULATOR macro to be correctly built
|
||||
add_definitions(-DOPNMIDI_USE_LEGACY_EMULATOR)
|
||||
|
||||
# Disable ADLMIDI's and OPNMIDI's MIDI Sequencer, MUS and XMI converters
|
||||
add_definitions(-DADLMIDI_DISABLE_MUS_SUPPORT -DADLMIDI_DISABLE_XMI_SUPPORT -DADLMIDI_DISABLE_MIDI_SEQUENCER)
|
||||
add_definitions(-DOPNMIDI_DISABLE_MUS_SUPPORT -DOPNMIDI_DISABLE_XMI_SUPPORT -DOPNMIDI_DISABLE_MIDI_SEQUENCER)
|
||||
# Disable ADLMIDI's and OPNMIDI's MIDI Sequencer
|
||||
add_definitions(-DADLMIDI_DISABLE_MIDI_SEQUENCER)
|
||||
add_definitions(-DOPNMIDI_DISABLE_MIDI_SEQUENCER)
|
||||
|
||||
# Disable OPNMIDI's experimental yet emulator (using of it has some issues and missing notes in playback)
|
||||
add_definitions(-DOPNMIDI_DISABLE_GX_EMULATOR)
|
||||
|
@ -876,6 +873,7 @@ set( FASTMATH_SOURCES
|
|||
sound/opnmidi/opnmidi_midiplay.cpp
|
||||
sound/opnmidi/opnmidi_opn2.cpp
|
||||
sound/opnmidi/opnmidi_private.cpp
|
||||
sound/opnmidi/wopn/wopn_file.c
|
||||
|
||||
)
|
||||
|
||||
|
@ -1352,7 +1350,12 @@ endif()
|
|||
|
||||
add_custom_command(TARGET zdoom POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/soundfont/gzdoom.sf2 $<TARGET_FILE_DIR:zdoom>/soundfonts/gzdoom.sf2)
|
||||
${CMAKE_SOURCE_DIR}/soundfont/gzdoom.sf2 $<TARGET_FILE_DIR:zdoom>/soundfonts/gzdoom.sf2
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/fm_banks/GENMIDI.GS.wopl $<TARGET_FILE_DIR:zdoom>/fm_banks/GENMIDI.GS.wopl
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/fm_banks/gs-by-papiezak-and-sneakernets.wopn $<TARGET_FILE_DIR:zdoom>/fm_banks/gs-by-papiezak-and-sneakernets.wopn
|
||||
)
|
||||
|
||||
if( CMAKE_COMPILER_IS_GNUCXX )
|
||||
# GCC misoptimizes this file
|
||||
|
|
|
@ -136,17 +136,27 @@ FGameConfigFile::FGameConfigFile ()
|
|||
SetSection("SoundfontSearch.Directories", true);
|
||||
#ifdef __APPLE__
|
||||
SetValueForKey("Path", user_docs + "/soundfonts", true);
|
||||
SetValueForKey("Path", user_docs + "/fm_banks", true);
|
||||
SetValueForKey("Path", user_app_support + "/soundfonts", true);
|
||||
SetValueForKey("Path", user_app_support + "/fm_banks", true);
|
||||
SetValueForKey("Path", "$PROGDIR/soundfonts", true);
|
||||
SetValueForKey("Path", "$PROGDIR/fm_banks", true);
|
||||
SetValueForKey("Path", local_app_support + "/soundfonts", true);
|
||||
SetValueForKey("Path", local_app_support + "/fm_banks", true);
|
||||
#elif !defined(__unix__)
|
||||
SetValueForKey("Path", "$PROGDIR/soundfonts", true);
|
||||
SetValueForKey("Path", "$PROGDIR/fm_banks", true);
|
||||
#else
|
||||
SetValueForKey("Path", "$HOME/" GAME_DIR "/soundfonts", true);
|
||||
SetValueForKey("Path", "$HOME/" GAME_DIR "/fm_banks", true);
|
||||
SetValueForKey("Path", "/usr/local/share/doom/soundfonts", true);
|
||||
SetValueForKey("Path", "/usr/local/share/doom/fm_banks", true);
|
||||
SetValueForKey("Path", "/usr/local/share/games/doom/soundfonts", true);
|
||||
SetValueForKey("Path", "/usr/local/share/games/doom/fm_banks", true);
|
||||
SetValueForKey("Path", "/usr/share/doom/soundfonts", true);
|
||||
SetValueForKey("Path", "/usr/share/doom/fm_banks", true);
|
||||
SetValueForKey("Path", "/usr/share/games/doom/soundfonts", true);
|
||||
SetValueForKey("Path", "/usr/share/games/doom/fm_banks", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -1382,10 +1382,12 @@ static void InitMusicMenus()
|
|||
{
|
||||
DMenuDescriptor **advmenu = MenuDescriptors.CheckKey("AdvSoundOptions");
|
||||
auto soundfonts = sfmanager.GetList();
|
||||
std::tuple<const char *, int, const char *> sfmenus[] = { std::make_tuple("GusConfigMenu", SF_SF2 | SF_GUS, "midi_config"),
|
||||
std::make_tuple("WildMidiConfigMenu", SF_GUS, "wildmidi_config"),
|
||||
std::make_tuple("TimidityConfigMenu", SF_SF2 | SF_GUS, "timidity_config"),
|
||||
std::make_tuple("FluidPatchsetMenu", SF_SF2, "fluid_patchset") };
|
||||
std::tuple<const char *, int, const char *> sfmenus[] = { std::make_tuple("GusConfigMenu", SF_SF2 | SF_GUS, "midi_config"),
|
||||
std::make_tuple("WildMidiConfigMenu", SF_GUS, "wildmidi_config"),
|
||||
std::make_tuple("TimidityConfigMenu", SF_SF2 | SF_GUS, "timidity_config"),
|
||||
std::make_tuple("FluidPatchsetMenu", SF_SF2, "fluid_patchset"),
|
||||
std::make_tuple("ADLMIDICustomBanksMenu", SF_WOPL, "adl_custom_bank"),
|
||||
std::make_tuple("OPNMIDICustomBanksMenu", SF_WOPN, "opn_custom_bank")};
|
||||
|
||||
for (auto &p : sfmenus)
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <cstring>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
#define ADLDATA_BYTE_COMPARABLE(T) \
|
||||
|
@ -34,32 +35,32 @@
|
|||
inline bool operator!=(const T &a, const T &b) \
|
||||
{ return !operator==(a, b); }
|
||||
|
||||
extern const struct adldata
|
||||
struct adldata
|
||||
{
|
||||
uint32_t modulator_E862, carrier_E862; // See below
|
||||
uint8_t modulator_40, carrier_40; // KSL/attenuation settings
|
||||
uint8_t feedconn; // Feedback/connection bits for the channel
|
||||
|
||||
int8_t finetune;
|
||||
} adl[];
|
||||
};
|
||||
ADLDATA_BYTE_COMPARABLE(struct adldata)
|
||||
enum { adlDefaultNumber = 189 };
|
||||
|
||||
extern const struct adlinsdata
|
||||
struct adlinsdata
|
||||
{
|
||||
enum { Flag_Pseudo4op = 0x01, Flag_NoSound = 0x02, Flag_Real4op = 0x04 };
|
||||
|
||||
enum { Flag_RM_BassDrum = 0x08, Flag_RM_Snare = 0x10, Flag_RM_TomTom = 0x18,
|
||||
Flag_RM_Cymbal = 0x20, Flag_RM_HiHat = 0x28, Mask_RhythmMode = 0x38 };
|
||||
|
||||
uint16_t adlno1, adlno2;
|
||||
uint8_t tone;
|
||||
uint8_t flags;
|
||||
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
|
||||
uint16_t ms_sound_koff;
|
||||
int8_t midi_velocity_offset;
|
||||
double voice2_fine_tune;
|
||||
} adlins[];
|
||||
};
|
||||
ADLDATA_BYTE_COMPARABLE(struct adlinsdata)
|
||||
int maxAdlBanks();
|
||||
extern const unsigned short banks[][256];
|
||||
extern const char* const banknames[];
|
||||
|
||||
enum { adlNoteOnMaxTime = 40000 };
|
||||
|
||||
|
@ -73,9 +74,9 @@ struct adlinsdata2
|
|||
uint8_t flags;
|
||||
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
|
||||
uint16_t ms_sound_koff;
|
||||
int8_t midi_velocity_offset;
|
||||
double voice2_fine_tune;
|
||||
adlinsdata2() {}
|
||||
explicit adlinsdata2(const adlinsdata &d);
|
||||
static adlinsdata2 from_adldata(const adlinsdata &d);
|
||||
};
|
||||
ADLDATA_BYTE_COMPARABLE(struct adlinsdata2)
|
||||
|
||||
|
@ -85,25 +86,43 @@ ADLDATA_BYTE_COMPARABLE(struct adlinsdata2)
|
|||
/**
|
||||
* @brief Bank global setup
|
||||
*/
|
||||
extern const struct AdlBankSetup
|
||||
struct AdlBankSetup
|
||||
{
|
||||
int volumeModel;
|
||||
bool deepTremolo;
|
||||
bool deepVibrato;
|
||||
bool adLibPercussions;
|
||||
bool scaleModulators;
|
||||
} adlbanksetup[];
|
||||
};
|
||||
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
int maxAdlBanks();
|
||||
extern const adldata adl[];
|
||||
extern const adlinsdata adlins[];
|
||||
extern const unsigned short banks[][256];
|
||||
extern const char* const banknames[];
|
||||
extern const AdlBankSetup adlbanksetup[];
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Conversion of storage formats
|
||||
*/
|
||||
inline adlinsdata2::adlinsdata2(const adlinsdata &d)
|
||||
: tone(d.tone), flags(d.flags),
|
||||
ms_sound_kon(d.ms_sound_kon), ms_sound_koff(d.ms_sound_koff),
|
||||
voice2_fine_tune(d.voice2_fine_tune)
|
||||
inline adlinsdata2 adlinsdata2::from_adldata(const adlinsdata &d)
|
||||
{
|
||||
adl[0] = ::adl[d.adlno1];
|
||||
adl[1] = ::adl[d.adlno2];
|
||||
adlinsdata2 ins;
|
||||
ins.tone = d.tone;
|
||||
ins.flags = d.flags;
|
||||
ins.ms_sound_kon = d.ms_sound_kon;
|
||||
ins.ms_sound_koff = d.ms_sound_koff;
|
||||
ins.midi_velocity_offset = d.midi_velocity_offset;
|
||||
ins.voice2_fine_tune = d.voice2_fine_tune;
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
std::memset(ins.adl, 0, sizeof(adldata) * 2);
|
||||
#else
|
||||
ins.adl[0] = ::adl[d.adlno1];
|
||||
ins.adl[1] = ::adl[d.adlno2];
|
||||
#endif
|
||||
return ins;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -24,9 +24,11 @@
|
|||
#ifndef ADLMIDI_HPP
|
||||
#define ADLMIDI_HPP
|
||||
|
||||
#include "adlmidi.h"
|
||||
|
||||
struct ADL_MIDIPlayer;
|
||||
|
||||
class AdlInstrumentTester
|
||||
class ADLMIDI_DECLSPEC AdlInstrumentTester
|
||||
{
|
||||
struct Impl;
|
||||
Impl *P;
|
||||
|
|
|
@ -40,7 +40,7 @@ template <class T>
|
|||
class BasicBankMap
|
||||
{
|
||||
public:
|
||||
typedef uint16_t key_type; /* the bank identifier */
|
||||
typedef size_t key_type; /* the bank identifier */
|
||||
typedef T mapped_type;
|
||||
typedef std::pair<key_type, T> value_type;
|
||||
|
||||
|
@ -74,7 +74,7 @@ private:
|
|||
enum
|
||||
{
|
||||
hash_bits = 8, /* worst case # of collisions: 128^2/2^hash_bits */
|
||||
hash_buckets = 1 << hash_bits,
|
||||
hash_buckets = 1 << hash_bits
|
||||
};
|
||||
|
||||
public:
|
||||
|
|
|
@ -37,7 +37,7 @@ template <class T>
|
|||
inline size_t BasicBankMap<T>::hash(key_type key)
|
||||
{
|
||||
// disregard the 0 high bit in LSB
|
||||
key = (key & 127) | ((key >> 8) << 7);
|
||||
key = key_type(key & 127) | key_type((key >> 8) << 7);
|
||||
// take low part as hash value
|
||||
return key & (hash_buckets - 1);
|
||||
}
|
||||
|
|
124
src/sound/adlmidi/adlmidi_cvt.hpp
Normal file
124
src/sound/adlmidi/adlmidi_cvt.hpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "adldata.hh"
|
||||
#include "wopl/wopl_file.h"
|
||||
#include <cmath>
|
||||
|
||||
template <class WOPLI>
|
||||
static void cvt_generic_to_FMIns(adlinsdata2 &ins, const WOPLI &in)
|
||||
{
|
||||
ins.voice2_fine_tune = 0.0;
|
||||
int8_t voice2_fine_tune = in.second_voice_detune;
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
if(voice2_fine_tune == 1)
|
||||
ins.voice2_fine_tune = 0.000025;
|
||||
else if(voice2_fine_tune == -1)
|
||||
ins.voice2_fine_tune = -0.000025;
|
||||
else
|
||||
ins.voice2_fine_tune = voice2_fine_tune * (15.625 / 1000.0);
|
||||
}
|
||||
|
||||
ins.midi_velocity_offset = in.midi_velocity_offset;
|
||||
ins.tone = in.percussion_key_number;
|
||||
ins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? adlinsdata::Flag_Pseudo4op : 0;
|
||||
ins.flags|= (in.inst_flags & WOPL_Ins_4op) && ((in.inst_flags & WOPL_Ins_Pseudo4op) == 0) ? adlinsdata::Flag_Real4op : 0;
|
||||
ins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? adlinsdata::Flag_NoSound : 0;
|
||||
ins.flags|= in.inst_flags & WOPL_RhythmModeMask;
|
||||
|
||||
for(size_t op = 0, slt = 0; op < 4; op++, slt++)
|
||||
{
|
||||
ins.adl[slt].carrier_E862 =
|
||||
((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.adl[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL
|
||||
|
||||
op++;
|
||||
ins.adl[slt].modulator_E862 =
|
||||
((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.adl[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL
|
||||
}
|
||||
|
||||
ins.adl[0].finetune = static_cast<int8_t>(in.note_offset1);
|
||||
ins.adl[0].feedconn = in.fb_conn1_C0;
|
||||
ins.adl[1].finetune = static_cast<int8_t>(in.note_offset2);
|
||||
ins.adl[1].feedconn = in.fb_conn2_C0;
|
||||
|
||||
ins.ms_sound_kon = in.delay_on_ms;
|
||||
ins.ms_sound_koff = in.delay_off_ms;
|
||||
}
|
||||
|
||||
template <class WOPLI>
|
||||
static void cvt_FMIns_to_generic(WOPLI &ins, const adlinsdata2 &in)
|
||||
{
|
||||
ins.second_voice_detune = 0;
|
||||
double voice2_fine_tune = in.voice2_fine_tune;
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
if(voice2_fine_tune > 0 && voice2_fine_tune <= 0.000025)
|
||||
ins.second_voice_detune = 1;
|
||||
else if(voice2_fine_tune < 0 && voice2_fine_tune >= -0.000025)
|
||||
ins.second_voice_detune = -1;
|
||||
else
|
||||
{
|
||||
long value = static_cast<long>(round(voice2_fine_tune * (1000.0 / 15.625)));
|
||||
value = (value < -128) ? -128 : value;
|
||||
value = (value > +127) ? +127 : value;
|
||||
ins.second_voice_detune = static_cast<int8_t>(value);
|
||||
}
|
||||
}
|
||||
|
||||
ins.midi_velocity_offset = in.midi_velocity_offset;
|
||||
ins.percussion_key_number = in.tone;
|
||||
ins.inst_flags = (in.flags & (adlinsdata::Flag_Pseudo4op|adlinsdata::Flag_Real4op)) ? WOPL_Ins_4op : 0;
|
||||
ins.inst_flags|= (in.flags & adlinsdata::Flag_Pseudo4op) ? WOPL_Ins_Pseudo4op : 0;
|
||||
ins.inst_flags|= (in.flags & adlinsdata::Flag_NoSound) ? WOPL_Ins_IsBlank : 0;
|
||||
ins.inst_flags |= in.flags & adlinsdata::Mask_RhythmMode;
|
||||
|
||||
for(size_t op = 0; op < 4; op++)
|
||||
{
|
||||
const adldata &in2op = in.adl[(op < 2) ? 0 : 1];
|
||||
uint32_t regE862 = ((op & 1) == 0) ? in2op.carrier_E862 : in2op.modulator_E862;
|
||||
uint8_t reg40 = ((op & 1) == 0) ? in2op.carrier_40 : in2op.modulator_40;
|
||||
|
||||
ins.operators[op].waveform_E0 = static_cast<uint8_t>(regE862 >> 24);
|
||||
ins.operators[op].susrel_80 = static_cast<uint8_t>(regE862 >> 16);
|
||||
ins.operators[op].atdec_60 = static_cast<uint8_t>(regE862 >> 8);
|
||||
ins.operators[op].avekf_20 = static_cast<uint8_t>(regE862 >> 0);
|
||||
ins.operators[op].ksl_l_40 = reg40;
|
||||
}
|
||||
|
||||
ins.note_offset1 = in.adl[0].finetune;
|
||||
ins.fb_conn1_C0 = in.adl[0].feedconn;
|
||||
ins.note_offset2 = in.adl[1].finetune;
|
||||
ins.fb_conn2_C0 = in.adl[1].feedconn;
|
||||
|
||||
ins.delay_on_ms = in.ms_sound_kon;
|
||||
ins.delay_off_ms = in.ms_sound_koff;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
|
@ -22,168 +22,23 @@
|
|||
*/
|
||||
|
||||
#include "adlmidi_private.hpp"
|
||||
#include "adlmidi_cvt.hpp"
|
||||
#include "wopl/wopl_file.h"
|
||||
|
||||
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
# ifndef ADLMIDI_DISABLE_MUS_SUPPORT
|
||||
# include "adlmidi_mus2mid.h"
|
||||
# endif//MUS
|
||||
# ifndef ADLMIDI_DISABLE_XMI_SUPPORT
|
||||
# include "adlmidi_xmi2mid.h"
|
||||
# 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;
|
||||
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
|
||||
|
||||
for(unsigned n = 0; n < nbytes; ++n)
|
||||
result = (result << 8) + data[n];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
|
||||
|
||||
for(unsigned n = 0; n < nbytes; ++n)
|
||||
result = result + static_cast<uint64_t>(data[n] << (n * 8));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool MIDIplay::LoadBank(const std::string &filename)
|
||||
{
|
||||
fileReader file;
|
||||
FileAndMemReader file;
|
||||
file.openFile(filename.c_str());
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(const void *data, size_t size)
|
||||
{
|
||||
fileReader file;
|
||||
FileAndMemReader file;
|
||||
file.openData(data, size);
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
template <class WOPLI>
|
||||
static void cvt_generic_to_FMIns(adlinsdata2 &ins, const WOPLI &in)
|
||||
{
|
||||
ins.voice2_fine_tune = 0.0;
|
||||
int8_t voice2_fine_tune = in.second_voice_detune;
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
if(voice2_fine_tune == 1)
|
||||
ins.voice2_fine_tune = 0.000025;
|
||||
else if(voice2_fine_tune == -1)
|
||||
ins.voice2_fine_tune = -0.000025;
|
||||
else
|
||||
ins.voice2_fine_tune = voice2_fine_tune * (15.625 / 1000.0);
|
||||
}
|
||||
|
||||
ins.tone = in.percussion_key_number;
|
||||
ins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? adlinsdata::Flag_Pseudo4op : 0;
|
||||
ins.flags|= (in.inst_flags & WOPL_Ins_4op) && ((in.inst_flags & WOPL_Ins_Pseudo4op) == 0) ? adlinsdata::Flag_Real4op : 0;
|
||||
ins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? adlinsdata::Flag_NoSound : 0;
|
||||
|
||||
bool fourOps = (in.inst_flags & WOPL_Ins_4op) || (in.inst_flags & WOPL_Ins_Pseudo4op);
|
||||
for(size_t op = 0, slt = 0; op < static_cast<size_t>(fourOps ? 4 : 2); op++, slt++)
|
||||
{
|
||||
ins.adl[slt].carrier_E862 =
|
||||
((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.adl[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL
|
||||
|
||||
op++;
|
||||
ins.adl[slt].modulator_E862 =
|
||||
((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.adl[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL
|
||||
}
|
||||
|
||||
ins.adl[0].finetune = static_cast<int8_t>(in.note_offset1);
|
||||
ins.adl[0].feedconn = in.fb_conn1_C0;
|
||||
if(!fourOps)
|
||||
ins.adl[1] = ins.adl[0];
|
||||
else
|
||||
{
|
||||
ins.adl[1].finetune = static_cast<int8_t>(in.note_offset2);
|
||||
ins.adl[1].feedconn = in.fb_conn2_C0;
|
||||
}
|
||||
|
||||
ins.ms_sound_kon = in.delay_on_ms;
|
||||
ins.ms_sound_koff = in.delay_off_ms;
|
||||
}
|
||||
|
||||
template <class WOPLI>
|
||||
static void cvt_FMIns_to_generic(WOPLI &ins, const adlinsdata2 &in)
|
||||
{
|
||||
ins.second_voice_detune = 0;
|
||||
double voice2_fine_tune = in.voice2_fine_tune;
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
if(voice2_fine_tune > 0 && voice2_fine_tune <= 0.000025)
|
||||
ins.second_voice_detune = 1;
|
||||
else if(voice2_fine_tune < 0 && voice2_fine_tune >= -0.000025)
|
||||
ins.second_voice_detune = -1;
|
||||
else
|
||||
{
|
||||
long value = lround(voice2_fine_tune * (1000.0 / 15.625));
|
||||
value = (value < -128) ? -128 : value;
|
||||
value = (value > +127) ? +127 : value;
|
||||
ins.second_voice_detune = static_cast<int8_t>(value);
|
||||
}
|
||||
}
|
||||
|
||||
ins.percussion_key_number = in.tone;
|
||||
bool fourOps = (in.flags & adlinsdata::Flag_Pseudo4op) || in.adl[0] != in.adl[1];
|
||||
ins.inst_flags = fourOps ? WOPL_Ins_4op : 0;
|
||||
ins.inst_flags|= (in.flags & adlinsdata::Flag_Pseudo4op) ? WOPL_Ins_Pseudo4op : 0;
|
||||
ins.inst_flags|= (in.flags & adlinsdata::Flag_NoSound) ? WOPL_Ins_IsBlank : 0;
|
||||
|
||||
for(size_t op = 0, slt = 0; op < static_cast<size_t>(fourOps ? 4 : 2); op++, slt++)
|
||||
{
|
||||
ins.operators[op].waveform_E0 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 24);
|
||||
ins.operators[op].susrel_80 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 16);
|
||||
ins.operators[op].atdec_60 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 8);
|
||||
ins.operators[op].avekf_20 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 0);
|
||||
ins.operators[op].ksl_l_40 = in.adl[slt].carrier_40;
|
||||
|
||||
op++;
|
||||
ins.operators[op].waveform_E0 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 24);
|
||||
ins.operators[op].susrel_80 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 16);
|
||||
ins.operators[op].atdec_60 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 8);
|
||||
ins.operators[op].avekf_20 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 0);
|
||||
ins.operators[op].ksl_l_40 = in.adl[slt].carrier_40;
|
||||
}
|
||||
|
||||
ins.note_offset1 = in.adl[0].finetune;
|
||||
ins.fb_conn1_C0 = in.adl[0].feedconn;
|
||||
if(!fourOps)
|
||||
{
|
||||
ins.operators[2] = ins.operators[0];
|
||||
ins.operators[3] = ins.operators[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
ins.note_offset2 = in.adl[1].finetune;
|
||||
ins.fb_conn2_C0 = in.adl[1].feedconn;
|
||||
}
|
||||
|
||||
ins.delay_on_ms = in.ms_sound_kon;
|
||||
ins.delay_off_ms = in.ms_sound_koff;
|
||||
}
|
||||
|
||||
void cvt_ADLI_to_FMIns(adlinsdata2 &ins, const ADL_Instrument &in)
|
||||
{
|
||||
return cvt_generic_to_FMIns(ins, in);
|
||||
|
@ -194,7 +49,7 @@ void cvt_FMIns_to_ADLI(ADL_Instrument &ins, const adlinsdata2 &in)
|
|||
cvt_FMIns_to_generic(ins, in);
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
|
||||
bool MIDIplay::LoadBank(FileAndMemReader &fr)
|
||||
{
|
||||
int err = 0;
|
||||
WOPLFile *wopl = NULL;
|
||||
|
@ -207,9 +62,8 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
|
|||
}
|
||||
|
||||
// Read complete bank file into the memory
|
||||
fr.seek(0, SEEK_END);
|
||||
fsize = fr.tell();
|
||||
fr.seek(0, SEEK_SET);
|
||||
fsize = fr.fileSize();
|
||||
fr.seek(0, FileAndMemReader::SET);
|
||||
// Allocate necessary memory block
|
||||
raw_file_data = (char*)malloc(fsize);
|
||||
if(!raw_file_data)
|
||||
|
@ -250,29 +104,28 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
|
|||
}
|
||||
}
|
||||
|
||||
opl.dynamic_bank_setup.adLibPercussions = false;
|
||||
opl.dynamic_bank_setup.scaleModulators = false;
|
||||
opl.dynamic_bank_setup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0;
|
||||
opl.dynamic_bank_setup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0;
|
||||
opl.dynamic_bank_setup.volumeModel = wopl->volume_model;
|
||||
m_setup.HighTremoloMode = -1;
|
||||
m_setup.HighVibratoMode = -1;
|
||||
m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO;
|
||||
m_synth.m_insBankSetup.adLibPercussions = false;
|
||||
m_synth.m_insBankSetup.scaleModulators = false;
|
||||
m_synth.m_insBankSetup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0;
|
||||
m_synth.m_insBankSetup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0;
|
||||
m_synth.m_insBankSetup.volumeModel = wopl->volume_model;
|
||||
m_setup.deepTremoloMode = -1;
|
||||
m_setup.deepVibratoMode = -1;
|
||||
m_setup.volumeScaleModel = ADLMIDI_VolumeModel_AUTO;
|
||||
|
||||
opl.setEmbeddedBank(m_setup.AdlBank);
|
||||
m_synth.setEmbeddedBank(m_setup.bankId);
|
||||
|
||||
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(unsigned ss = 0; ss < 2; ss++)
|
||||
for(size_t ss = 0; ss < 2; ss++)
|
||||
{
|
||||
for(unsigned i = 0; i < slots_counts[ss]; i++)
|
||||
for(size_t i = 0; i < slots_counts[ss]; i++)
|
||||
{
|
||||
unsigned bankno =
|
||||
(slots_src_ins[ss][i].bank_midi_msb * 256) +
|
||||
slots_src_ins[ss][i].bank_midi_lsb +
|
||||
(ss ? OPL3::PercussionTag : 0);
|
||||
OPL3::Bank &bank = opl.dynamic_banks[bankno];
|
||||
size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) +
|
||||
(slots_src_ins[ss][i].bank_midi_lsb) +
|
||||
(ss ? size_t(OPL3::PercussionTag) : 0);
|
||||
OPL3::Bank &bank = m_synth.m_insBanks[bankno];
|
||||
for(int j = 0; j < 128; j++)
|
||||
{
|
||||
adlinsdata2 &ins = bank.ins[j];
|
||||
|
@ -283,7 +136,7 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
|
|||
}
|
||||
}
|
||||
|
||||
opl.AdlBank = ~0u; // Use dynamic banks!
|
||||
m_synth.m_embeddedBank = OPL3::CustomBankTag; // Use dynamic banks!
|
||||
//Percussion offset is count of instruments multipled to count of melodic banks
|
||||
applySetup();
|
||||
|
||||
|
@ -293,189 +146,45 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
|
|||
}
|
||||
|
||||
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
bool MIDIplay::LoadMIDI(const std::string &filename)
|
||||
{
|
||||
fileReader file;
|
||||
file.openFile(filename.c_str());
|
||||
if(!LoadMIDI(file))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadMIDI(const void *data, size_t size)
|
||||
bool MIDIplay::LoadMIDI_pre()
|
||||
{
|
||||
fileReader file;
|
||||
file.openData(data, size);
|
||||
return LoadMIDI(file);
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr)
|
||||
{
|
||||
size_t fsize;
|
||||
ADL_UNUSED(fsize);
|
||||
//! Temp buffer for conversion
|
||||
AdlMIDI_CPtr<uint8_t> cvt_buf;
|
||||
errorString.clear();
|
||||
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
if((opl.AdlBank != ~0u) || opl.dynamic_banks.empty())
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
if((m_synth.m_embeddedBank != OPL3::CustomBankTag) || m_synth.m_insBanks.empty())
|
||||
{
|
||||
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!fr.isValid())
|
||||
{
|
||||
errorStringOut = "Invalid data stream!\n";
|
||||
#ifndef _WIN32
|
||||
errorStringOut += std::strerror(errno);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
/**** Set all properties BEFORE starting of actial file reading! ****/
|
||||
resetMIDI();
|
||||
applySetup();
|
||||
|
||||
atEnd = false;
|
||||
loopStart = true;
|
||||
invalidLoop = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_GMF = false; // GMD/MUS files (ScummVM)
|
||||
//bool is_MUS = false; // MUS/DMX files (Doom)
|
||||
bool is_IMF = false; // IMF
|
||||
bool is_CMF = false; // Creative Music format (CMF/CTMF)
|
||||
bool is_RSXX = false; // RSXX, such as Cartooners
|
||||
|
||||
const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14
|
||||
char HeaderBuf[HeaderSize] = "";
|
||||
size_t DeltaTicks = 192, TrackCount = 1;
|
||||
|
||||
riffskip:
|
||||
fsize = fr.read(HeaderBuf, 1, HeaderSize);
|
||||
|
||||
if(std::memcmp(HeaderBuf, "RIFF", 4) == 0)
|
||||
bool MIDIplay::LoadMIDI_post()
|
||||
{
|
||||
MidiSequencer::FileFormat format = m_sequencer.getFormat();
|
||||
if(format == MidiSequencer::Format_CMF)
|
||||
{
|
||||
fr.seek(6l, SEEK_CUR);
|
||||
goto riffskip;
|
||||
}
|
||||
const std::vector<MidiSequencer::CmfInstrument> &instruments = m_sequencer.getRawCmfInstruments();
|
||||
m_synth.m_insBanks.clear();//Clean up old banks
|
||||
|
||||
if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0)
|
||||
{
|
||||
// GMD/MUS files (ScummVM)
|
||||
fr.seek(7 - static_cast<long>(HeaderSize), SEEK_CUR);
|
||||
is_GMF = true;
|
||||
}
|
||||
#ifndef ADLMIDI_DISABLE_MUS_SUPPORT
|
||||
else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0)
|
||||
{
|
||||
// MUS/DMX files (Doom)
|
||||
fr.seek(0, SEEK_END);
|
||||
size_t mus_len = fr.tell();
|
||||
fr.seek(0, SEEK_SET);
|
||||
uint8_t *mus = (uint8_t *)malloc(mus_len);
|
||||
if(!mus)
|
||||
uint16_t ins_count = static_cast<uint16_t>(instruments.size());
|
||||
for(uint16_t i = 0; i < ins_count; ++i)
|
||||
{
|
||||
errorStringOut = "Out of memory!";
|
||||
return false;
|
||||
}
|
||||
fr.read(mus, 1, mus_len);
|
||||
//Close source stream
|
||||
fr.close();
|
||||
|
||||
uint8_t *mid = NULL;
|
||||
uint32_t mid_len = 0;
|
||||
int m2mret = AdlMidi_mus2midi(mus, static_cast<uint32_t>(mus_len),
|
||||
&mid, &mid_len, 0);
|
||||
if(mus) free(mus);
|
||||
if(m2mret < 0)
|
||||
{
|
||||
errorStringOut = "Invalid MUS/DMX data format!";
|
||||
return false;
|
||||
}
|
||||
cvt_buf.reset(mid);
|
||||
//Open converted MIDI file
|
||||
fr.openData(mid, static_cast<size_t>(mid_len));
|
||||
//Re-Read header again!
|
||||
goto riffskip;
|
||||
}
|
||||
#endif //ADLMIDI_DISABLE_MUS_SUPPORT
|
||||
#ifndef ADLMIDI_DISABLE_XMI_SUPPORT
|
||||
else if(std::memcmp(HeaderBuf, "FORM", 4) == 0)
|
||||
{
|
||||
if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0)
|
||||
{
|
||||
fr.close();
|
||||
errorStringOut = fr._fileName + ": Invalid format\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
fr.seek(0, SEEK_END);
|
||||
size_t mus_len = fr.tell();
|
||||
fr.seek(0, SEEK_SET);
|
||||
uint8_t *mus = (uint8_t*)malloc(mus_len);
|
||||
if(!mus)
|
||||
{
|
||||
errorStringOut = "Out of memory!";
|
||||
return false;
|
||||
}
|
||||
fr.read(mus, 1, mus_len);
|
||||
//Close source stream
|
||||
fr.close();
|
||||
|
||||
uint8_t *mid = NULL;
|
||||
uint32_t mid_len = 0;
|
||||
int m2mret = AdlMidi_xmi2midi(mus, static_cast<uint32_t>(mus_len),
|
||||
&mid, &mid_len, XMIDI_CONVERT_NOCONVERSION);
|
||||
if(mus) free(mus);
|
||||
if(m2mret < 0)
|
||||
{
|
||||
errorStringOut = "Invalid XMI data format!";
|
||||
return false;
|
||||
}
|
||||
cvt_buf.reset(mid);
|
||||
//Open converted MIDI file
|
||||
fr.openData(mid, static_cast<size_t>(mid_len));
|
||||
//Re-Read header again!
|
||||
goto riffskip;
|
||||
}
|
||||
#endif //ADLMIDI_DISABLE_XMI_SUPPORT
|
||||
else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0)
|
||||
{
|
||||
opl.dynamic_banks.clear();
|
||||
// Creative Music Format (CMF).
|
||||
// When playing CTMF files, use the following commandline:
|
||||
// adlmidi song8.ctmf -p -v 1 1 0
|
||||
// i.e. enable percussion mode, deeper vibrato, and use only 1 card.
|
||||
is_CMF = true;
|
||||
//unsigned version = ReadLEint(HeaderBuf+4, 2);
|
||||
uint64_t ins_start = ReadLEint(HeaderBuf + 6, 2);
|
||||
uint64_t mus_start = ReadLEint(HeaderBuf + 8, 2);
|
||||
//unsigned deltas = ReadLEint(HeaderBuf+10, 2);
|
||||
uint64_t ticks = ReadLEint(HeaderBuf + 12, 2);
|
||||
// Read title, author, remarks start offsets in file
|
||||
fr.read(HeaderBuf, 1, 6);
|
||||
//unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)};
|
||||
fr.seek(16, SEEK_CUR); // Skip the channels-in-use table
|
||||
fr.read(HeaderBuf, 1, 4);
|
||||
uint64_t ins_count = ReadLEint(HeaderBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2);
|
||||
fr.seek(static_cast<long>(ins_start), SEEK_SET);
|
||||
|
||||
//std::printf("%u instruments\n", ins_count);
|
||||
for(unsigned i = 0; i < ins_count; ++i)
|
||||
{
|
||||
unsigned bank = i / 256;
|
||||
bank = (bank & 127) + ((bank >> 7) << 8);
|
||||
const uint8_t *InsData = instruments[i].data;
|
||||
size_t bank = i / 256;
|
||||
bank = ((bank & 127) + ((bank >> 7) << 8));
|
||||
if(bank > 127 + (127 << 8))
|
||||
break;
|
||||
bank += (i % 256 < 128) ? 0 : OPL3::PercussionTag;
|
||||
bank += (i % 256 < 128) ? 0 : size_t(OPL3::PercussionTag);
|
||||
|
||||
unsigned char InsData[16];
|
||||
fr.read(InsData, 1, 16);
|
||||
/*std::printf("Ins %3u: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
|
||||
i, InsData[0],InsData[1],InsData[2],InsData[3], InsData[4],InsData[5],InsData[6],InsData[7],
|
||||
InsData[8],InsData[9],InsData[10],InsData[11], InsData[12],InsData[13],InsData[14],InsData[15]);*/
|
||||
adlinsdata2 &adlins = opl.dynamic_banks[bank].ins[i % 128];
|
||||
adlinsdata2 &adlins = m_synth.m_insBanks[bank].ins[i % 128];
|
||||
adldata adl;
|
||||
adl.modulator_E862 =
|
||||
((static_cast<uint32_t>(InsData[8] & 0x07) << 24) & 0xFF000000) //WaveForm
|
||||
|
@ -500,224 +209,79 @@ riffskip:
|
|||
adlins.voice2_fine_tune = 0.0;
|
||||
}
|
||||
|
||||
fr.seeku(mus_start, SEEK_SET);
|
||||
TrackCount = 1;
|
||||
DeltaTicks = (size_t)ticks;
|
||||
opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead
|
||||
m_synth.m_embeddedBank = OPL3::CustomBankTag; // Ignore AdlBank number, use dynamic banks instead
|
||||
//std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo);
|
||||
opl.AdlPercussionMode = true;
|
||||
opl.m_musicMode = OPL3::MODE_CMF;
|
||||
opl.m_volumeScale = OPL3::VOLUME_NATIVE;
|
||||
m_synth.m_rhythmMode = true;
|
||||
m_synth.m_musicMode = OPL3::MODE_CMF;
|
||||
m_synth.m_volumeScale = OPL3::VOLUME_NATIVE;
|
||||
|
||||
m_synth.m_numChips = 1;
|
||||
m_synth.m_numFourOps = 0;
|
||||
}
|
||||
else if(format == MidiSequencer::Format_RSXX)
|
||||
{
|
||||
//opl.CartoonersVolumes = true;
|
||||
m_synth.m_musicMode = OPL3::MODE_RSXX;
|
||||
m_synth.m_volumeScale = OPL3::VOLUME_NATIVE;
|
||||
|
||||
m_synth.m_numChips = 1;
|
||||
m_synth.m_numFourOps = 0;
|
||||
}
|
||||
else if(format == MidiSequencer::Format_IMF)
|
||||
{
|
||||
//std::fprintf(stderr, "Done reading IMF file\n");
|
||||
m_synth.m_numFourOps = 0; //Don't use 4-operator channels for IMF playing!
|
||||
m_synth.m_musicMode = OPL3::MODE_IMF;
|
||||
|
||||
m_synth.m_numChips = 1;
|
||||
m_synth.m_numFourOps = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to identify RSXX format
|
||||
if(HeaderBuf[0] == 0x7D)
|
||||
{
|
||||
fr.seek(0x6D, SEEK_SET);
|
||||
fr.read(HeaderBuf, 6, 1);
|
||||
if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0)
|
||||
{
|
||||
is_RSXX = true;
|
||||
fr.seek(0x7D, SEEK_SET);
|
||||
TrackCount = 1;
|
||||
DeltaTicks = 60;
|
||||
//opl.CartoonersVolumes = true;
|
||||
opl.m_musicMode = OPL3::MODE_RSXX;
|
||||
opl.m_volumeScale = OPL3::VOLUME_NATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing as an IMF file
|
||||
if(!is_RSXX)
|
||||
{
|
||||
do
|
||||
{
|
||||
uint8_t raw[4];
|
||||
size_t end = static_cast<size_t>(HeaderBuf[0]) + 256 * static_cast<size_t>(HeaderBuf[1]);
|
||||
|
||||
if(!end || (end & 3))
|
||||
break;
|
||||
|
||||
size_t backup_pos = fr.tell();
|
||||
int64_t sum1 = 0, sum2 = 0;
|
||||
fr.seek(2, SEEK_SET);
|
||||
|
||||
for(unsigned n = 0; n < 42; ++n)
|
||||
{
|
||||
if(fr.read(raw, 1, 4) != 4)
|
||||
break;
|
||||
int64_t value1 = raw[0];
|
||||
value1 += raw[1] << 8;
|
||||
sum1 += value1;
|
||||
int64_t value2 = raw[2];
|
||||
value2 += raw[3] << 8;
|
||||
sum2 += value2;
|
||||
}
|
||||
|
||||
fr.seek(static_cast<long>(backup_pos), SEEK_SET);
|
||||
|
||||
if(sum1 > sum2)
|
||||
{
|
||||
is_IMF = true;
|
||||
DeltaTicks = 1;
|
||||
}
|
||||
} while(false);
|
||||
}
|
||||
|
||||
if(!is_IMF && !is_RSXX)
|
||||
{
|
||||
if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0)
|
||||
{
|
||||
fr.close();
|
||||
errorStringOut = fr._fileName + ": Invalid format, Header signature is unknown!\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
/*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/
|
||||
TrackCount = (size_t)ReadBEint(HeaderBuf + 10, 2);
|
||||
DeltaTicks = (size_t)ReadBEint(HeaderBuf + 12, 2);
|
||||
}
|
||||
m_synth.m_numChips = m_setup.numChips;
|
||||
if(m_setup.numFourOps < 0)
|
||||
adlCalculateFourOpChannels(this, true);
|
||||
}
|
||||
|
||||
TrackData.clear();
|
||||
TrackData.resize(TrackCount, std::vector<uint8_t>());
|
||||
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
|
||||
if(is_CMF || is_RSXX)
|
||||
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks));
|
||||
else
|
||||
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2);
|
||||
static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00};
|
||||
size_t totalGotten = 0;
|
||||
|
||||
for(size_t tk = 0; tk < TrackCount; ++tk)
|
||||
{
|
||||
// Read track header
|
||||
size_t TrackLength;
|
||||
|
||||
if(is_IMF)
|
||||
{
|
||||
//std::fprintf(stderr, "Reading IMF file...\n");
|
||||
size_t end = static_cast<size_t>(HeaderBuf[0]) + 256 * static_cast<size_t>(HeaderBuf[1]);
|
||||
unsigned IMF_tempo = 1428;
|
||||
static const unsigned char imf_tempo[] = {0x0,//Zero delay!
|
||||
MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4,
|
||||
static_cast<uint8_t>(IMF_tempo >> 24),
|
||||
static_cast<uint8_t>(IMF_tempo >> 16),
|
||||
static_cast<uint8_t>(IMF_tempo >> 8),
|
||||
static_cast<uint8_t>(IMF_tempo)
|
||||
};
|
||||
TrackData[tk].insert(TrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo));
|
||||
TrackData[tk].push_back(0x00);
|
||||
fr.seek(2, SEEK_SET);
|
||||
|
||||
while(fr.tell() < end && !fr.eof())
|
||||
{
|
||||
uint8_t special_event_buf[5];
|
||||
uint8_t raw[4];
|
||||
special_event_buf[0] = MidiEvent::T_SPECIAL;
|
||||
special_event_buf[1] = MidiEvent::ST_RAWOPL;
|
||||
special_event_buf[2] = 0x02;
|
||||
if(fr.read(raw, 1, 4) != 4)
|
||||
break;
|
||||
special_event_buf[3] = raw[0]; // port index
|
||||
special_event_buf[4] = raw[1]; // port value
|
||||
uint32_t delay = static_cast<uint32_t>(raw[2]);
|
||||
delay += 256 * static_cast<uint32_t>(raw[3]);
|
||||
totalGotten += 4;
|
||||
//if(special_event_buf[3] <= 8) continue;
|
||||
//fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay);
|
||||
TrackData[tk].insert(TrackData[tk].end(), special_event_buf, special_event_buf + 5);
|
||||
//if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) );
|
||||
if(delay >> 14)
|
||||
TrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F));
|
||||
if(delay >> 7)
|
||||
TrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F));
|
||||
TrackData[tk].push_back(((delay >> 0) & 0x7F));
|
||||
}
|
||||
|
||||
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
|
||||
//CurrentPosition.track[tk].delay = 0;
|
||||
//CurrentPosition.began = true;
|
||||
//std::fprintf(stderr, "Done reading IMF file\n");
|
||||
opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing!
|
||||
opl.m_musicMode = OPL3::MODE_IMF;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Take the rest of the file
|
||||
if(is_GMF || is_CMF || is_RSXX)
|
||||
{
|
||||
size_t pos = fr.tell();
|
||||
fr.seek(0, SEEK_END);
|
||||
TrackLength = fr.tell() - pos;
|
||||
fr.seek(static_cast<long>(pos), SEEK_SET);
|
||||
}
|
||||
//else if(is_MUS) // Read TrackLength from file position 4
|
||||
//{
|
||||
// size_t pos = fr.tell();
|
||||
// fr.seek(4, SEEK_SET);
|
||||
// TrackLength = static_cast<size_t>(fr.getc());
|
||||
// TrackLength += static_cast<size_t>(fr.getc() << 8);
|
||||
// fr.seek(static_cast<long>(pos), SEEK_SET);
|
||||
//}
|
||||
else
|
||||
{
|
||||
fsize = fr.read(HeaderBuf, 1, 8);
|
||||
if(std::memcmp(HeaderBuf, "MTrk", 4) != 0)
|
||||
{
|
||||
fr.close();
|
||||
errorStringOut = fr._fileName + ": Invalid format, MTrk signature is not found!\n";
|
||||
return false;
|
||||
}
|
||||
TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4);
|
||||
}
|
||||
|
||||
// Read track data
|
||||
TrackData[tk].resize(TrackLength);
|
||||
fsize = fr.read(&TrackData[tk][0], 1, TrackLength);
|
||||
totalGotten += fsize;
|
||||
|
||||
if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag.
|
||||
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
|
||||
if(is_RSXX)//Finalize raw track data with a zero
|
||||
TrackData[tk].push_back(0);
|
||||
|
||||
//bool ok = false;
|
||||
//// Read next event time
|
||||
//uint64_t tkDelay = ReadVarLenEx(tk, ok);
|
||||
//if(ok)
|
||||
// CurrentPosition.track[tk].delay = tkDelay;
|
||||
//else
|
||||
//{
|
||||
// std::stringstream msg;
|
||||
// msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")";
|
||||
// ADLMIDI_ErrorString = msg.str();
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t tk = 0; tk < TrackCount; ++tk)
|
||||
totalGotten += TrackData[tk].size();
|
||||
|
||||
if(totalGotten == 0)
|
||||
{
|
||||
errorStringOut = fr._fileName + ": Empty track data";
|
||||
return false;
|
||||
}
|
||||
|
||||
//Build new MIDI events table
|
||||
if(!buildTrackData())
|
||||
{
|
||||
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip
|
||||
m_setup.tick_skip_samples_delay = 0;
|
||||
m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip
|
||||
//opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously)
|
||||
ch.clear();
|
||||
ch.resize(opl.NumChannels);
|
||||
m_chipChannels.clear();
|
||||
m_chipChannels.resize(m_synth.m_numChannels);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
|
||||
bool MIDIplay::LoadMIDI(const std::string &filename)
|
||||
{
|
||||
FileAndMemReader file;
|
||||
file.openFile(filename.c_str());
|
||||
if(!LoadMIDI_pre())
|
||||
return false;
|
||||
if(!m_sequencer.loadMIDI(file))
|
||||
{
|
||||
errorStringOut = m_sequencer.getErrorString();
|
||||
return false;
|
||||
}
|
||||
if(!LoadMIDI_post())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadMIDI(const void *data, size_t size)
|
||||
{
|
||||
FileAndMemReader file;
|
||||
file.openData(data, size);
|
||||
if(!LoadMIDI_pre())
|
||||
return false;
|
||||
if(!m_sequencer.loadMIDI(file))
|
||||
{
|
||||
errorStringOut = m_sequencer.getErrorString();
|
||||
return false;
|
||||
}
|
||||
if(!LoadMIDI_post())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,6 +22,8 @@
|
|||
*/
|
||||
|
||||
#include "adlmidi_private.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <cassert>
|
||||
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
static const unsigned OPLBase = 0x388;
|
||||
|
@ -42,32 +44,48 @@ static const unsigned OPLBase = 0x388;
|
|||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
/*
|
||||
Dummy data which replaces adldata.cpp banks database
|
||||
*/
|
||||
static const unsigned adl_emulatorSupport = 0
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
# ifndef ADLMIDI_DISABLE_NUKED_EMULATOR
|
||||
| (1u << ADLMIDI_EMU_NUKED) | (1u << ADLMIDI_EMU_NUKED_174)
|
||||
# endif
|
||||
|
||||
const struct adldata adl[] =
|
||||
{
|
||||
{0, 0, (unsigned char)'\0', (unsigned char)'\0', (unsigned char)'\0', 0}
|
||||
};
|
||||
# ifndef ADLMIDI_DISABLE_DOSBOX_EMULATOR
|
||||
| (1u << ADLMIDI_EMU_DOSBOX)
|
||||
# endif
|
||||
#endif
|
||||
;
|
||||
|
||||
const struct adlinsdata adlins[] =
|
||||
//! Check emulator availability
|
||||
bool adl_isEmulatorAvailable(int emulator)
|
||||
{
|
||||
{0, 0, 0, 0, 0, 0, 0.0}
|
||||
};
|
||||
|
||||
int maxAdlBanks()
|
||||
{
|
||||
return 0;
|
||||
return (adl_emulatorSupport & (1u << (unsigned)emulator)) != 0;
|
||||
}
|
||||
|
||||
const unsigned short banks[][256] = {{0}};
|
||||
const char *const banknames[] = {"<Embedded banks are disabled>"};
|
||||
const AdlBankSetup adlbanksetup[] = {{0, 1, 1, 0, 0}};
|
||||
#endif
|
||||
//! Find highest emulator
|
||||
int adl_getHighestEmulator()
|
||||
{
|
||||
int emu = -1;
|
||||
for(unsigned m = adl_emulatorSupport; m > 0; m >>= 1)
|
||||
++emu;
|
||||
return emu;
|
||||
}
|
||||
|
||||
static const unsigned short Operators[23 * 2] =
|
||||
//! Find lowest emulator
|
||||
int adl_getLowestEmulator()
|
||||
{
|
||||
int emu = -1;
|
||||
unsigned m = adl_emulatorSupport;
|
||||
if(m > 0)
|
||||
{
|
||||
for(emu = 0; (m & 1) == 0; m >>= 1)
|
||||
++emu;
|
||||
}
|
||||
return emu;
|
||||
}
|
||||
|
||||
//! Per-channel and per-operator registers map
|
||||
static const uint16_t g_operatorsMap[23 * 2] =
|
||||
{
|
||||
// Channels 0-2
|
||||
0x000, 0x003, 0x001, 0x004, 0x002, 0x005, // operators 0, 3, 1, 4, 2, 5
|
||||
|
@ -91,7 +109,8 @@ static const unsigned short Operators[23 * 2] =
|
|||
0x011, 0xFFF
|
||||
}; // operator 13
|
||||
|
||||
static const unsigned short Channels[23] =
|
||||
//! Channel map to regoster offsets
|
||||
static const uint16_t g_channelsMap[23] =
|
||||
{
|
||||
0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, // 0..8
|
||||
0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, // 9..17 (secondary set)
|
||||
|
@ -129,29 +148,6 @@ static const unsigned short Channels[23] =
|
|||
Ports: ???
|
||||
*/
|
||||
|
||||
void OPL3::setEmbeddedBank(unsigned int bank)
|
||||
{
|
||||
AdlBank = bank;
|
||||
//Embedded banks are supports 128:128 GM set only
|
||||
dynamic_banks.clear();
|
||||
|
||||
if(bank >= static_cast<unsigned int>(maxAdlBanks()))
|
||||
return;
|
||||
|
||||
Bank *bank_pair[2] =
|
||||
{
|
||||
&dynamic_banks[0],
|
||||
&dynamic_banks[PercussionTag]
|
||||
};
|
||||
|
||||
for(unsigned i = 0; i < 256; ++i)
|
||||
{
|
||||
size_t meta = banks[bank][i];
|
||||
adlinsdata2 &ins = bank_pair[i / 128]->ins[i % 128];
|
||||
ins = adlinsdata2(adlins[meta]);
|
||||
}
|
||||
}
|
||||
|
||||
static adlinsdata2 makeEmptyInstrument()
|
||||
{
|
||||
adlinsdata2 ins;
|
||||
|
@ -160,126 +156,239 @@ static adlinsdata2 makeEmptyInstrument()
|
|||
return ins;
|
||||
}
|
||||
|
||||
const adlinsdata2 OPL3::emptyInstrument = makeEmptyInstrument();
|
||||
const adlinsdata2 OPL3::m_emptyInstrument = makeEmptyInstrument();
|
||||
|
||||
OPL3::OPL3() :
|
||||
NumCards(1),
|
||||
NumFourOps(0),
|
||||
HighTremoloMode(false),
|
||||
HighVibratoMode(false),
|
||||
AdlPercussionMode(false),
|
||||
m_numChips(1),
|
||||
m_numFourOps(0),
|
||||
m_deepTremoloMode(false),
|
||||
m_deepVibratoMode(false),
|
||||
m_rhythmMode(false),
|
||||
m_softPanning(false),
|
||||
m_musicMode(MODE_MIDI),
|
||||
m_volumeScale(VOLUME_Generic)
|
||||
{
|
||||
m_insBankSetup.volumeModel = OPL3::VOLUME_Generic;
|
||||
m_insBankSetup.deepTremolo = false;
|
||||
m_insBankSetup.deepVibrato = false;
|
||||
m_insBankSetup.adLibPercussions = false;
|
||||
m_insBankSetup.scaleModulators = false;
|
||||
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
AdlBank = ~0u;
|
||||
m_embeddedBank = CustomBankTag;
|
||||
#else
|
||||
setEmbeddedBank(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OPL3::Poke(size_t card, uint16_t index, uint8_t value)
|
||||
bool OPL3::setupLocked()
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
(void)card;
|
||||
unsigned o = index >> 8;
|
||||
return (m_musicMode == MODE_CMF ||
|
||||
m_musicMode == MODE_IMF ||
|
||||
m_musicMode == MODE_RSXX);
|
||||
}
|
||||
|
||||
void OPL3::setEmbeddedBank(uint32_t bank)
|
||||
{
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
m_embeddedBank = bank;
|
||||
//Embedded banks are supports 128:128 GM set only
|
||||
m_insBanks.clear();
|
||||
|
||||
if(bank >= static_cast<unsigned int>(maxAdlBanks()))
|
||||
return;
|
||||
|
||||
Bank *bank_pair[2] =
|
||||
{
|
||||
&m_insBanks[0],
|
||||
&m_insBanks[PercussionTag]
|
||||
};
|
||||
|
||||
for(unsigned i = 0; i < 256; ++i)
|
||||
{
|
||||
size_t meta = banks[bank][i];
|
||||
adlinsdata2 &ins = bank_pair[i / 128]->ins[i % 128];
|
||||
ins = adlinsdata2::from_adldata(::adlins[meta]);
|
||||
}
|
||||
#else
|
||||
ADL_UNUSED(bank);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OPL3::writeReg(size_t chip, uint16_t address, uint8_t value)
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
ADL_UNUSED(chip);
|
||||
unsigned o = address >> 8;
|
||||
unsigned port = OPLBase + o * 2;
|
||||
|
||||
#ifdef __DJGPP__
|
||||
outportb(port, index);
|
||||
outportb(port, address);
|
||||
for(unsigned c = 0; c < 6; ++c) inportb(port);
|
||||
outportb(port + 1, value);
|
||||
for(unsigned c = 0; c < 35; ++c) inportb(port);
|
||||
#endif
|
||||
|
||||
#ifdef __WATCOMC__
|
||||
outp(port, index);
|
||||
outp(port, address);
|
||||
for(uint16_t c = 0; c < 6; ++c) inp(port);
|
||||
outp(port + 1, value);
|
||||
for(uint16_t c = 0; c < 35; ++c) inp(port);
|
||||
#endif//__WATCOMC__
|
||||
|
||||
#else
|
||||
cardsOP2[card]->writeReg(index, value);
|
||||
#endif
|
||||
#else//ADLMIDI_HW_OPL
|
||||
m_chips[chip]->writeReg(address, value);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OPL3::writeRegI(size_t chip, uint32_t address, uint32_t value)
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
writeReg(chip, static_cast<uint16_t>(address), static_cast<uint8_t>(value));
|
||||
#else//ADLMIDI_HW_OPL
|
||||
m_chips[chip]->writeReg(static_cast<uint16_t>(address), static_cast<uint8_t>(value));
|
||||
#endif
|
||||
}
|
||||
|
||||
void OPL3::writePan(size_t chip, uint32_t address, uint32_t value)
|
||||
{
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
m_chips[chip]->writePan(static_cast<uint16_t>(address), static_cast<uint8_t>(value));
|
||||
#else
|
||||
ADL_UNUSED(chip);
|
||||
ADL_UNUSED(address);
|
||||
ADL_UNUSED(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void OPL3::NoteOff(size_t c)
|
||||
void OPL3::noteOff(size_t c)
|
||||
{
|
||||
size_t card = c / 23, cc = c % 23;
|
||||
size_t chip = c / 23, cc = c % 23;
|
||||
|
||||
if(cc >= 18)
|
||||
{
|
||||
regBD[card] &= ~(0x10 >> (cc - 18));
|
||||
Poke(card, 0xBD, regBD[card]);
|
||||
m_regBD[chip] &= ~(0x10 >> (cc - 18));
|
||||
writeRegI(chip, 0xBD, m_regBD[chip]);
|
||||
return;
|
||||
}
|
||||
|
||||
Poke(card, 0xB0 + Channels[cc], pit[c] & 0xDF);
|
||||
writeRegI(chip, 0xB0 + g_channelsMap[cc], m_keyBlockFNumCache[c] & 0xDF);
|
||||
}
|
||||
|
||||
void OPL3::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071
|
||||
void OPL3::noteOn(size_t c1, size_t c2, double hertz) // Hertz range: 0..131071
|
||||
{
|
||||
unsigned card = c / 23, cc = c % 23;
|
||||
unsigned x = 0x2000;
|
||||
size_t chip = c1 / 23, cc1 = c1 % 23, cc2 = c2 % 23;
|
||||
uint32_t octave = 0, ftone = 0, mul_offset = 0;
|
||||
|
||||
if(hertz < 0 || hertz > 131071) // Avoid infinite loop
|
||||
if(hertz < 0)
|
||||
return;
|
||||
|
||||
while(hertz >= 1023.5)
|
||||
//Basic range until max of octaves reaching
|
||||
while((hertz >= 1023.5) && (octave < 0x1C00))
|
||||
{
|
||||
hertz /= 2.0; // Calculate octave
|
||||
x += 0x400;
|
||||
octave += 0x400;
|
||||
}
|
||||
//Extended range, rely on frequency multiplication increment
|
||||
while(hertz >= 1022.75)
|
||||
{
|
||||
hertz /= 2.0; // Calculate octave
|
||||
mul_offset++;
|
||||
}
|
||||
|
||||
x += static_cast<unsigned int>(hertz + 0.5);
|
||||
unsigned chn = Channels[cc];
|
||||
ftone = octave + static_cast<uint32_t>(hertz + 0.5);
|
||||
uint32_t chn = g_channelsMap[cc1];
|
||||
const adldata &patch1 = m_insCache[c1];
|
||||
const adldata &patch2 = m_insCache[c2 < m_insCache.size() ? c2 : 0];
|
||||
|
||||
if(cc >= 18)
|
||||
if(cc1 < 18)
|
||||
{
|
||||
regBD[card] |= (0x10 >> (cc - 18));
|
||||
Poke(card, 0x0BD, regBD[card]);
|
||||
x &= ~0x2000u;
|
||||
//x |= 0x800; // for test
|
||||
ftone += 0x2000u; /* Key-ON [KON] */
|
||||
|
||||
const bool natural_4op = (m_channelCategory[c1] == ChanCat_4op_Master);
|
||||
const size_t opsCount = natural_4op ? 4 : 2;
|
||||
const uint16_t op_addr[4] =
|
||||
{
|
||||
g_operatorsMap[cc1 * 2 + 0], g_operatorsMap[cc1 * 2 + 1],
|
||||
g_operatorsMap[cc2 * 2 + 0], g_operatorsMap[cc2 * 2 + 1]
|
||||
};
|
||||
const uint32_t ops[4] =
|
||||
{
|
||||
patch1.modulator_E862 & 0xFF,
|
||||
patch1.carrier_E862 & 0xFF,
|
||||
patch2.modulator_E862 & 0xFF,
|
||||
patch2.carrier_E862 & 0xFF
|
||||
};
|
||||
|
||||
for(size_t op = 0; op < opsCount; op++)
|
||||
{
|
||||
if((op > 0) && (op_addr[op] == 0xFFF))
|
||||
break;
|
||||
if(mul_offset > 0)
|
||||
{
|
||||
uint32_t dt = ops[op] & 0xF0;
|
||||
uint32_t mul = ops[op] & 0x0F;
|
||||
if((mul + mul_offset) > 0x0F)
|
||||
{
|
||||
mul_offset = 0;
|
||||
mul = 0x0F;
|
||||
}
|
||||
writeRegI(chip, 0x20 + op_addr[op], (dt | (mul + mul_offset)) & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeRegI(chip, 0x20 + op_addr[op], ops[op] & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(chn != 0xFFF)
|
||||
{
|
||||
Poke(card, 0xA0 + chn, x & 0xFF);
|
||||
Poke(card, 0xB0 + chn, pit[c] = static_cast<uint8_t>(x >> 8));
|
||||
writeRegI(chip , 0xA0 + chn, (ftone & 0xFF));
|
||||
writeRegI(chip , 0xB0 + chn, (ftone >> 8));
|
||||
m_keyBlockFNumCache[c1] = (ftone >> 8);
|
||||
}
|
||||
|
||||
if(cc1 >= 18)
|
||||
{
|
||||
m_regBD[chip ] |= (0x10 >> (cc1 - 18));
|
||||
writeRegI(chip , 0x0BD, m_regBD[chip ]);
|
||||
//x |= 0x800; // for test
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
||||
void OPL3::touchNote(size_t c, uint8_t volume, uint8_t brightness)
|
||||
{
|
||||
if(volume > 63)
|
||||
volume = 63;
|
||||
|
||||
size_t card = c / 23, cc = c % 23;
|
||||
const adldata &adli = ins[c];
|
||||
uint16_t o1 = Operators[cc * 2 + 0];
|
||||
uint16_t o2 = Operators[cc * 2 + 1];
|
||||
size_t chip = c / 23, cc = c % 23;
|
||||
const adldata &adli = m_insCache[c];
|
||||
uint16_t o1 = g_operatorsMap[cc * 2 + 0];
|
||||
uint16_t o2 = g_operatorsMap[cc * 2 + 1];
|
||||
uint8_t x = adli.modulator_40, y = adli.carrier_40;
|
||||
uint16_t mode = 1; // 2-op AM
|
||||
uint32_t mode = 1; // 2-op AM
|
||||
|
||||
if(four_op_category[c] == 0 || four_op_category[c] == 3)
|
||||
if(m_channelCategory[c] == ChanCat_Regular ||
|
||||
m_channelCategory[c] == ChanCat_Rhythm_Bass)
|
||||
{
|
||||
mode = adli.feedconn & 1; // 2-op FM or 2-op AM
|
||||
}
|
||||
else if(four_op_category[c] == 1 || four_op_category[c] == 2)
|
||||
else if(m_channelCategory[c] == ChanCat_4op_Master ||
|
||||
m_channelCategory[c] == ChanCat_4op_Slave)
|
||||
{
|
||||
const adldata *i0, *i1;
|
||||
|
||||
if(four_op_category[c] == 1)
|
||||
if(m_channelCategory[c] == ChanCat_4op_Master)
|
||||
{
|
||||
i0 = &adli;
|
||||
i1 = &ins[c + 3];
|
||||
i1 = &m_insCache[c + 3];
|
||||
mode = 2; // 4-op xx-xx ops 1&2
|
||||
}
|
||||
else
|
||||
{
|
||||
i0 = &ins[c - 3];
|
||||
i0 = &m_insCache[c - 3];
|
||||
i1 = &adli;
|
||||
mode = 6; // 4-op xx-xx ops 3&4
|
||||
}
|
||||
|
@ -303,14 +412,14 @@ void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
|||
|
||||
if(m_musicMode == MODE_RSXX)
|
||||
{
|
||||
Poke(card, 0x40 + o1, x);
|
||||
writeRegI(chip, 0x40 + o1, x);
|
||||
if(o2 != 0xFFF)
|
||||
Poke(card, 0x40 + o2, y - volume / 2);
|
||||
writeRegI(chip, 0x40 + o2, y - volume / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool do_modulator = do_ops[ mode ][ 0 ] || ScaleModulators;
|
||||
bool do_carrier = do_ops[ mode ][ 1 ] || ScaleModulators;
|
||||
bool do_modulator = do_ops[ mode ][ 0 ] || m_scaleModulators;
|
||||
bool do_carrier = do_ops[ mode ][ 1 ] || m_scaleModulators;
|
||||
|
||||
uint32_t modulator = do_modulator ? (x | 63) - volume + volume * (x & 63) / 63 : x;
|
||||
uint32_t carrier = do_carrier ? (y | 63) - volume + volume * (y & 63) / 63 : y;
|
||||
|
@ -324,9 +433,9 @@ void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
|||
carrier = (carrier | 63) - brightness + brightness * (carrier & 63) / 63;
|
||||
}
|
||||
|
||||
Poke(card, 0x40 + o1, modulator);
|
||||
writeRegI(chip, 0x40 + o1, modulator);
|
||||
if(o2 != 0xFFF)
|
||||
Poke(card, 0x40 + o2, carrier);
|
||||
writeRegI(chip, 0x40 + o2, carrier);
|
||||
}
|
||||
|
||||
// Correct formula (ST3, AdPlug):
|
||||
|
@ -351,70 +460,99 @@ void OPL3::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127
|
|||
}
|
||||
}*/
|
||||
|
||||
void OPL3::Patch(uint16_t c, const adldata &adli)
|
||||
void OPL3::setPatch(size_t c, const adldata &instrument)
|
||||
{
|
||||
uint16_t card = c / 23, cc = c % 23;
|
||||
size_t chip = c / 23, cc = c % 23;
|
||||
static const uint8_t data[4] = {0x20, 0x60, 0x80, 0xE0};
|
||||
ins[c] = adli;
|
||||
uint16_t o1 = Operators[cc * 2 + 0];
|
||||
uint16_t o2 = Operators[cc * 2 + 1];
|
||||
unsigned x = adli.modulator_E862, y = adli.carrier_E862;
|
||||
m_insCache[c] = instrument;
|
||||
uint16_t o1 = g_operatorsMap[cc * 2 + 0];
|
||||
uint16_t o2 = g_operatorsMap[cc * 2 + 1];
|
||||
unsigned x = instrument.modulator_E862, y = instrument.carrier_E862;
|
||||
|
||||
for(unsigned a = 0; a < 4; ++a, x >>= 8, y >>= 8)
|
||||
for(size_t a = 0; a < 4; ++a, x >>= 8, y >>= 8)
|
||||
{
|
||||
Poke(card, data[a] + o1, x & 0xFF);
|
||||
writeRegI(chip, data[a] + o1, x & 0xFF);
|
||||
if(o2 != 0xFFF)
|
||||
Poke(card, data[a] + o2, y & 0xFF);
|
||||
writeRegI(chip, data[a] + o2, y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::Pan(unsigned c, unsigned value)
|
||||
void OPL3::setPan(size_t c, uint8_t value)
|
||||
{
|
||||
unsigned card = c / 23, cc = c % 23;
|
||||
|
||||
if(Channels[cc] != 0xFFF)
|
||||
Poke(card, 0xC0 + Channels[cc], ins[c].feedconn | value);
|
||||
}
|
||||
|
||||
void OPL3::Silence() // Silence all OPL channels.
|
||||
{
|
||||
for(unsigned c = 0; c < NumChannels; ++c)
|
||||
size_t chip = c / 23, cc = c % 23;
|
||||
if(g_channelsMap[cc] != 0xFFF)
|
||||
{
|
||||
NoteOff(c);
|
||||
Touch_Real(c, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::updateFlags()
|
||||
{
|
||||
unsigned fours = NumFourOps;
|
||||
|
||||
for(unsigned card = 0; card < NumCards; ++card)
|
||||
{
|
||||
Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80
|
||||
+ HighVibratoMode * 0x40
|
||||
+ AdlPercussionMode * 0x20));
|
||||
unsigned fours_this_card = std::min(fours, 6u);
|
||||
Poke(card, 0x104, (1 << fours_this_card) - 1);
|
||||
fours -= fours_this_card;
|
||||
}
|
||||
|
||||
// Mark all channels that are reserved for four-operator function
|
||||
if(AdlPercussionMode == 1)
|
||||
for(unsigned a = 0; a < NumCards; ++a)
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
if (m_softPanning)
|
||||
{
|
||||
for(unsigned b = 0; b < 5; ++b)
|
||||
four_op_category[a * 23 + 18 + b] = static_cast<char>(b + 3);
|
||||
for(unsigned b = 0; b < 3; ++b)
|
||||
four_op_category[a * 23 + 6 + b] = 8;
|
||||
writePan(chip, g_channelsMap[cc], value);
|
||||
writeRegI(chip, 0xC0 + g_channelsMap[cc], m_insCache[c].feedconn | OPL_PANNING_BOTH);
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
int panning = 0;
|
||||
if(value < 64 + 32) panning |= OPL_PANNING_LEFT;
|
||||
if(value >= 64 - 32) panning |= OPL_PANNING_RIGHT;
|
||||
writePan(chip, g_channelsMap[cc], 64);
|
||||
writeRegI(chip, 0xC0 + g_channelsMap[cc], m_insCache[c].feedconn | panning);
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
unsigned nextfour = 0;
|
||||
|
||||
for(unsigned a = 0; a < NumFourOps; ++a)
|
||||
void OPL3::silenceAll() // Silence all OPL channels.
|
||||
{
|
||||
for(size_t c = 0; c < m_numChannels; ++c)
|
||||
{
|
||||
four_op_category[nextfour ] = 1;
|
||||
four_op_category[nextfour + 3] = 2;
|
||||
noteOff(c);
|
||||
touchNote(c, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::updateChannelCategories()
|
||||
{
|
||||
const uint32_t fours = m_numFourOps;
|
||||
|
||||
for(uint32_t chip = 0, fours_left = fours; chip < m_numChips; ++chip)
|
||||
{
|
||||
m_regBD[chip] = (m_deepTremoloMode * 0x80 + m_deepVibratoMode * 0x40 + m_rhythmMode * 0x20);
|
||||
writeRegI(chip, 0x0BD, m_regBD[chip]);
|
||||
uint32_t fours_this_chip = std::min(fours_left, static_cast<uint32_t>(6u));
|
||||
writeRegI(chip, 0x104, (1 << fours_this_chip) - 1);
|
||||
fours_left -= fours_this_chip;
|
||||
}
|
||||
|
||||
if(!m_rhythmMode)
|
||||
{
|
||||
for(size_t a = 0, n = m_numChips; a < n; ++a)
|
||||
{
|
||||
for(size_t b = 0; b < 23; ++b)
|
||||
{
|
||||
m_channelCategory[a * 23 + b] =
|
||||
(b >= 18) ? ChanCat_Rhythm_Slave : ChanCat_Regular;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t a = 0, n = m_numChips; a < n; ++a)
|
||||
{
|
||||
for(size_t b = 0; b < 23; ++b)
|
||||
{
|
||||
m_channelCategory[a * 23 + b] =
|
||||
(b >= 18) ? static_cast<ChanCat>(ChanCat_Rhythm_Bass + (b - 18)) :
|
||||
(b >= 6 && b < 9) ? ChanCat_Rhythm_Slave : ChanCat_Regular;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nextfour = 0;
|
||||
for(uint32_t a = 0; a < fours; ++a)
|
||||
{
|
||||
m_channelCategory[nextfour] = ChanCat_4op_Master;
|
||||
m_channelCategory[nextfour + 3] = ChanCat_4op_Slave;
|
||||
|
||||
switch(a % 6)
|
||||
{
|
||||
|
@ -434,19 +572,45 @@ void OPL3::updateFlags()
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**/
|
||||
/*
|
||||
In two-op mode, channels 0..8 go as follows:
|
||||
Op1[port] Op2[port]
|
||||
Channel 0: 00 00 03 03
|
||||
Channel 1: 01 01 04 04
|
||||
Channel 2: 02 02 05 05
|
||||
Channel 3: 06 08 09 0B
|
||||
Channel 4: 07 09 10 0C
|
||||
Channel 5: 08 0A 11 0D
|
||||
Channel 6: 12 10 15 13
|
||||
Channel 7: 13 11 16 14
|
||||
Channel 8: 14 12 17 15
|
||||
In four-op mode, channels 0..8 go as follows:
|
||||
Op1[port] Op2[port] Op3[port] Op4[port]
|
||||
Channel 0: 00 00 03 03 06 08 09 0B
|
||||
Channel 1: 01 01 04 04 07 09 10 0C
|
||||
Channel 2: 02 02 05 05 08 0A 11 0D
|
||||
Channel 3: CHANNEL 0 SLAVE
|
||||
Channel 4: CHANNEL 1 SLAVE
|
||||
Channel 5: CHANNEL 2 SLAVE
|
||||
Channel 6: 12 10 15 13
|
||||
Channel 7: 13 11 16 14
|
||||
Channel 8: 14 12 17 15
|
||||
Same goes principally for channels 9-17 respectively.
|
||||
*/
|
||||
}
|
||||
|
||||
void OPL3::updateDeepFlags()
|
||||
void OPL3::commitDeepFlags()
|
||||
{
|
||||
for(unsigned card = 0; card < NumCards; ++card)
|
||||
for(size_t chip = 0; chip < m_numChips; ++chip)
|
||||
{
|
||||
Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80
|
||||
+ HighVibratoMode * 0x40
|
||||
+ AdlPercussionMode * 0x20));
|
||||
m_regBD[chip] = (m_deepTremoloMode * 0x80 + m_deepVibratoMode * 0x40 + m_rhythmMode * 0x20);
|
||||
writeRegI(chip, 0x0BD, m_regBD[chip]);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel)
|
||||
void OPL3::setVolumeScaleModel(ADLMIDI_VolumeModels volumeModel)
|
||||
{
|
||||
switch(volumeModel)
|
||||
{
|
||||
|
@ -475,19 +639,37 @@ void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel)
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
void OPL3::ClearChips()
|
||||
ADLMIDI_VolumeModels OPL3::getVolumeScaleModel()
|
||||
{
|
||||
for(size_t i = 0; i < cardsOP2.size(); i++)
|
||||
cardsOP2[i].reset(NULL);
|
||||
cardsOP2.clear();
|
||||
switch(m_volumeScale)
|
||||
{
|
||||
default:
|
||||
case OPL3::VOLUME_Generic:
|
||||
return ADLMIDI_VolumeModel_Generic;
|
||||
case OPL3::VOLUME_NATIVE:
|
||||
return ADLMIDI_VolumeModel_NativeOPL3;
|
||||
case OPL3::VOLUME_DMX:
|
||||
return ADLMIDI_VolumeModel_DMX;
|
||||
case OPL3::VOLUME_APOGEE:
|
||||
return ADLMIDI_VolumeModel_APOGEE;
|
||||
case OPL3::VOLUME_9X:
|
||||
return ADLMIDI_VolumeModel_9X;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
void OPL3::clearChips()
|
||||
{
|
||||
for(size_t i = 0; i < m_chips.size(); i++)
|
||||
m_chips[i].reset(NULL);
|
||||
m_chips.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
|
||||
void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
|
||||
{
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
ClearChips();
|
||||
clearChips();
|
||||
#else
|
||||
(void)emulator;
|
||||
(void)PCM_RATE;
|
||||
|
@ -495,24 +677,27 @@ void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
|
|||
#if !defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
(void)audioTickHandler;
|
||||
#endif
|
||||
ins.clear();
|
||||
pit.clear();
|
||||
regBD.clear();
|
||||
m_insCache.clear();
|
||||
m_keyBlockFNumCache.clear();
|
||||
m_regBD.clear();
|
||||
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
cardsOP2.resize(NumCards, AdlMIDI_SPtr<OPLChipBase>());
|
||||
m_chips.resize(m_numChips, AdlMIDI_SPtr<OPLChipBase>());
|
||||
#endif
|
||||
|
||||
NumChannels = NumCards * 23;
|
||||
ins.resize(NumChannels, adl[adlDefaultNumber]);
|
||||
pit.resize(NumChannels, 0);
|
||||
regBD.resize(NumCards, 0);
|
||||
four_op_category.resize(NumChannels, 0);
|
||||
const struct adldata defaultInsCache = { 0x1557403,0x005B381, 0x49,0x80, 0x4, +0 };
|
||||
m_numChannels = m_numChips * 23;
|
||||
m_insCache.resize(m_numChannels, defaultInsCache);
|
||||
m_keyBlockFNumCache.resize(m_numChannels, 0);
|
||||
m_regBD.resize(m_numChips, 0);
|
||||
m_channelCategory.resize(m_numChannels, 0);
|
||||
|
||||
for(unsigned p = 0, a = 0; a < NumCards; ++a)
|
||||
for(size_t p = 0, a = 0; a < m_numChips; ++a)
|
||||
{
|
||||
for(unsigned b = 0; b < 18; ++b) four_op_category[p++] = 0;
|
||||
for(unsigned b = 0; b < 5; ++b) four_op_category[p++] = 8;
|
||||
for(size_t b = 0; b < 18; ++b)
|
||||
m_channelCategory[p++] = 0;
|
||||
for(size_t b = 0; b < 5; ++b)
|
||||
m_channelCategory[p++] = ChanCat_Rhythm_Slave;
|
||||
}
|
||||
|
||||
static const uint16_t data[] =
|
||||
|
@ -521,15 +706,17 @@ void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
|
|||
0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable
|
||||
0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions
|
||||
};
|
||||
unsigned fours = NumFourOps;
|
||||
// size_t fours = m_numFourOps;
|
||||
|
||||
for(size_t i = 0; i < NumCards; ++i)
|
||||
for(size_t i = 0; i < m_numChips; ++i)
|
||||
{
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
OPLChipBase *chip;
|
||||
switch(emulator)
|
||||
{
|
||||
default:
|
||||
assert(false);
|
||||
abort();
|
||||
#ifndef ADLMIDI_DISABLE_NUKED_EMULATOR
|
||||
case ADLMIDI_EMU_NUKED: /* Latest Nuked OPL3 */
|
||||
chip = new NukedOPL3;
|
||||
|
@ -544,91 +731,23 @@ void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
|
|||
break;
|
||||
#endif
|
||||
}
|
||||
cardsOP2[i].reset(chip);
|
||||
m_chips[i].reset(chip);
|
||||
chip->setChipId((uint32_t)i);
|
||||
chip->setRate((uint32_t)PCM_RATE);
|
||||
if(runAtPcmRate)
|
||||
if(m_runAtPcmRate)
|
||||
chip->setRunningAtPcmRate(true);
|
||||
# if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
chip->setAudioTickHandlerInstance(audioTickHandler);
|
||||
# endif
|
||||
#endif // ADLMIDI_HW_OPL
|
||||
|
||||
for(unsigned a = 0; a < 18; ++a) Poke(i, 0xB0 + Channels[a], 0x00);
|
||||
for(unsigned a = 0; a < sizeof(data) / sizeof(*data); a += 2)
|
||||
Poke(i, data[a], static_cast<uint8_t>(data[a + 1]));
|
||||
Poke(i, 0x0BD, regBD[i] = (HighTremoloMode * 0x80
|
||||
+ HighVibratoMode * 0x40
|
||||
+ AdlPercussionMode * 0x20));
|
||||
unsigned fours_this_card = std::min(fours, 6u);
|
||||
Poke(i, 0x104, (1 << fours_this_card) - 1);
|
||||
//fprintf(stderr, "Card %u: %u four-ops.\n", card, fours_this_card);
|
||||
fours -= fours_this_card;
|
||||
/* Clean-up channels from any playing junk sounds */
|
||||
for(size_t a = 0; a < 18; ++a)
|
||||
writeRegI(i, 0xB0 + g_channelsMap[a], 0x00);
|
||||
for(size_t a = 0; a < sizeof(data) / sizeof(*data); a += 2)
|
||||
writeRegI(i, data[a], (data[a + 1]));
|
||||
}
|
||||
|
||||
// Mark all channels that are reserved for four-operator function
|
||||
if(AdlPercussionMode == 1)
|
||||
{
|
||||
for(unsigned a = 0; a < NumCards; ++a)
|
||||
{
|
||||
for(unsigned b = 0; b < 5; ++b) four_op_category[a * 23 + 18 + b] = static_cast<char>(b + 3);
|
||||
for(unsigned b = 0; b < 3; ++b) four_op_category[a * 23 + 6 + b] = 8;
|
||||
}
|
||||
}
|
||||
unsigned nextfour = 0;
|
||||
|
||||
for(unsigned a = 0; a < NumFourOps; ++a)
|
||||
{
|
||||
four_op_category[nextfour ] = 1;
|
||||
four_op_category[nextfour + 3] = 2;
|
||||
|
||||
switch(a % 6)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
nextfour += 1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
nextfour += 9 - 2;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
nextfour += 1;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
nextfour += 23 - 9 - 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**/
|
||||
/*
|
||||
In two-op mode, channels 0..8 go as follows:
|
||||
Op1[port] Op2[port]
|
||||
Channel 0: 00 00 03 03
|
||||
Channel 1: 01 01 04 04
|
||||
Channel 2: 02 02 05 05
|
||||
Channel 3: 06 08 09 0B
|
||||
Channel 4: 07 09 10 0C
|
||||
Channel 5: 08 0A 11 0D
|
||||
Channel 6: 12 10 15 13
|
||||
Channel 7: 13 11 16 14
|
||||
Channel 8: 14 12 17 15
|
||||
In four-op mode, channels 0..8 go as follows:
|
||||
Op1[port] Op2[port] Op3[port] Op4[port]
|
||||
Channel 0: 00 00 03 03 06 08 09 0B
|
||||
Channel 1: 01 01 04 04 07 09 10 0C
|
||||
Channel 2: 02 02 05 05 08 0A 11 0D
|
||||
Channel 3: CHANNEL 0 SLAVE
|
||||
Channel 4: CHANNEL 1 SLAVE
|
||||
Channel 5: CHANNEL 2 SLAVE
|
||||
Channel 6: 12 10 15 13
|
||||
Channel 7: 13 11 16 14
|
||||
Channel 8: 14 12 17 15
|
||||
Same goes principally for channels 9-17 respectively.
|
||||
*/
|
||||
Silence();
|
||||
updateChannelCategories();
|
||||
silenceAll();
|
||||
}
|
||||
|
|
|
@ -27,53 +27,58 @@ std::string ADLMIDI_ErrorString;
|
|||
|
||||
// Generator callback on audio rate ticks
|
||||
|
||||
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate)
|
||||
{
|
||||
reinterpret_cast<MIDIplay *>(instance)->AudioTick(chipId, rate);
|
||||
}
|
||||
#endif
|
||||
|
||||
int adlRefreshNumCards(ADL_MIDIPlayer *device)
|
||||
int adlCalculateFourOpChannels(MIDIplay *play, bool silent)
|
||||
{
|
||||
unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
size_t n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
|
||||
|
||||
//Automatically calculate how much 4-operator channels is necessary
|
||||
if(play->opl.AdlBank == ~0u)
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
if(play->m_synth.m_embeddedBank == OPL3::CustomBankTag)
|
||||
#endif
|
||||
{
|
||||
//For custom bank
|
||||
OPL3::BankMap::iterator it = play->opl.dynamic_banks.begin();
|
||||
OPL3::BankMap::iterator end = play->opl.dynamic_banks.end();
|
||||
OPL3::BankMap::iterator it = play->m_synth.m_insBanks.begin();
|
||||
OPL3::BankMap::iterator end = play->m_synth.m_insBanks.end();
|
||||
for(; it != end; ++it)
|
||||
{
|
||||
uint16_t bank = it->first;
|
||||
unsigned div = (bank & OPL3::PercussionTag) ? 1 : 0;
|
||||
for(unsigned i = 0; i < 128; ++i)
|
||||
size_t bank = it->first;
|
||||
size_t div = (bank & OPL3::PercussionTag) ? 1 : 0;
|
||||
for(size_t i = 0; i < 128; ++i)
|
||||
{
|
||||
adlinsdata2 &ins = it->second.ins[i];
|
||||
if(ins.flags & adlinsdata::Flag_NoSound)
|
||||
continue;
|
||||
if((ins.adl[0] != ins.adl[1]) && ((ins.flags & adlinsdata::Flag_Pseudo4op) == 0))
|
||||
if((ins.flags & adlinsdata::Flag_Real4op) != 0)
|
||||
++n_fourop[div];
|
||||
++n_total[div];
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
else
|
||||
{
|
||||
//For embedded bank
|
||||
for(unsigned a = 0; a < 256; ++a)
|
||||
for(size_t a = 0; a < 256; ++a)
|
||||
{
|
||||
unsigned insno = banks[play->m_setup.AdlBank][a];
|
||||
size_t insno = banks[play->m_setup.bankId][a];
|
||||
if(insno == 198)
|
||||
continue;
|
||||
++n_total[a / 128];
|
||||
adlinsdata2 ins(adlins[insno]);
|
||||
if(ins.flags & adlinsdata::Flag_Real4op)
|
||||
adlinsdata2 ins = adlinsdata2::from_adldata(::adlins[insno]);
|
||||
if((ins.flags & adlinsdata::Flag_Real4op) != 0)
|
||||
++n_fourop[a / 128];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned numFourOps = 0;
|
||||
size_t numFourOps = 0;
|
||||
|
||||
// All 2ops (no 4ops)
|
||||
if((n_fourop[0] == 0) && (n_fourop[1] == 0))
|
||||
|
@ -94,7 +99,10 @@ int adlRefreshNumCards(ADL_MIDIPlayer *device)
|
|||
: (play->m_setup.NumCards == 1 ? 1 : play->m_setup.NumCards * 4);
|
||||
*/
|
||||
|
||||
play->opl.NumFourOps = play->m_setup.NumFourOps = (numFourOps * play->m_setup.NumCards);
|
||||
play->m_synth.m_numFourOps = static_cast<unsigned>(numFourOps * play->m_synth.m_numChips);
|
||||
// Update channel categories and set up four-operator channels
|
||||
if(!silent)
|
||||
play->m_synth.updateChannelCategories();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -37,6 +37,8 @@
|
|||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "dbopl.h"
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ > 3
|
||||
|
@ -70,6 +72,37 @@
|
|||
#define PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
struct NoCopy {
|
||||
NoCopy() {}
|
||||
private:
|
||||
NoCopy(const NoCopy &);
|
||||
NoCopy &operator=(const NoCopy &);
|
||||
};
|
||||
#if !defined(_WIN32)
|
||||
#include <pthread.h>
|
||||
struct Mutex : NoCopy {
|
||||
Mutex() { pthread_mutex_init(&m, NULL);}
|
||||
~Mutex() { pthread_mutex_destroy(&m); }
|
||||
void lock() { pthread_mutex_lock(&m); }
|
||||
void unlock() { pthread_mutex_unlock(&m); }
|
||||
pthread_mutex_t m;
|
||||
};
|
||||
#else
|
||||
#include <windows.h>
|
||||
struct Mutex : NoCopy {
|
||||
Mutex() { InitializeCriticalSection(&m); }
|
||||
~Mutex() { DeleteCriticalSection(&m); }
|
||||
void lock() { EnterCriticalSection(&m); }
|
||||
void unlock() { LeaveCriticalSection(&m); }
|
||||
CRITICAL_SECTION m;
|
||||
};
|
||||
#endif
|
||||
struct MutexHolder : NoCopy {
|
||||
explicit MutexHolder(Mutex &m) : m(m) { m.lock(); }
|
||||
~MutexHolder() { m.unlock(); }
|
||||
Mutex &m;
|
||||
};
|
||||
|
||||
namespace DBOPL {
|
||||
|
||||
#define OPLRATE ((double)(14318180.0 / 288.0))
|
||||
|
@ -219,6 +252,29 @@ static const Bit8u KslShiftTable[4] = {
|
|||
31,1,2,0
|
||||
};
|
||||
|
||||
// Pan law table
|
||||
static const Bit16u PanLawTable[] =
|
||||
{
|
||||
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
|
||||
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
|
||||
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
|
||||
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
|
||||
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
|
||||
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
|
||||
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
|
||||
50433, 49912, 49383, 48846, 48302, 47750, 47191,
|
||||
46340, /* Center left */
|
||||
46340, /* Center right */
|
||||
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
|
||||
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
|
||||
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
|
||||
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
|
||||
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
|
||||
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
|
||||
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
|
||||
4858, 4050, 3240, 2431, 1620, 810, 0
|
||||
};
|
||||
|
||||
//Generate a table index and table shift value using input value from a selected rate
|
||||
static void EnvelopeSelect( Bit8u val, Bit8u& index, Bit8u& shift ) {
|
||||
if ( val < 13 * 4 ) { //Rate 0 - 12
|
||||
|
@ -442,6 +498,7 @@ Bits Operator::TemplateVolume( ) {
|
|||
return vol;
|
||||
}
|
||||
//In sustain phase, but not sustaining, do regular release
|
||||
/* fall through */
|
||||
case RELEASE:
|
||||
vol += RateForward( releaseAdd );;
|
||||
if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
|
||||
|
@ -757,6 +814,11 @@ void Channel::WriteC0(const Chip* chip, Bit8u val) {
|
|||
UpdateSynth(chip);
|
||||
}
|
||||
|
||||
void Channel::WritePan(Bit8u val) {
|
||||
panLeft = PanLawTable[val & 0x7F];
|
||||
panRight = PanLawTable[0x7F - (val & 0x7F)];
|
||||
}
|
||||
|
||||
void Channel::UpdateSynth( const Chip* chip ) {
|
||||
//Select the new synth mode
|
||||
if ( chip->opl3Active ) {
|
||||
|
@ -971,8 +1033,8 @@ Channel* Channel::BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ) {
|
|||
case sm3AMFM:
|
||||
case sm3FMAM:
|
||||
case sm3AMAM:
|
||||
output[ i * 2 + 0 ] += sample & maskLeft;
|
||||
output[ i * 2 + 1 ] += sample & maskRight;
|
||||
output[ i * 2 + 0 ] += (sample * panLeft / 65535) & maskLeft;
|
||||
output[ i * 2 + 1 ] += (sample * panRight / 65535) & maskRight;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1265,21 +1327,56 @@ void Chip::GenerateBlock3_Mix( Bitu total, Bit32s* output ) {
|
|||
}
|
||||
}
|
||||
|
||||
void Chip::Setup( Bit32u rate ) {
|
||||
struct CacheEntry {
|
||||
Bit32u rate;
|
||||
Bit32u freqMul[16];
|
||||
Bit32u linearRates[76];
|
||||
Bit32u attackRates[76];
|
||||
};
|
||||
struct Cache : NoCopy {
|
||||
~Cache();
|
||||
Mutex mutex;
|
||||
std::vector<CacheEntry *> entries;
|
||||
};
|
||||
|
||||
static Cache cache;
|
||||
|
||||
Cache::~Cache()
|
||||
{
|
||||
for ( size_t i = 0, n = entries.size(); i < n; ++i )
|
||||
delete entries[i];
|
||||
}
|
||||
|
||||
static const CacheEntry *CacheLookupRateDependent( Bit32u rate )
|
||||
{
|
||||
for ( size_t i = 0, n = cache.entries.size(); i < n; ++i ) {
|
||||
const CacheEntry *entry = cache.entries[i];
|
||||
if (entry->rate == rate)
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const CacheEntry &ComputeRateDependent( Bit32u rate )
|
||||
{
|
||||
{
|
||||
MutexHolder lock( cache.mutex );
|
||||
if (const CacheEntry *entry = CacheLookupRateDependent( rate ))
|
||||
return *entry;
|
||||
}
|
||||
|
||||
double original = OPLRATE;
|
||||
// double original = rate;
|
||||
double scale = original / (double)rate;
|
||||
|
||||
//Noise counter is run at the same precision as general waves
|
||||
noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
|
||||
noiseCounter = 0;
|
||||
noiseValue = 1; //Make sure it triggers the noise xor the first time
|
||||
//The low frequency oscillation counter
|
||||
//Every time his overflows vibrato and tremoloindex are increased
|
||||
lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
|
||||
lfoCounter = 0;
|
||||
vibratoIndex = 0;
|
||||
tremoloIndex = 0;
|
||||
#if __cplusplus >= 201103L
|
||||
std::unique_ptr<CacheEntry> entry(new CacheEntry);
|
||||
#else
|
||||
std::auto_ptr<CacheEntry> entry(new CacheEntry);
|
||||
#endif
|
||||
entry->rate = rate;
|
||||
Bit32u *freqMul = entry->freqMul;
|
||||
Bit32u *linearRates = entry->linearRates;
|
||||
Bit32u *attackRates = entry->attackRates;
|
||||
|
||||
//With higher octave this gets shifted up
|
||||
//-1 since the freqCreateTable = *2
|
||||
|
@ -1301,6 +1398,7 @@ void Chip::Setup( Bit32u rate ) {
|
|||
EnvelopeSelect( i, index, shift );
|
||||
linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 )));
|
||||
}
|
||||
|
||||
// Bit32s attackDiffs[62];
|
||||
//Generate the best matching attack rate
|
||||
for ( Bit8u i = 0; i < 62; i++ ) {
|
||||
|
@ -1353,6 +1451,36 @@ void Chip::Setup( Bit32u rate ) {
|
|||
//This should provide instant volume maximizing
|
||||
attackRates[i] = 8 << RATE_SH;
|
||||
}
|
||||
|
||||
MutexHolder lock( cache.mutex );
|
||||
if (const CacheEntry *entry = CacheLookupRateDependent( rate ))
|
||||
return *entry;
|
||||
|
||||
cache.entries.push_back(entry.get());
|
||||
return *entry.release();
|
||||
}
|
||||
|
||||
void Chip::Setup( Bit32u rate ) {
|
||||
double original = OPLRATE;
|
||||
// double original = rate;
|
||||
double scale = original / (double)rate;
|
||||
|
||||
//Noise counter is run at the same precision as general waves
|
||||
noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
|
||||
noiseCounter = 0;
|
||||
noiseValue = 1; //Make sure it triggers the noise xor the first time
|
||||
//The low frequency oscillation counter
|
||||
//Every time his overflows vibrato and tremoloindex are increased
|
||||
lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
|
||||
lfoCounter = 0;
|
||||
vibratoIndex = 0;
|
||||
tremoloIndex = 0;
|
||||
|
||||
const CacheEntry &entry = ComputeRateDependent( rate );
|
||||
freqMul = entry.freqMul;
|
||||
linearRates = entry.linearRates;
|
||||
attackRates = entry.attackRates;
|
||||
|
||||
//Setup the channels with the correct four op flags
|
||||
//Channels are accessed through a table so they appear linear here
|
||||
chan[ 0].fourMask = 0x00 | ( 1 << 0 );
|
||||
|
@ -1388,6 +1516,10 @@ void Chip::Setup( Bit32u rate ) {
|
|||
WriteReg( i, 0xff );
|
||||
WriteReg( i, 0x0 );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < 18; i++ ) {
|
||||
chan[i].WritePan( 0x40 );
|
||||
}
|
||||
}
|
||||
|
||||
static bool doneTables = false;
|
||||
|
@ -1614,5 +1746,14 @@ void Handler::Init( Bitu rate ) {
|
|||
chip.Setup( static_cast<Bit32u>(rate) );
|
||||
}
|
||||
|
||||
void Handler::WritePan( Bit32u reg, Bit8u val )
|
||||
{
|
||||
Bitu index;
|
||||
index = ((reg >> 4) & 0x10) | (reg & 0xf);
|
||||
if (ChanOffsetTable[index]) {
|
||||
Channel* regChan = (Channel*)(((char *)&chip) + ChanOffsetTable[index]);
|
||||
regChan->WritePan(val);
|
||||
}
|
||||
}
|
||||
|
||||
} //Namespace DBOPL
|
||||
|
|
|
@ -75,13 +75,13 @@ typedef enum {
|
|||
sm3AMAM,
|
||||
sm6Start,
|
||||
sm2Percussion,
|
||||
sm3Percussion,
|
||||
sm3Percussion
|
||||
} SynthMode;
|
||||
|
||||
//Shifts for the values contained in chandata variable
|
||||
enum {
|
||||
SHIFT_KSLBASE = 16,
|
||||
SHIFT_KEYCODE = 24,
|
||||
SHIFT_KEYCODE = 24
|
||||
};
|
||||
|
||||
struct Operator {
|
||||
|
@ -91,7 +91,7 @@ public:
|
|||
MASK_KSR = 0x10,
|
||||
MASK_SUSTAIN = 0x20,
|
||||
MASK_VIBRATO = 0x40,
|
||||
MASK_TREMOLO = 0x80,
|
||||
MASK_TREMOLO = 0x80
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
@ -99,7 +99,7 @@ public:
|
|||
RELEASE,
|
||||
SUSTAIN,
|
||||
DECAY,
|
||||
ATTACK,
|
||||
ATTACK
|
||||
} State;
|
||||
|
||||
VolumeHandler volHandler;
|
||||
|
@ -192,6 +192,9 @@ struct Channel {
|
|||
Bit8s maskLeft; //Sign extended values for both channel's panning
|
||||
Bit8s maskRight;
|
||||
|
||||
Bit16u panLeft; // Extended behavior, scale values for soft panning
|
||||
Bit16u panRight;
|
||||
|
||||
//Forward the channel data to the operators of the channel
|
||||
void SetChanData( const Chip* chip, Bit32u data );
|
||||
//Change in the chandata, check for new values and if we have to forward to operators
|
||||
|
@ -201,6 +204,8 @@ struct Channel {
|
|||
void WriteB0( const Chip* chip, Bit8u val );
|
||||
void WriteC0( const Chip* chip, Bit8u val );
|
||||
|
||||
void WritePan( Bit8u val );
|
||||
|
||||
//call this for the first channel
|
||||
template< bool opl3Mode >
|
||||
void GeneratePercussion( Chip* chip, Bit32s* output );
|
||||
|
@ -222,11 +227,11 @@ struct Chip {
|
|||
Bit32u noiseValue;
|
||||
|
||||
//Frequency scales for the different multiplications
|
||||
Bit32u freqMul[16];
|
||||
const Bit32u *freqMul/*[16]*/;
|
||||
//Rates for decay and release for rate of this chip
|
||||
Bit32u linearRates[76];
|
||||
const Bit32u *linearRates/*[76]*/;
|
||||
//Best match attack rates for the rate of this chip
|
||||
Bit32u attackRates[76];
|
||||
const Bit32u *attackRates/*[76]*/;
|
||||
|
||||
//18 channels with 2 operators each
|
||||
Channel chan[18];
|
||||
|
@ -271,6 +276,7 @@ struct Chip {
|
|||
|
||||
struct Handler {
|
||||
DBOPL::Chip chip;
|
||||
void WritePan( Bit32u port, Bit8u val );
|
||||
Bit32u WriteAddr( Bit32u port, Bit8u val );
|
||||
void WriteReg( Bit32u addr, Bit8u val );
|
||||
void GenerateArr(Bit32s *out, Bitu *samples);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "dosbox_opl3.h"
|
||||
#include "dosbox/dbopl.h"
|
||||
#include <new>
|
||||
|
@ -41,6 +61,12 @@ void DosBoxOPL3::writeReg(uint16_t addr, uint8_t data)
|
|||
chip_r->WriteReg(static_cast<Bit32u>(addr), data);
|
||||
}
|
||||
|
||||
void DosBoxOPL3::writePan(uint16_t addr, uint8_t data)
|
||||
{
|
||||
DBOPL::Handler *chip_r = reinterpret_cast<DBOPL::Handler*>(m_chip);
|
||||
chip_r->WritePan(static_cast<Bit32u>(addr), data);
|
||||
}
|
||||
|
||||
void DosBoxOPL3::nativeGenerateN(int16_t *output, size_t frames)
|
||||
{
|
||||
DBOPL::Handler *chip_r = reinterpret_cast<DBOPL::Handler*>(m_chip);
|
||||
|
@ -50,5 +76,5 @@ void DosBoxOPL3::nativeGenerateN(int16_t *output, size_t frames)
|
|||
|
||||
const char *DosBoxOPL3::emulatorName()
|
||||
{
|
||||
return "DosBox 0.74-r4111 OPL3";
|
||||
return "DOSBox 0.74-r4111 OPL3";
|
||||
}
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef DOSBOX_OPL3_H
|
||||
#define DOSBOX_OPL3_H
|
||||
|
||||
|
@ -14,6 +34,7 @@ public:
|
|||
void setRate(uint32_t rate) override;
|
||||
void reset() override;
|
||||
void writeReg(uint16_t addr, uint8_t data) override;
|
||||
void writePan(uint16_t addr, uint8_t data) override;
|
||||
void nativePreGenerate() override {}
|
||||
void nativePostGenerate() override {}
|
||||
void nativeGenerateN(int16_t *output, size_t frames) override;
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Nuked OPL3 emulator.
|
||||
* Thanks:
|
||||
|
@ -174,6 +177,32 @@ static const Bit8u ch_slot[18] = {
|
|||
0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32
|
||||
};
|
||||
|
||||
/*
|
||||
* Pan law table
|
||||
*/
|
||||
|
||||
static const Bit16u panlawtable[] =
|
||||
{
|
||||
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
|
||||
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
|
||||
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
|
||||
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
|
||||
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
|
||||
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
|
||||
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
|
||||
50433, 49912, 49383, 48846, 48302, 47750, 47191,
|
||||
46340, /* Center left */
|
||||
46340, /* Center right */
|
||||
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
|
||||
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
|
||||
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
|
||||
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
|
||||
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
|
||||
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
|
||||
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
|
||||
4858, 4050, 3240, 2431, 1620, 810, 0
|
||||
};
|
||||
|
||||
/*
|
||||
* Envelope generator
|
||||
*/
|
||||
|
@ -1068,7 +1097,7 @@ void OPL3_Generate(opl3_chip *chip, Bit16s *buf)
|
|||
{
|
||||
accm += *chip->channel[ii].out[jj];
|
||||
}
|
||||
chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha);
|
||||
chip->mixbuff[0] += (Bit16s)((accm * chip->channel[ii].chl / 65535) & chip->channel[ii].cha);
|
||||
}
|
||||
|
||||
for (ii = 15; ii < 18; ii++)
|
||||
|
@ -1097,7 +1126,7 @@ void OPL3_Generate(opl3_chip *chip, Bit16s *buf)
|
|||
{
|
||||
accm += *chip->channel[ii].out[jj];
|
||||
}
|
||||
chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb);
|
||||
chip->mixbuff[1] += (Bit16s)((accm * chip->channel[ii].chr / 65535) & chip->channel[ii].chb);
|
||||
}
|
||||
|
||||
for (ii = 33; ii < 36; ii++)
|
||||
|
@ -1229,6 +1258,8 @@ void OPL3_Reset(opl3_chip *chip, Bit32u samplerate)
|
|||
chip->channel[channum].chtype = ch_2op;
|
||||
chip->channel[channum].cha = 0xffff;
|
||||
chip->channel[channum].chb = 0xffff;
|
||||
chip->channel[channum].chl = 46340;
|
||||
chip->channel[channum].chr = 46340;
|
||||
chip->channel[channum].ch_num = channum;
|
||||
OPL3_ChannelSetupAlg(&chip->channel[channum]);
|
||||
}
|
||||
|
@ -1238,6 +1269,19 @@ void OPL3_Reset(opl3_chip *chip, Bit32u samplerate)
|
|||
chip->vibshift = 1;
|
||||
}
|
||||
|
||||
static void OPL3_ChannelWritePan(opl3_channel *channel, Bit8u data)
|
||||
{
|
||||
channel->chl = panlawtable[data & 0x7F];
|
||||
channel->chr = panlawtable[0x7F - (data & 0x7F)];
|
||||
}
|
||||
|
||||
void OPL3_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v)
|
||||
{
|
||||
Bit8u high = (reg >> 8) & 0x01;
|
||||
Bit8u regm = reg & 0xff;
|
||||
OPL3_ChannelWritePan(&chip->channel[9 * high + (regm & 0x0f)], v);
|
||||
}
|
||||
|
||||
void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v)
|
||||
{
|
||||
Bit8u high = (reg >> 8) & 0x01;
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Nuked OPL3 emulator.
|
||||
* Thanks:
|
||||
|
@ -98,6 +101,7 @@ struct _opl3_channel {
|
|||
Bit8u alg;
|
||||
Bit8u ksv;
|
||||
Bit16u cha, chb;
|
||||
Bit16u chl, chr;
|
||||
Bit8u ch_num;
|
||||
};
|
||||
|
||||
|
@ -150,6 +154,7 @@ void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf);
|
|||
void OPL3_Reset(opl3_chip *chip, Bit32u samplerate);
|
||||
void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v);
|
||||
void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v);
|
||||
void OPL3_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v);
|
||||
void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
|
||||
void OPL3_GenerateStreamMix(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
|
||||
|
||||
|
|
|
@ -229,6 +229,32 @@ static const Bit8u ch_slot[18] = {
|
|||
0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32
|
||||
};
|
||||
|
||||
/*
|
||||
* Pan law table
|
||||
*/
|
||||
|
||||
static const Bit16u panlawtable[] =
|
||||
{
|
||||
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
|
||||
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
|
||||
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
|
||||
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
|
||||
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
|
||||
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
|
||||
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
|
||||
50433, 49912, 49383, 48846, 48302, 47750, 47191,
|
||||
46340, /* Center left */
|
||||
46340, /* Center right */
|
||||
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
|
||||
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
|
||||
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
|
||||
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
|
||||
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
|
||||
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
|
||||
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
|
||||
4858, 4050, 3240, 2431, 1620, 810, 0
|
||||
};
|
||||
|
||||
/*
|
||||
* Envelope generator
|
||||
*/
|
||||
|
@ -1082,7 +1108,7 @@ void OPL3v17_Generate(opl3_chip *chip, Bit16s *buf)
|
|||
{
|
||||
accm += *chip->channel[ii].out[jj];
|
||||
}
|
||||
chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha);
|
||||
chip->mixbuff[0] += (Bit16s)((accm * chip->channel[ii].chl / 65535) & chip->channel[ii].cha);
|
||||
}
|
||||
|
||||
for (ii = 15; ii < 18; ii++)
|
||||
|
@ -1121,7 +1147,7 @@ void OPL3v17_Generate(opl3_chip *chip, Bit16s *buf)
|
|||
{
|
||||
accm += *chip->channel[ii].out[jj];
|
||||
}
|
||||
chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb);
|
||||
chip->mixbuff[1] += (Bit16s)((accm * chip->channel[ii].chr / 65535) & chip->channel[ii].chb);
|
||||
}
|
||||
|
||||
for (ii = 33; ii < 36; ii++)
|
||||
|
@ -1220,8 +1246,10 @@ void OPL3v17_Reset(opl3_chip *chip, Bit32u samplerate)
|
|||
chip->channel[channum].out[2] = &chip->zeromod;
|
||||
chip->channel[channum].out[3] = &chip->zeromod;
|
||||
chip->channel[channum].chtype = ch_2op;
|
||||
chip->channel[channum].cha = ~0;
|
||||
chip->channel[channum].chb = ~0;
|
||||
chip->channel[channum].cha = 0xffff;
|
||||
chip->channel[channum].chb = 0xffff;
|
||||
chip->channel[channum].chl = 46340;
|
||||
chip->channel[channum].chr = 46340;
|
||||
OPL3_ChannelSetupAlg(&chip->channel[channum]);
|
||||
}
|
||||
chip->noise = 0x306600;
|
||||
|
@ -1230,6 +1258,19 @@ void OPL3v17_Reset(opl3_chip *chip, Bit32u samplerate)
|
|||
chip->vibshift = 1;
|
||||
}
|
||||
|
||||
static void OPL3v17_ChannelWritePan(opl3_channel *channel, Bit8u data)
|
||||
{
|
||||
channel->chl = panlawtable[data & 0x7F];
|
||||
channel->chr = panlawtable[0x7F - (data & 0x7F)];
|
||||
}
|
||||
|
||||
void OPL3v17_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v)
|
||||
{
|
||||
Bit8u high = (reg >> 8) & 0x01;
|
||||
Bit8u regm = reg & 0xff;
|
||||
OPL3v17_ChannelWritePan(&chip->channel[9 * high + (regm & 0x0f)], v);
|
||||
}
|
||||
|
||||
void OPL3v17_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v)
|
||||
{
|
||||
Bit8u high = (reg >> 8) & 0x01;
|
||||
|
|
|
@ -103,6 +103,7 @@ struct _opl3_channel {
|
|||
Bit8u alg;
|
||||
Bit8u ksv;
|
||||
Bit16u cha, chb;
|
||||
Bit16u chl, chr;
|
||||
};
|
||||
|
||||
typedef struct _opl3_writebuf {
|
||||
|
@ -142,6 +143,7 @@ struct _opl3_chip {
|
|||
void OPL3v17_Generate(opl3_chip *chip, Bit16s *buf);
|
||||
void OPL3v17_GenerateResampled(opl3_chip *chip, Bit16s *buf);
|
||||
void OPL3v17_Reset(opl3_chip *chip, Bit32u samplerate);
|
||||
void OPL3v17_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v);
|
||||
void OPL3v17_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v);
|
||||
void OPL3v17_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v);
|
||||
void OPL3v17_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "nuked_opl3.h"
|
||||
#include "nuked/nukedopl3.h"
|
||||
#include <cstring>
|
||||
|
@ -37,6 +57,12 @@ void NukedOPL3::writeReg(uint16_t addr, uint8_t data)
|
|||
OPL3_WriteRegBuffered(chip_r, addr, data);
|
||||
}
|
||||
|
||||
void NukedOPL3::writePan(uint16_t addr, uint8_t data)
|
||||
{
|
||||
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);
|
||||
OPL3_WritePan(chip_r, addr, data);
|
||||
}
|
||||
|
||||
void NukedOPL3::nativeGenerate(int16_t *frame)
|
||||
{
|
||||
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef NUKED_OPL3_H
|
||||
#define NUKED_OPL3_H
|
||||
|
||||
|
@ -14,6 +34,7 @@ public:
|
|||
void setRate(uint32_t rate) override;
|
||||
void reset() override;
|
||||
void writeReg(uint16_t addr, uint8_t data) override;
|
||||
void writePan(uint16_t addr, uint8_t data) override;
|
||||
void nativePreGenerate() override {}
|
||||
void nativePostGenerate() override {}
|
||||
void nativeGenerate(int16_t *frame) override;
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "nuked_opl3_v174.h"
|
||||
#include "nuked/nukedopl3_174.h"
|
||||
#include <cstring>
|
||||
|
@ -37,6 +57,12 @@ void NukedOPL3v174::writeReg(uint16_t addr, uint8_t data)
|
|||
OPL3v17_WriteReg(chip_r, addr, data);
|
||||
}
|
||||
|
||||
void NukedOPL3v174::writePan(uint16_t addr, uint8_t data)
|
||||
{
|
||||
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);
|
||||
OPL3v17_WritePan(chip_r, addr, data);
|
||||
}
|
||||
|
||||
void NukedOPL3v174::nativeGenerate(int16_t *frame)
|
||||
{
|
||||
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef NUKED_OPL3174_H
|
||||
#define NUKED_OPL3174_H
|
||||
|
||||
|
@ -14,6 +34,7 @@ public:
|
|||
void setRate(uint32_t rate) override;
|
||||
void reset() override;
|
||||
void writeReg(uint16_t addr, uint8_t data) override;
|
||||
void writePan(uint16_t addr, uint8_t data) override;
|
||||
void nativePreGenerate() override {}
|
||||
void nativePostGenerate() override {}
|
||||
void nativeGenerate(int16_t *frame) override;
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ONP_CHIP_BASE_H
|
||||
#define ONP_CHIP_BASE_H
|
||||
|
||||
|
@ -43,6 +61,9 @@ public:
|
|||
virtual void reset() = 0;
|
||||
virtual void writeReg(uint16_t addr, uint8_t data) = 0;
|
||||
|
||||
// extended
|
||||
virtual void writePan(uint16_t addr, uint8_t data) { (void)addr; (void)data; }
|
||||
|
||||
virtual void nativePreGenerate() = 0;
|
||||
virtual void nativePostGenerate() = 0;
|
||||
virtual void nativeGenerate(int16_t *frame) = 0;
|
||||
|
|
|
@ -236,8 +236,8 @@ void OPLChipBaseT<T>::resampledGenerate(int32_t *output)
|
|||
rsm->out_count = 1;
|
||||
rsm->out_data = f_out;
|
||||
}
|
||||
output[0] = std::lround(f_out[0]);
|
||||
output[1] = std::lround(f_out[1]);
|
||||
output[0] = static_cast<int32_t>(std::lround(f_out[0]));
|
||||
output[1] = static_cast<int32_t>(std::lround(f_out[1]));
|
||||
}
|
||||
#else
|
||||
template <class T>
|
||||
|
|
300
src/sound/adlmidi/file_reader.hpp
Normal file
300
src/sound/adlmidi/file_reader.hpp
Normal file
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef FILE_AND_MEM_READER_HHHH
|
||||
#define FILE_AND_MEM_READER_HHHH
|
||||
|
||||
#include <string> // std::string
|
||||
#include <cstdio> // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof
|
||||
#include <stdint.h> // uint*_t
|
||||
#include <stddef.h> // size_t and friends
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX 1
|
||||
#include <cstring> // std::strlen
|
||||
#include <windows.h> // MultiByteToWideChar
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief A little class gives able to read filedata from disk and also from a memory segment
|
||||
*/
|
||||
class FileAndMemReader
|
||||
{
|
||||
//! Currently loaded filename (empty for a memory blocks)
|
||||
std::string m_file_name;
|
||||
//! File reader descriptor
|
||||
std::FILE *m_fp;
|
||||
|
||||
//! Memory pointer descriptor
|
||||
const void *m_mp;
|
||||
//! Size of memory block
|
||||
size_t m_mp_size;
|
||||
//! Cursor position in the memory block
|
||||
size_t m_mp_tell;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Relation direction
|
||||
*/
|
||||
enum relTo
|
||||
{
|
||||
//! At begin position
|
||||
SET = SEEK_SET,
|
||||
//! At current position
|
||||
CUR = SEEK_CUR,
|
||||
//! At end position
|
||||
END = SEEK_END
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief C.O.: It's a constructor!
|
||||
*/
|
||||
FileAndMemReader() :
|
||||
m_fp(NULL),
|
||||
m_mp(NULL),
|
||||
m_mp_size(0),
|
||||
m_mp_tell(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief C.O.: It's a destructor!
|
||||
*/
|
||||
~FileAndMemReader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open file from a disk
|
||||
* @param path Path to the file in UTF-8 (even on Windows!)
|
||||
*/
|
||||
void openFile(const char *path)
|
||||
{
|
||||
if(m_fp)
|
||||
this->close();//Close previously opened file first!
|
||||
#if !defined(_WIN32) || defined(__WATCOMC__)
|
||||
m_fp = std::fopen(path, "rb");
|
||||
#else
|
||||
wchar_t widePath[MAX_PATH];
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast<int>(std::strlen(path)), widePath, MAX_PATH);
|
||||
widePath[size] = '\0';
|
||||
m_fp = _wfopen(widePath, L"rb");
|
||||
#endif
|
||||
m_file_name = path;
|
||||
m_mp = NULL;
|
||||
m_mp_size = 0;
|
||||
m_mp_tell = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open file from memory block
|
||||
* @param mem Pointer to the memory block
|
||||
* @param lenght Size of given block
|
||||
*/
|
||||
void openData(const void *mem, size_t lenght)
|
||||
{
|
||||
if(m_fp)
|
||||
this->close();//Close previously opened file first!
|
||||
m_fp = NULL;
|
||||
m_mp = mem;
|
||||
m_mp_size = lenght;
|
||||
m_mp_tell = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Seek to given position
|
||||
* @param pos Offset or position
|
||||
* @param rel_to Relation (at begin, at current, or at end)
|
||||
*/
|
||||
void seek(long pos, int rel_to)
|
||||
{
|
||||
if(!this->isValid())
|
||||
return;
|
||||
|
||||
if(m_fp)//If a file
|
||||
{
|
||||
std::fseek(m_fp, pos, rel_to);
|
||||
}
|
||||
else//If a memory block
|
||||
{
|
||||
switch(rel_to)
|
||||
{
|
||||
case SET:
|
||||
m_mp_tell = static_cast<size_t>(pos);
|
||||
break;
|
||||
|
||||
case END:
|
||||
m_mp_tell = m_mp_size - static_cast<size_t>(pos);
|
||||
break;
|
||||
|
||||
case CUR:
|
||||
m_mp_tell = m_mp_tell + static_cast<size_t>(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_mp_tell > m_mp_size)
|
||||
m_mp_tell = m_mp_size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Seek to given position (unsigned integer 64 as relation. Negative values not supported)
|
||||
* @param pos Offset or position
|
||||
* @param rel_to Relation (at begin, at current, or at end)
|
||||
*/
|
||||
inline void seeku(uint64_t pos, int rel_to)
|
||||
{
|
||||
this->seek(static_cast<long>(pos), rel_to);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the buffer from a file
|
||||
* @param buf Pointer to the destination memory block
|
||||
* @param num Number of elements
|
||||
* @param size Size of one element
|
||||
* @return Size
|
||||
*/
|
||||
size_t read(void *buf, size_t num, size_t size)
|
||||
{
|
||||
if(!this->isValid())
|
||||
return 0;
|
||||
if(m_fp)
|
||||
return std::fread(buf, num, size, m_fp);
|
||||
else
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t maxSize = static_cast<size_t>(size * num);
|
||||
|
||||
while((pos < maxSize) && (m_mp_tell < m_mp_size))
|
||||
{
|
||||
reinterpret_cast<uint8_t *>(buf)[pos] = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
|
||||
m_mp_tell++;
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos / num;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get one byte and seek forward
|
||||
* @return Readed byte or EOF (a.k.a. -1)
|
||||
*/
|
||||
int getc()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return -1;
|
||||
if(m_fp)//If a file
|
||||
{
|
||||
return std::getc(m_fp);
|
||||
}
|
||||
else //If a memory block
|
||||
{
|
||||
if(m_mp_tell >= m_mp_size)
|
||||
return -1;
|
||||
int x = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
|
||||
m_mp_tell++;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns current offset of cursor in a file
|
||||
* @return Offset position
|
||||
*/
|
||||
size_t tell()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return 0;
|
||||
if(m_fp)//If a file
|
||||
return static_cast<size_t>(std::ftell(m_fp));
|
||||
else//If a memory block
|
||||
return m_mp_tell;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close the file
|
||||
*/
|
||||
void close()
|
||||
{
|
||||
if(m_fp)
|
||||
std::fclose(m_fp);
|
||||
|
||||
m_fp = NULL;
|
||||
m_mp = NULL;
|
||||
m_mp_size = 0;
|
||||
m_mp_tell = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Is file instance valid
|
||||
* @return true if vaild
|
||||
*/
|
||||
bool isValid()
|
||||
{
|
||||
return (m_fp) || (m_mp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Is End Of File?
|
||||
* @return true if end of file was reached
|
||||
*/
|
||||
bool eof()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return true;
|
||||
if(m_fp)
|
||||
return (std::feof(m_fp) != 0);
|
||||
else
|
||||
return m_mp_tell >= m_mp_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a current file name
|
||||
* @return File name of currently loaded file
|
||||
*/
|
||||
const std::string &fileName()
|
||||
{
|
||||
return m_file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve file size
|
||||
* @return Size of file in bytes
|
||||
*/
|
||||
size_t fileSize()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return 0;
|
||||
if(!m_fp)
|
||||
return m_mp_size; //Size of memory block is well known
|
||||
size_t old_pos = this->tell();
|
||||
seek(0l, FileAndMemReader::END);
|
||||
size_t file_size = this->tell();
|
||||
seek(static_cast<long>(old_pos), FileAndMemReader::SET);
|
||||
return file_size;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* FILE_AND_MEM_READER_HHHH */
|
|
@ -1,215 +0,0 @@
|
|||
#ifndef bqw_fraction_h
|
||||
#define bqw_fraction_h
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
|
||||
/* Fraction number handling.
|
||||
* Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/)
|
||||
*/
|
||||
|
||||
template<typename inttype=int>
|
||||
class fraction
|
||||
{
|
||||
inttype num1, num2;
|
||||
typedef fraction<inttype> self;
|
||||
void Optim();
|
||||
|
||||
#if 1
|
||||
inline void Debug(char, const self &) { }
|
||||
#else
|
||||
inline void Debug(char op, const self &b)
|
||||
{
|
||||
cerr << nom() << '/' << denom() << ' ' << op
|
||||
<< ' ' << b.nom() << '/' << denom()
|
||||
<< ":\n";
|
||||
}
|
||||
#endif
|
||||
public:
|
||||
void set(inttype n, inttype d) { num1=n; num2=d; Optim(); }
|
||||
|
||||
fraction() : num1(0), num2(1) { }
|
||||
fraction(inttype value) : num1(value), num2(1) { }
|
||||
fraction(inttype n, inttype d) : num1(n), num2(d) { }
|
||||
fraction(int value) : num1(value), num2(1) { }
|
||||
template<typename floattype>
|
||||
explicit fraction(const floattype value) { operator= (value); }
|
||||
inline double value() const {return nom() / (double)denom(); }
|
||||
inline long double valuel() const {return nom() / (long double)denom(); }
|
||||
self &operator+= (const inttype &value) { num1+=value*denom(); Optim(); return *this; }
|
||||
self &operator-= (const inttype &value) { num1-=value*denom(); Optim(); return *this; }
|
||||
self &operator*= (const inttype &value) { num1*=value; Optim(); return *this; }
|
||||
self &operator/= (const inttype &value) { num2*=value; Optim(); return *this; }
|
||||
self &operator+= (const self &b);
|
||||
self &operator-= (const self &b);
|
||||
self &operator*= (const self &b) { Debug('*',b);num1*=b.nom(); num2*=b.denom(); Optim(); return *this; }
|
||||
self &operator/= (const self &b) { Debug('/',b);num1*=b.denom(); num2*=b.nom(); Optim(); return *this; }
|
||||
self operator- () const { return self(-num1, num2); }
|
||||
|
||||
#define fraction_blah_func(op1, op2) \
|
||||
self operator op1 (const self &b) const { self tmp(*this); tmp op2 b; return tmp; }
|
||||
|
||||
fraction_blah_func( +, += )
|
||||
fraction_blah_func( -, -= )
|
||||
fraction_blah_func( /, /= )
|
||||
fraction_blah_func( *, *= )
|
||||
|
||||
#undef fraction_blah_func
|
||||
#define fraction_blah_func(op) \
|
||||
bool operator op(const self &b) const { return value() op b.value(); } \
|
||||
bool operator op(inttype b) const { return value() op b; }
|
||||
|
||||
fraction_blah_func( < )
|
||||
fraction_blah_func( > )
|
||||
fraction_blah_func( <= )
|
||||
fraction_blah_func( >= )
|
||||
|
||||
#undef fraction_blah_func
|
||||
|
||||
const inttype &nom() const { return num1; }
|
||||
const inttype &denom() const { return num2; }
|
||||
inline bool operator == (inttype b) const { return denom() == 1 && nom() == b; }
|
||||
inline bool operator != (inttype b) const { return denom() != 1 || nom() != b; }
|
||||
inline bool operator == (const self &b) const { return denom()==b.denom() && nom()==b.nom(); }
|
||||
inline bool operator != (const self &b) const { return denom()!=b.denom() || nom()!=b.nom(); }
|
||||
//operator bool () const { return nom() != 0; }
|
||||
inline bool negative() const { return (nom() < 0) ^ (denom() < 0); }
|
||||
|
||||
self &operator= (const inttype value) { num2=1; num1=value; return *this; }
|
||||
//self &operator= (int value) { num2=1; num1=value; return *this; }
|
||||
|
||||
self &operator= (double orig) { return *this = (long double)orig; }
|
||||
self &operator= (long double orig);
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable:4146)
|
||||
#endif
|
||||
|
||||
template<typename inttype>
|
||||
void fraction<inttype>::Optim()
|
||||
{
|
||||
/* Euclidean algorithm */
|
||||
inttype n1, n2, nn1, nn2;
|
||||
|
||||
nn1 = std::numeric_limits<inttype>::is_signed ? (num1 >= 0 ? num1 : -num1) : num1;
|
||||
nn2 = std::numeric_limits<inttype>::is_signed ? (num2 >= 0 ? num2 : -num2) : num2;
|
||||
|
||||
if(nn1 < nn2)
|
||||
n1 = num1, n2 = num2;
|
||||
else
|
||||
n1 = num2, n2 = num1;
|
||||
|
||||
if(!num1) { num2 = 1; return; }
|
||||
for(;;)
|
||||
{
|
||||
//fprintf(stderr, "%d/%d: n1=%d,n2=%d\n", nom(),denom(),n1,n2);
|
||||
inttype tmp = n2 % n1;
|
||||
if(!tmp)break;
|
||||
n2 = n1;
|
||||
n1 = tmp;
|
||||
}
|
||||
num1 /= n1;
|
||||
num2 /= n1;
|
||||
//fprintf(stderr, "result: %d/%d\n\n", nom(), denom());
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(default:4146)
|
||||
#endif
|
||||
|
||||
template<typename inttype>
|
||||
inline const fraction<inttype> abs(const fraction<inttype> &f)
|
||||
{
|
||||
return fraction<inttype>(abs(f.nom()), abs(f.denom()));
|
||||
}
|
||||
|
||||
#define fraction_blah_func(op) \
|
||||
template<typename inttype> \
|
||||
fraction<inttype> operator op \
|
||||
(const inttype bla, const fraction<inttype> &b) \
|
||||
{ return fraction<inttype> (bla) op b; }
|
||||
fraction_blah_func( + )
|
||||
fraction_blah_func( - )
|
||||
fraction_blah_func( * )
|
||||
fraction_blah_func( / )
|
||||
#undef fraction_blah_func
|
||||
|
||||
#define fraction_blah_func(op1, op2) \
|
||||
template<typename inttype> \
|
||||
fraction<inttype> &fraction<inttype>::operator op2 (const fraction<inttype> &b) \
|
||||
{ \
|
||||
inttype newnom = nom()*b.denom() op1 denom()*b.nom(); \
|
||||
num2 *= b.denom(); \
|
||||
num1 = newnom; \
|
||||
Optim(); \
|
||||
return *this; \
|
||||
}
|
||||
fraction_blah_func( +, += )
|
||||
fraction_blah_func( -, -= )
|
||||
#undef fraction_blah_func
|
||||
|
||||
template<typename inttype>
|
||||
fraction<inttype> &fraction<inttype>::operator= (long double orig)
|
||||
{
|
||||
if(orig == 0.0)
|
||||
{
|
||||
set(0, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inttype cf[25];
|
||||
for(int maxdepth=1; maxdepth<25; ++maxdepth)
|
||||
{
|
||||
inttype u,v;
|
||||
long double virhe, a=orig;
|
||||
int i, viim;
|
||||
|
||||
for(i = 0; i < maxdepth; ++i)
|
||||
{
|
||||
cf[i] = (inttype)a;
|
||||
if(cf[i]-1 > cf[i])break;
|
||||
a = 1.0 / (a - cf[i]);
|
||||
}
|
||||
|
||||
for(viim=i-1; i < maxdepth; ++i)
|
||||
cf[i] = 0;
|
||||
|
||||
u = cf[viim];
|
||||
v = 1;
|
||||
for(i = viim-1; i >= 0; --i)
|
||||
{
|
||||
inttype w = cf[i] * u + v;
|
||||
v = u;
|
||||
u = w;
|
||||
}
|
||||
|
||||
virhe = (orig - (u / (long double)v)) / orig;
|
||||
|
||||
set(u, v);
|
||||
//if(verbose > 4)
|
||||
// cerr << "Guess: " << *this << " - error = " << virhe*100 << "%\n";
|
||||
|
||||
if(virhe < 1e-8 && virhe > -1e-8)break;
|
||||
}
|
||||
|
||||
//if(verbose > 4)
|
||||
//{
|
||||
// cerr << "Fraction=" << orig << ": " << *this << endl;
|
||||
//}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
template<typename inttype>
|
||||
ostream &operator << (ostream &dest, const fraction<inttype> &m)
|
||||
{
|
||||
if(m.denom() == (inttype)1) return dest << m.nom();
|
||||
return dest << m.nom() << '/' << m.denom();
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
|
@ -353,6 +353,8 @@ int WOPL_LoadInstFromMem(WOPIFile *file, void *mem, size_t length)
|
|||
GO_FORWARD(2);
|
||||
}
|
||||
|
||||
file->version = version;
|
||||
|
||||
{/* is drum flag */
|
||||
if(length < 1)
|
||||
return WOPL_ERR_UNEXPECTED_ENDING;
|
||||
|
@ -434,11 +436,13 @@ size_t WOPL_CalculateInstFileSize(WOPIFile *file, uint16_t version)
|
|||
* is percussive instrument
|
||||
*/
|
||||
|
||||
if(version >= 3)
|
||||
ins_size = WOPL_INST_SIZE_V3;
|
||||
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;
|
||||
final_size += ins_size * 128;
|
||||
final_size += ins_size;
|
||||
|
||||
return final_size;
|
||||
}
|
||||
|
|
|
@ -71,13 +71,13 @@ typedef enum WOPL_InstrumentFlags
|
|||
WOPL_Ins_IsBlank = 0x04,
|
||||
|
||||
/* RythmMode flags mask */
|
||||
WOPL_RythmModeMask = 0x38,
|
||||
WOPL_RhythmModeMask = 0x38,
|
||||
|
||||
/* Mask of the flags range */
|
||||
WOPL_Ins_ALL_MASK = 0x07
|
||||
} WOPL_InstrumentFlags;
|
||||
|
||||
typedef enum WOPL_RythmMode
|
||||
typedef enum WOPL_RhythmMode
|
||||
{
|
||||
/* RythmMode: BassDrum */
|
||||
WOPL_RM_BassDrum = 0x08,
|
||||
|
@ -86,10 +86,13 @@ typedef enum WOPL_RythmMode
|
|||
/* RythmMode: TomTom */
|
||||
WOPL_RM_TomTom = 0x18,
|
||||
/* RythmMode: Cymbell */
|
||||
WOPL_RM_Cymball = 0x20,
|
||||
WOPL_RM_Cymbal = 0x20,
|
||||
/* RythmMode: HiHat */
|
||||
WOPL_RM_HiHat = 0x28
|
||||
} WOPL_RythmMode;
|
||||
} WOPL_RhythmMode;
|
||||
|
||||
/* DEPRECATED: It has typo. Don't use it! */
|
||||
typedef WOPL_RhythmMode WOPL_RythmMode;
|
||||
|
||||
/* Error codes */
|
||||
typedef enum WOPL_ErrorCodes
|
||||
|
|
|
@ -331,6 +331,9 @@ protected:
|
|||
void HandleEvent(int status, int parm1, int parm2);
|
||||
void HandleLongEvent(const uint8_t *data, int len);
|
||||
void ComputeOutput(float *buffer, int len);
|
||||
|
||||
private:
|
||||
int LoadCustomBank(const char *bankfile);
|
||||
};
|
||||
|
||||
|
||||
|
@ -341,14 +344,17 @@ public:
|
|||
OPNMIDIDevice(const char *args);
|
||||
~OPNMIDIDevice();
|
||||
|
||||
|
||||
int Open(MidiCallback, void *userdata);
|
||||
int GetDeviceType() const override { return MDEV_OPN; }
|
||||
|
||||
protected:
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2);
|
||||
void HandleLongEvent(const uint8_t *data, int len);
|
||||
void ComputeOutput(float *buffer, int len);
|
||||
|
||||
private:
|
||||
int LoadCustomBank(const char *bankfile);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -302,6 +302,16 @@ void FSoundFontManager::ProcessOneFile(const FString &fn)
|
|||
FSoundFontInfo sft = { fb, fbe, fn, SF_SF2 };
|
||||
soundfonts.Push(sft);
|
||||
}
|
||||
if (!memcmp(head, "WOPL3-BANK\0", 11))
|
||||
{
|
||||
FSoundFontInfo sft = { fb, fbe, fn, SF_WOPL };
|
||||
soundfonts.Push(sft);
|
||||
}
|
||||
if (!memcmp(head, "WOPN2-BANK\0", 11) || !memcmp(head, "WOPN2-B2NK\0", 11))
|
||||
{
|
||||
FSoundFontInfo sft = { fb, fbe, fn, SF_WOPN };
|
||||
soundfonts.Push(sft);
|
||||
}
|
||||
else if (!memcmp(head, "PK", 2))
|
||||
{
|
||||
auto zip = FResourceFile::OpenResourceFile(fn, true);
|
||||
|
@ -333,19 +343,18 @@ void FSoundFontManager::CollectSoundfonts()
|
|||
{
|
||||
findstate_t c_file;
|
||||
void *file;
|
||||
|
||||
|
||||
|
||||
if (GameConfig != NULL && GameConfig->SetSection ("SoundfontSearch.Directories"))
|
||||
{
|
||||
const char *key;
|
||||
const char *value;
|
||||
|
||||
|
||||
while (GameConfig->NextInSection (key, value))
|
||||
{
|
||||
if (stricmp (key, "Path") == 0)
|
||||
{
|
||||
FString dir;
|
||||
|
||||
|
||||
dir = NicePath(value);
|
||||
FixPathSeperator(dir);
|
||||
if (dir.IsNotEmpty())
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
enum
|
||||
{
|
||||
SF_SF2 = 1,
|
||||
SF_GUS = 2
|
||||
SF_GUS = 2,
|
||||
SF_WOPL = 4,
|
||||
SF_WOPN = 8
|
||||
};
|
||||
|
||||
struct FSoundFontInfo
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#include "i_musicinterns.h"
|
||||
#include "adlmidi/adlmidi.h"
|
||||
#include "i_soundfont.h"
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -72,6 +73,15 @@ CUSTOM_CVAR(Bool, adl_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|||
}
|
||||
}
|
||||
|
||||
CUSTOM_CVAR(Bool, adl_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
{
|
||||
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_ADL)
|
||||
{
|
||||
MIDIDeviceChanged(-1, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CUSTOM_CVAR(Int, adl_bank, 14, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
{
|
||||
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_ADL)
|
||||
|
@ -118,18 +128,11 @@ ADLMIDIDevice::ADLMIDIDevice(const char *args)
|
|||
{
|
||||
adl_switchEmulator(Renderer, (int)adl_emulator_id);
|
||||
adl_setRunAtPcmRate(Renderer, (int)adl_run_at_pcm_rate);
|
||||
// todo: Implement handling of external or in-resources WOPL bank files and load
|
||||
/*
|
||||
if(adl_use_custom_bank)
|
||||
{
|
||||
adl_openBankFile(Renderer, (char*)adl_bank_file);
|
||||
adl_openBankData(Renderer, (char*)adl_bank, (unsigned long)size);
|
||||
}
|
||||
else
|
||||
*/
|
||||
if(!LoadCustomBank(adl_custom_bank))
|
||||
adl_setBank(Renderer, (int)adl_bank);
|
||||
adl_setNumChips(Renderer, (int)adl_chips_count);
|
||||
adl_setVolumeRangeModel(Renderer, (int)adl_volume_model);
|
||||
adl_setSoftPanEnabled(Renderer, (int)adl_fullpan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,6 +151,27 @@ ADLMIDIDevice::~ADLMIDIDevice()
|
|||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: LoadCustomBank
|
||||
//
|
||||
// Loads a custom WOPL bank for libADLMIDI. Returns 1 when bank has been
|
||||
// loaded, otherwise, returns 0 when custom banks are disabled or failed
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int ADLMIDIDevice::LoadCustomBank(const char *bankfile)
|
||||
{
|
||||
if(!adl_use_custom_bank)
|
||||
return 0;
|
||||
auto info = sfmanager.FindSoundFont(bankfile, SF_WOPL);
|
||||
if(info == nullptr)
|
||||
return 0;
|
||||
bankfile = info->mFilename.GetChars();
|
||||
return (adl_openBankFile(Renderer, bankfile) == 0);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: Open
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "w_wad.h"
|
||||
#include "i_system.h"
|
||||
#include "opnmidi/opnmidi.h"
|
||||
#include "i_soundfont.h"
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -74,6 +75,14 @@ CUSTOM_CVAR(Bool, opn_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|||
}
|
||||
}
|
||||
|
||||
CUSTOM_CVAR(Bool, opn_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
{
|
||||
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_OPN)
|
||||
{
|
||||
MIDIDeviceChanged(-1, true);
|
||||
}
|
||||
}
|
||||
|
||||
CUSTOM_CVAR(Bool, opn_use_custom_bank, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
{
|
||||
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_OPN)
|
||||
|
@ -102,25 +111,21 @@ OPNMIDIDevice::OPNMIDIDevice(const char *args)
|
|||
Renderer = opn2_init(44100); // todo: make it configurable
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
if (!LoadCustomBank(opn_custom_bank))
|
||||
{
|
||||
int lump = Wads.CheckNumForFullName("xg.wopn");
|
||||
if (lump < 0)
|
||||
{
|
||||
I_Error("No OPN bank found");
|
||||
}
|
||||
FMemLump data = Wads.ReadLump(lump);
|
||||
opn2_openBankData(Renderer, data.GetMem(), (long)data.GetSize());
|
||||
}
|
||||
|
||||
opn2_switchEmulator(Renderer, (int)opn_emulator_id);
|
||||
opn2_setRunAtPcmRate(Renderer, (int)opn_run_at_pcm_rate);
|
||||
// todo: Implement handling of external or in-resources WOPN bank files and load
|
||||
/*
|
||||
if(opn_use_custom_bank)
|
||||
{
|
||||
opn2_openBankFile(Renderer, (char*)opn_bank_file);
|
||||
opn2_openBankData(Renderer, (char*)opn_bank, (long)size);
|
||||
}
|
||||
else
|
||||
*/
|
||||
int lump = Wads.CheckNumForFullName("xg.wopn");
|
||||
if (lump < 0)
|
||||
{
|
||||
I_Error("No OPN bank found");
|
||||
}
|
||||
FMemLump data = Wads.ReadLump(lump);
|
||||
opn2_openBankData(Renderer, data.GetMem(), (long)data.GetSize());
|
||||
opn2_setNumChips(Renderer, opn_chips_count);
|
||||
opn2_setSoftPanEnabled(Renderer, (int)opn_fullpan);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +144,27 @@ OPNMIDIDevice::~OPNMIDIDevice()
|
|||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: LoadCustomBank
|
||||
//
|
||||
// Loads a custom WOPN bank for libOPNMIDI. Returns 1 when bank has been
|
||||
// loaded, otherwise, returns 0 when custom banks are disabled or failed
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
||||
int OPNMIDIDevice::LoadCustomBank(const char *bankfile)
|
||||
{
|
||||
if(!opn_use_custom_bank)
|
||||
return 0;
|
||||
auto info = sfmanager.FindSoundFont(bankfile, SF_WOPN);
|
||||
if(info == nullptr)
|
||||
return 0;
|
||||
bankfile = info->mFilename.GetChars();
|
||||
return (opn2_openBankFile(Renderer, bankfile) == 0);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: Open
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
|
||||
// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
|
||||
|
||||
// Based on Gens 2.10 ym2612.c
|
||||
|
||||
|
@ -96,6 +96,8 @@ struct channel_t
|
|||
int KC[4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S)
|
||||
slot_t SLOT[4]; // four slot.operators = les 4 slots de la voie
|
||||
int FFlag; // Frequency step recalculation flag
|
||||
int PANVolumeL; // Left PCM output channel volume
|
||||
int PANVolumeR; // Right PCM output channel volume
|
||||
};
|
||||
|
||||
struct state_t
|
||||
|
@ -253,6 +255,32 @@ static const unsigned char LFO_FMS_TAB [8] =
|
|||
LFO_FMS_BASE * 12, LFO_FMS_BASE * 24
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Pan law table
|
||||
*/
|
||||
static const unsigned short panlawtable[] =
|
||||
{
|
||||
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
|
||||
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
|
||||
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
|
||||
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
|
||||
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
|
||||
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
|
||||
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
|
||||
50433, 49912, 49383, 48846, 48302, 47750, 47191,
|
||||
46340, /* Center left */
|
||||
46340, /* Center right */
|
||||
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
|
||||
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
|
||||
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
|
||||
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
|
||||
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
|
||||
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
|
||||
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
|
||||
4858, 4050, 3240, 2431, 1620, 810, 0
|
||||
};
|
||||
|
||||
inline void YM2612_Special_Update() { }
|
||||
|
||||
struct Ym2612_Impl
|
||||
|
@ -273,6 +301,7 @@ struct Ym2612_Impl
|
|||
void reset();
|
||||
void write0( int addr, int data );
|
||||
void write1( int addr, int data );
|
||||
void write_pan(int channel, int data );
|
||||
void run_timer( int );
|
||||
void run( int pair_count, Ym2612_Emu::sample_t* );
|
||||
};
|
||||
|
@ -880,6 +909,12 @@ inline void Ym2612_Impl::write1( int opn_addr, int data )
|
|||
}
|
||||
}
|
||||
|
||||
void Ym2612_Impl::write_pan( int channel, int data )
|
||||
{
|
||||
YM2612.CHANNEL[channel].PANVolumeL = panlawtable[data & 0x7F];
|
||||
YM2612.CHANNEL[channel].PANVolumeR = panlawtable[0x7F - (data & 0x7F)];
|
||||
}
|
||||
|
||||
void Ym2612_Emu::reset()
|
||||
{
|
||||
impl->reset();
|
||||
|
@ -910,6 +945,9 @@ void Ym2612_Impl::reset()
|
|||
ch.FMS = 0;
|
||||
ch.AMS = 0;
|
||||
|
||||
ch.PANVolumeL = 46340;
|
||||
ch.PANVolumeR = 46340;
|
||||
|
||||
for ( int j = 0 ;j < 4 ; j++ )
|
||||
{
|
||||
ch.S0_OUT [j] = 0;
|
||||
|
@ -959,6 +997,11 @@ void Ym2612_Emu::write1( int addr, int data )
|
|||
impl->write1( addr, data );
|
||||
}
|
||||
|
||||
void Ym2612_Emu::write_pan(int channel, int data)
|
||||
{
|
||||
impl->write_pan( channel, data );
|
||||
}
|
||||
|
||||
void Ym2612_Emu::mute_voices( int mask ) { impl->mute_mask = mask; }
|
||||
|
||||
static void update_envelope_( slot_t* sl )
|
||||
|
@ -1166,10 +1209,10 @@ void ym2612_update_chan<algo>::func( tables_t& g, channel_t& ch,
|
|||
in1 += (ch.SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1);
|
||||
in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1);
|
||||
in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1);
|
||||
|
||||
int t0 = buf [0] + (CH_OUTd & ch.LEFT);
|
||||
int t1 = buf [1] + (CH_OUTd & ch.RIGHT);
|
||||
|
||||
|
||||
int t0 = buf [0] + ((CH_OUTd * ch.PANVolumeL / 65535) & ch.LEFT);
|
||||
int t1 = buf [1] + ((CH_OUTd * ch.PANVolumeR / 65535) & ch.RIGHT);
|
||||
|
||||
update_envelope( ch.SLOT [0] );
|
||||
update_envelope( ch.SLOT [1] );
|
||||
update_envelope( ch.SLOT [2] );
|
||||
|
|
|
@ -29,6 +29,9 @@ public:
|
|||
// Write addr to register 2 then data to register 3
|
||||
void write1( int addr, int data );
|
||||
|
||||
// Write pan level channel data
|
||||
void write_pan( int channel, int data );
|
||||
|
||||
// Run and add pair_count samples into current output buffer contents
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "gens_opn2.h"
|
||||
#include <cstring>
|
||||
|
||||
|
@ -40,6 +60,11 @@ void GensOPN2::writeReg(uint32_t port, uint16_t addr, uint8_t data)
|
|||
}
|
||||
}
|
||||
|
||||
void GensOPN2::writePan(uint16_t chan, uint8_t data)
|
||||
{
|
||||
chip->write_pan(static_cast<int>(chan), static_cast<int>(data));
|
||||
}
|
||||
|
||||
void GensOPN2::nativeGenerateN(int16_t *output, size_t frames)
|
||||
{
|
||||
std::memset(output, 0, frames * sizeof(int16_t) * 2);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef GENS_OPN2_H
|
||||
#define GENS_OPN2_H
|
||||
|
||||
|
@ -15,6 +35,7 @@ public:
|
|||
void setRate(uint32_t rate, uint32_t clock) override;
|
||||
void reset() override;
|
||||
void writeReg(uint32_t port, uint16_t addr, uint8_t data) override;
|
||||
void writePan(uint16_t chan, uint8_t data) override;
|
||||
void nativePreGenerate() override {}
|
||||
void nativePostGenerate() override {}
|
||||
void nativeGenerateN(int16_t *output, size_t frames) override;
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h> /* for memset */
|
||||
#include <stddef.h> /* for NULL */
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include "mamedef.h"
|
||||
#include "mame_ym2612fm.h"
|
||||
|
@ -543,6 +544,33 @@ static FILE *sample[1];
|
|||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Pan law table
|
||||
*/
|
||||
|
||||
static const UINT16 panlawtable[] =
|
||||
{
|
||||
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
|
||||
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
|
||||
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
|
||||
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
|
||||
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
|
||||
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
|
||||
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
|
||||
50433, 49912, 49383, 48846, 48302, 47750, 47191,
|
||||
46340, /* Center left */
|
||||
46340, /* Center right */
|
||||
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
|
||||
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
|
||||
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
|
||||
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
|
||||
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
|
||||
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
|
||||
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
|
||||
4858, 4050, 3240, 2431, 1620, 810, 0
|
||||
};
|
||||
|
||||
|
||||
/* struct describing a single operator (SLOT) */
|
||||
typedef struct
|
||||
{
|
||||
|
@ -608,6 +636,9 @@ typedef struct
|
|||
UINT8 kcode; /* key code: */
|
||||
UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */
|
||||
UINT8 Muted;
|
||||
|
||||
INT32 pan_volume_l;
|
||||
INT32 pan_volume_r;
|
||||
} FM_CH;
|
||||
|
||||
|
||||
|
@ -2457,34 +2488,40 @@ void ym2612_generate_one_native(void *chip, FMSAMPLE buffer[])
|
|||
if (out_fm[5] > 8192) out_fm[5] = 8192;
|
||||
else if (out_fm[5] < -8192) out_fm[5] = -8192;
|
||||
|
||||
#define PANLAW_L(ch, chpan) (((out_fm[ch]>>0) * cch[ch].pan_volume_l / 65535) & OPN->pan[chpan]);
|
||||
#define PANLAW_R(ch, chpan) (((out_fm[ch]>>0) * cch[ch].pan_volume_r / 65535) & OPN->pan[chpan]);
|
||||
|
||||
/* 6-channels mixing */
|
||||
lt = ((out_fm[0]>>0) & OPN->pan[0]);
|
||||
rt = ((out_fm[0]>>0) & OPN->pan[1]);
|
||||
lt += ((out_fm[1]>>0) & OPN->pan[2]);
|
||||
rt += ((out_fm[1]>>0) & OPN->pan[3]);
|
||||
lt += ((out_fm[2]>>0) & OPN->pan[4]);
|
||||
rt += ((out_fm[2]>>0) & OPN->pan[5]);
|
||||
lt += ((out_fm[3]>>0) & OPN->pan[6]);
|
||||
rt += ((out_fm[3]>>0) & OPN->pan[7]);
|
||||
lt = PANLAW_L(0, 0);
|
||||
rt = PANLAW_R(0, 1);
|
||||
lt += PANLAW_L(1, 2);
|
||||
rt += PANLAW_R(1, 3);
|
||||
lt += PANLAW_L(2, 4);
|
||||
rt += PANLAW_R(2, 5);
|
||||
lt += PANLAW_L(3, 6);
|
||||
rt += PANLAW_R(3, 7);
|
||||
if (! F2612->dac_test)
|
||||
{
|
||||
lt += ((out_fm[4]>>0) & OPN->pan[8]);
|
||||
rt += ((out_fm[4]>>0) & OPN->pan[9]);
|
||||
lt += PANLAW_L(4, 8);
|
||||
rt += PANLAW_R(4, 9);
|
||||
}
|
||||
else
|
||||
{
|
||||
lt += dacout;
|
||||
lt += dacout;
|
||||
rt += dacout;
|
||||
}
|
||||
lt += ((out_fm[5]>>0) & OPN->pan[10]);
|
||||
rt += ((out_fm[5]>>0) & OPN->pan[11]);
|
||||
lt += PANLAW_L(5, 10);
|
||||
rt += PANLAW_R(5, 11);
|
||||
|
||||
#undef PANLAW_L
|
||||
#undef PANLAW_R
|
||||
|
||||
/* Limit( lt, MAXOUT, MINOUT ); */
|
||||
/* Limit( rt, MAXOUT, MINOUT ); */
|
||||
|
||||
#ifdef SAVE_SAMPLE
|
||||
SAVE_ALL_CHANNELS
|
||||
#endif
|
||||
#ifdef SAVE_SAMPLE
|
||||
SAVE_ALL_CHANNELS
|
||||
#endif
|
||||
|
||||
/* buffering */
|
||||
if (F2612->WaveOutMode & 0x01)
|
||||
|
@ -2587,6 +2624,7 @@ void * ym2612_init(void *param, int clock, int rate,
|
|||
FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler)
|
||||
{
|
||||
YM2612 *F2612;
|
||||
int i = 0;
|
||||
|
||||
if (clock <= 0 || rate <= 0)
|
||||
return NULL; /* Forbid zero clock and sample rate */
|
||||
|
@ -2624,6 +2662,13 @@ void * ym2612_init(void *param, int clock, int rate,
|
|||
F2612->WaveOutMode = 0x01;
|
||||
else
|
||||
F2612->WaveOutMode = 0x03;
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
F2612->CH[i].pan_volume_l = 46340;
|
||||
F2612->CH[i].pan_volume_r = 46340;
|
||||
}
|
||||
|
||||
/*hFile = fopen("YM2612.log", "wt");
|
||||
fprintf(hFile, "Clock: %d, Sample Rate: %d\n", clock, rate);
|
||||
fprintf(hFile, "Sample\tCh 0\tCh 1\tCh 2\tCh 3\tCh 4\tCh 5\n");
|
||||
|
@ -2786,6 +2831,14 @@ int ym2612_write(void *chip, int a, UINT8 v)
|
|||
return F2612->OPN.ST.irq;
|
||||
}
|
||||
|
||||
void ym2612_write_pan(void *chip, int c, unsigned char v)
|
||||
{
|
||||
YM2612 *F2612 = (YM2612 *)chip;
|
||||
assert((c >= 0) && (c < 6));
|
||||
F2612->CH[c].pan_volume_l = panlawtable[v & 0x7F];
|
||||
F2612->CH[c].pan_volume_r = panlawtable[0x7F - (v & 0x7F)];
|
||||
}
|
||||
|
||||
UINT8 ym2612_read(void *chip,int a)
|
||||
{
|
||||
YM2612 *F2612 = (YM2612 *)chip;
|
||||
|
|
|
@ -148,8 +148,9 @@ void ym2612_generate_one_native(void *chip, FMSAMPLE buffer[2]);
|
|||
|
||||
/* void ym2612_post_generate(void *chip, int length); */
|
||||
|
||||
int ym2612_write(void *chip, int a,unsigned char v);
|
||||
unsigned char ym2612_read(void *chip,int a);
|
||||
int ym2612_write(void *chip, int a, unsigned char v);
|
||||
void ym2612_write_pan(void *chip, int c, unsigned char v);
|
||||
unsigned char ym2612_read(void *chip, int a);
|
||||
int ym2612_timer_over(void *chip, int c );
|
||||
void ym2612_postload(void *chip);
|
||||
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "mame_opn2.h"
|
||||
#include "mame/mame_ym2612fm.h"
|
||||
#include <cstdlib>
|
||||
|
@ -36,6 +56,11 @@ void MameOPN2::writeReg(uint32_t port, uint16_t addr, uint8_t data)
|
|||
ym2612_write(chip, 1 + (int)(port) * 2, data);
|
||||
}
|
||||
|
||||
void MameOPN2::writePan(uint16_t chan, uint8_t data)
|
||||
{
|
||||
ym2612_write_pan(chip, (int)chan, data);
|
||||
}
|
||||
|
||||
void MameOPN2::nativePreGenerate()
|
||||
{
|
||||
void *chip = this->chip;
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef MAME_OPN2_H
|
||||
#define MAME_OPN2_H
|
||||
|
||||
|
@ -14,6 +34,7 @@ public:
|
|||
void setRate(uint32_t rate, uint32_t clock) override;
|
||||
void reset() override;
|
||||
void writeReg(uint32_t port, uint16_t addr, uint8_t data) override;
|
||||
void writePan(uint16_t chan, uint8_t data) override;
|
||||
void nativePreGenerate() override;
|
||||
void nativePostGenerate() override {}
|
||||
void nativeGenerate(int16_t *frame) override;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Alexey Khokholov (Nuke.YKT)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*
|
||||
* Nuked OPN2(Yamaha YM3438) emulator.
|
||||
|
@ -216,6 +216,32 @@ static const Bit32u fm_algorithm[4][6][8] = {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Pan law table
|
||||
*/
|
||||
|
||||
static const Bit16u panlawtable[] =
|
||||
{
|
||||
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
|
||||
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
|
||||
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
|
||||
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
|
||||
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
|
||||
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
|
||||
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
|
||||
50433, 49912, 49383, 48846, 48302, 47750, 47191,
|
||||
46340, /* Center left */
|
||||
46340, /* Center right */
|
||||
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
|
||||
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
|
||||
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
|
||||
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
|
||||
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
|
||||
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
|
||||
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
|
||||
4858, 4050, 3240, 2431, 1620, 810, 0
|
||||
};
|
||||
|
||||
static Bit32u chip_type = ym3438_type_discrete;
|
||||
|
||||
void OPN2_DoIO(ym3438_t *chip)
|
||||
|
@ -1204,6 +1230,8 @@ void OPN2_Reset(ym3438_t *chip, Bit32u rate, Bit32u clock)
|
|||
{
|
||||
chip->pan_l[i] = 1;
|
||||
chip->pan_r[i] = 1;
|
||||
chip->pan_volume_l[i] = 46340;
|
||||
chip->pan_volume_r[i] = 46340;
|
||||
}
|
||||
|
||||
if (rate != 0)
|
||||
|
@ -1426,6 +1454,13 @@ Bit8u OPN2_Read(ym3438_t *chip, Bit32u port)
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void OPN2_WritePan(ym3438_t *chip, Bit32u channel, Bit8u data)
|
||||
{
|
||||
chip->pan_volume_l[channel] = panlawtable[data & 0x7F];
|
||||
chip->pan_volume_r[channel] = panlawtable[0x7F - (data & 0x7F)];
|
||||
}
|
||||
|
||||
void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data)
|
||||
{
|
||||
Bit64u time1, time2;
|
||||
|
@ -1466,6 +1501,7 @@ void OPN2_Generate(ym3438_t *chip, Bit16s *buf)
|
|||
Bit32u i;
|
||||
Bit16s buffer[2];
|
||||
Bit32u mute;
|
||||
Bit32s channel = -1;
|
||||
|
||||
buf[0] = 0;
|
||||
buf[1] = 0;
|
||||
|
@ -1476,21 +1512,27 @@ void OPN2_Generate(ym3438_t *chip, Bit16s *buf)
|
|||
{
|
||||
case 0: /* Ch 2 */
|
||||
mute = chip->mute[1];
|
||||
channel = 1;
|
||||
break;
|
||||
case 1: /* Ch 6, DAC */
|
||||
mute = chip->mute[5 + chip->dacen];
|
||||
channel = 5;
|
||||
break;
|
||||
case 2: /* Ch 4 */
|
||||
mute = chip->mute[3];
|
||||
channel = 3;
|
||||
break;
|
||||
case 3: /* Ch 1 */
|
||||
mute = chip->mute[0];
|
||||
channel = 0;
|
||||
break;
|
||||
case 4: /* Ch 5 */
|
||||
mute = chip->mute[4];
|
||||
channel = 4;
|
||||
break;
|
||||
case 5: /* Ch 3 */
|
||||
mute = chip->mute[2];
|
||||
channel = 2;
|
||||
break;
|
||||
default:
|
||||
mute = 0;
|
||||
|
@ -1499,6 +1541,11 @@ void OPN2_Generate(ym3438_t *chip, Bit16s *buf)
|
|||
OPN2_Clock(chip, buffer);
|
||||
if (!mute)
|
||||
{
|
||||
if (channel >= 0)
|
||||
{
|
||||
buffer[0] = buffer[0] * chip->pan_volume_l[channel] / 65535;
|
||||
buffer[1] = buffer[1] * chip->pan_volume_r[channel] / 65535;
|
||||
}
|
||||
buf[0] += buffer[0];
|
||||
buf[1] += buffer[1];
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Alexey Khokholov (Nuke.YKT)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*
|
||||
* Nuked OPN2(Yamaha YM3438) emulator.
|
||||
|
@ -213,6 +213,9 @@ typedef struct
|
|||
Bit32s oldsamples[2];
|
||||
Bit32s samples[2];
|
||||
|
||||
Bit32u pan_volume_l[6];
|
||||
Bit32u pan_volume_r[6];
|
||||
|
||||
Bit64u writebuf_samplecnt;
|
||||
Bit32u writebuf_cur;
|
||||
Bit32u writebuf_last;
|
||||
|
@ -231,6 +234,7 @@ Bit32u OPN2_ReadIRQPin(ym3438_t *chip);
|
|||
Bit8u OPN2_Read(ym3438_t *chip, Bit32u port);
|
||||
|
||||
/*EXTRA*/
|
||||
void OPN2_WritePan(ym3438_t *chip, Bit32u channel, Bit8u data);
|
||||
void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data);
|
||||
void OPN2_Generate(ym3438_t *chip, Bit16s *buf);
|
||||
void OPN2_GenerateResampled(ym3438_t *chip, Bit16s *buf);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "nuked_opn2.h"
|
||||
#include "nuked/ym3438.h"
|
||||
#include <cstring>
|
||||
|
@ -37,6 +57,12 @@ void NukedOPN2::writeReg(uint32_t port, uint16_t addr, uint8_t data)
|
|||
//qDebug() << QString("%1: 0x%2 => 0x%3").arg(port).arg(addr, 2, 16, QChar('0')).arg(data, 2, 16, QChar('0'));
|
||||
}
|
||||
|
||||
void NukedOPN2::writePan(uint16_t chan, uint8_t data)
|
||||
{
|
||||
ym3438_t *chip_r = reinterpret_cast<ym3438_t*>(chip);
|
||||
OPN2_WritePan(chip_r, (Bit32u)chan, data);
|
||||
}
|
||||
|
||||
void NukedOPN2::nativeGenerate(int16_t *frame)
|
||||
{
|
||||
ym3438_t *chip_r = reinterpret_cast<ym3438_t*>(chip);
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef NUKED_OPN2_H
|
||||
#define NUKED_OPN2_H
|
||||
|
||||
|
@ -14,6 +34,7 @@ public:
|
|||
void setRate(uint32_t rate, uint32_t clock) override;
|
||||
void reset() override;
|
||||
void writeReg(uint32_t port, uint16_t addr, uint8_t data) override;
|
||||
void writePan(uint16_t chan, uint8_t data) override;
|
||||
void nativePreGenerate() override {}
|
||||
void nativePostGenerate() override {}
|
||||
void nativeGenerate(int16_t *frame) override;
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
|
||||
*
|
||||
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ONP_CHIP_BASE_H
|
||||
#define ONP_CHIP_BASE_H
|
||||
|
||||
|
@ -13,25 +33,40 @@
|
|||
class VResampler;
|
||||
#endif
|
||||
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
extern void opn2_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate);
|
||||
#endif
|
||||
|
||||
class OPNChipBase
|
||||
{
|
||||
public:
|
||||
enum { nativeRate = 53267 };
|
||||
protected:
|
||||
uint32_t m_id;
|
||||
uint32_t m_rate;
|
||||
uint32_t m_clock;
|
||||
public:
|
||||
OPNChipBase();
|
||||
virtual ~OPNChipBase();
|
||||
|
||||
uint32_t chipId() const { return m_id; }
|
||||
void setChipId(uint32_t id) { m_id = id; }
|
||||
|
||||
virtual bool canRunAtPcmRate() const = 0;
|
||||
virtual bool isRunningAtPcmRate() const = 0;
|
||||
virtual bool setRunningAtPcmRate(bool r) = 0;
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
virtual void setAudioTickHandlerInstance(void *instance) = 0;
|
||||
#endif
|
||||
|
||||
virtual void setRate(uint32_t rate, uint32_t clock) = 0;
|
||||
virtual uint32_t effectiveRate() const = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual void writeReg(uint32_t port, uint16_t addr, uint8_t data) = 0;
|
||||
|
||||
// extended
|
||||
virtual void writePan(uint16_t addr, uint8_t data) { (void)addr; (void)data; }
|
||||
|
||||
virtual void nativePreGenerate() = 0;
|
||||
virtual void nativePostGenerate() = 0;
|
||||
virtual void nativeGenerate(int16_t *frame) = 0;
|
||||
|
@ -58,8 +93,12 @@ public:
|
|||
|
||||
bool isRunningAtPcmRate() const override;
|
||||
bool setRunningAtPcmRate(bool r) override;
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
void setAudioTickHandlerInstance(void *instance);
|
||||
#endif
|
||||
|
||||
virtual void setRate(uint32_t rate, uint32_t clock) override;
|
||||
uint32_t effectiveRate() const override;
|
||||
virtual void reset() override;
|
||||
void generate(int16_t *output, size_t frames) override;
|
||||
void generateAndMix(int16_t *output, size_t frames) override;
|
||||
|
@ -67,6 +106,10 @@ public:
|
|||
void generateAndMix32(int32_t *output, size_t frames) override;
|
||||
private:
|
||||
bool m_runningAtPcmRate;
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
void *m_audioTickHandlerInstance;
|
||||
#endif
|
||||
void nativeTick(int16_t *frame);
|
||||
void setupResampler(uint32_t rate);
|
||||
void resetResampler();
|
||||
void resampledGenerate(int32_t *output);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
/* OPNChipBase */
|
||||
|
||||
inline OPNChipBase::OPNChipBase() :
|
||||
m_id(0),
|
||||
m_rate(44100),
|
||||
m_clock(7670454)
|
||||
{
|
||||
|
@ -35,6 +36,10 @@ template <class T>
|
|||
OPNChipBaseT<T>::OPNChipBaseT()
|
||||
: OPNChipBase(),
|
||||
m_runningAtPcmRate(false)
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
,
|
||||
m_audioTickHandlerInstance(NULL)
|
||||
#endif
|
||||
{
|
||||
#if defined(OPNMIDI_ENABLE_HQ_RESAMPLER)
|
||||
m_resampler = new VResampler;
|
||||
|
@ -69,6 +74,14 @@ bool OPNChipBaseT<T>::setRunningAtPcmRate(bool r)
|
|||
return true;
|
||||
}
|
||||
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
template <class T>
|
||||
void OPNChipBaseT<T>::setAudioTickHandlerInstance(void *instance)
|
||||
{
|
||||
m_audioTickHandlerInstance = instance;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
void OPNChipBaseT<T>::setRate(uint32_t rate, uint32_t clock)
|
||||
{
|
||||
|
@ -81,6 +94,12 @@ void OPNChipBaseT<T>::setRate(uint32_t rate, uint32_t clock)
|
|||
resetResampler();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
uint32_t OPNChipBaseT<T>::effectiveRate() const
|
||||
{
|
||||
return m_runningAtPcmRate ? m_rate : (uint32_t)nativeRate;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void OPNChipBaseT<T>::reset()
|
||||
{
|
||||
|
@ -152,6 +171,15 @@ void OPNChipBaseT<T>::generateAndMix32(int32_t *output, size_t frames)
|
|||
static_cast<T *>(this)->nativePostGenerate();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void OPNChipBaseT<T>::nativeTick(int16_t *frame)
|
||||
{
|
||||
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
|
||||
opn2_audioTickHandler(m_audioTickHandlerInstance, m_id, effectiveRate());
|
||||
#endif
|
||||
static_cast<T *>(this)->nativeGenerate(frame);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void OPNChipBaseT<T>::setupResampler(uint32_t rate)
|
||||
{
|
||||
|
@ -184,7 +212,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
|
|||
if(UNLIKELY(m_runningAtPcmRate))
|
||||
{
|
||||
int16_t in[2];
|
||||
static_cast<T *>(this)->nativeGenerate(in);
|
||||
static_cast<T *>(this)->nativeTick(in);
|
||||
output[0] = (int32_t)in[0] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
||||
output[1] = (int32_t)in[1] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
||||
return;
|
||||
|
@ -202,7 +230,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
|
|||
while(rsm->process(), rsm->out_count != 0)
|
||||
{
|
||||
int16_t in[2];
|
||||
static_cast<T *>(this)->nativeGenerate(in);
|
||||
static_cast<T *>(this)->nativeTick(in);
|
||||
f_in[0] = scale * (float)in[0];
|
||||
f_in[1] = scale * (float)in[1];
|
||||
rsm->inp_count = 1;
|
||||
|
@ -210,8 +238,8 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
|
|||
rsm->out_count = 1;
|
||||
rsm->out_data = f_out;
|
||||
}
|
||||
output[0] = std::lround(f_out[0]);
|
||||
output[1] = std::lround(f_out[1]);
|
||||
output[0] = static_cast<int32_t>(std::lround(f_out[0]));
|
||||
output[1] = static_cast<int32_t>(std::lround(f_out[1]));
|
||||
}
|
||||
#else
|
||||
template <class T>
|
||||
|
@ -220,7 +248,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
|
|||
if(UNLIKELY(m_runningAtPcmRate))
|
||||
{
|
||||
int16_t in[2];
|
||||
static_cast<T *>(this)->nativeGenerate(in);
|
||||
static_cast<T *>(this)->nativeTick(in);
|
||||
output[0] = (int32_t)in[0] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
||||
output[1] = (int32_t)in[1] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
|
||||
return;
|
||||
|
@ -233,7 +261,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
|
|||
m_oldsamples[0] = m_samples[0];
|
||||
m_oldsamples[1] = m_samples[1];
|
||||
int16_t buffer[2];
|
||||
static_cast<T *>(this)->nativeGenerate(buffer);
|
||||
static_cast<T *>(this)->nativeTick(buffer);
|
||||
m_samples[0] = buffer[0] * T::resamplerPreAmplify;
|
||||
m_samples[1] = buffer[1] * T::resamplerPreAmplify;
|
||||
samplecnt -= rateratio;
|
||||
|
|
300
src/sound/opnmidi/file_reader.hpp
Normal file
300
src/sound/opnmidi/file_reader.hpp
Normal file
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef FILE_AND_MEM_READER_HHHH
|
||||
#define FILE_AND_MEM_READER_HHHH
|
||||
|
||||
#include <string> // std::string
|
||||
#include <cstdio> // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof
|
||||
#include <stdint.h> // uint*_t
|
||||
#include <stddef.h> // size_t and friends
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX 1
|
||||
#include <cstring> // std::strlen
|
||||
#include <windows.h> // MultiByteToWideChar
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief A little class gives able to read filedata from disk and also from a memory segment
|
||||
*/
|
||||
class FileAndMemReader
|
||||
{
|
||||
//! Currently loaded filename (empty for a memory blocks)
|
||||
std::string m_file_name;
|
||||
//! File reader descriptor
|
||||
std::FILE *m_fp;
|
||||
|
||||
//! Memory pointer descriptor
|
||||
const void *m_mp;
|
||||
//! Size of memory block
|
||||
size_t m_mp_size;
|
||||
//! Cursor position in the memory block
|
||||
size_t m_mp_tell;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Relation direction
|
||||
*/
|
||||
enum relTo
|
||||
{
|
||||
//! At begin position
|
||||
SET = SEEK_SET,
|
||||
//! At current position
|
||||
CUR = SEEK_CUR,
|
||||
//! At end position
|
||||
END = SEEK_END
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief C.O.: It's a constructor!
|
||||
*/
|
||||
FileAndMemReader() :
|
||||
m_fp(NULL),
|
||||
m_mp(NULL),
|
||||
m_mp_size(0),
|
||||
m_mp_tell(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief C.O.: It's a destructor!
|
||||
*/
|
||||
~FileAndMemReader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open file from a disk
|
||||
* @param path Path to the file in UTF-8 (even on Windows!)
|
||||
*/
|
||||
void openFile(const char *path)
|
||||
{
|
||||
if(m_fp)
|
||||
this->close();//Close previously opened file first!
|
||||
#if !defined(_WIN32) || defined(__WATCOMC__)
|
||||
m_fp = std::fopen(path, "rb");
|
||||
#else
|
||||
wchar_t widePath[MAX_PATH];
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast<int>(std::strlen(path)), widePath, MAX_PATH);
|
||||
widePath[size] = '\0';
|
||||
m_fp = _wfopen(widePath, L"rb");
|
||||
#endif
|
||||
m_file_name = path;
|
||||
m_mp = NULL;
|
||||
m_mp_size = 0;
|
||||
m_mp_tell = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open file from memory block
|
||||
* @param mem Pointer to the memory block
|
||||
* @param lenght Size of given block
|
||||
*/
|
||||
void openData(const void *mem, size_t lenght)
|
||||
{
|
||||
if(m_fp)
|
||||
this->close();//Close previously opened file first!
|
||||
m_fp = NULL;
|
||||
m_mp = mem;
|
||||
m_mp_size = lenght;
|
||||
m_mp_tell = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Seek to given position
|
||||
* @param pos Offset or position
|
||||
* @param rel_to Relation (at begin, at current, or at end)
|
||||
*/
|
||||
void seek(long pos, int rel_to)
|
||||
{
|
||||
if(!this->isValid())
|
||||
return;
|
||||
|
||||
if(m_fp)//If a file
|
||||
{
|
||||
std::fseek(m_fp, pos, rel_to);
|
||||
}
|
||||
else//If a memory block
|
||||
{
|
||||
switch(rel_to)
|
||||
{
|
||||
case SET:
|
||||
m_mp_tell = static_cast<size_t>(pos);
|
||||
break;
|
||||
|
||||
case END:
|
||||
m_mp_tell = m_mp_size - static_cast<size_t>(pos);
|
||||
break;
|
||||
|
||||
case CUR:
|
||||
m_mp_tell = m_mp_tell + static_cast<size_t>(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_mp_tell > m_mp_size)
|
||||
m_mp_tell = m_mp_size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Seek to given position (unsigned integer 64 as relation. Negative values not supported)
|
||||
* @param pos Offset or position
|
||||
* @param rel_to Relation (at begin, at current, or at end)
|
||||
*/
|
||||
inline void seeku(uint64_t pos, int rel_to)
|
||||
{
|
||||
this->seek(static_cast<long>(pos), rel_to);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the buffer from a file
|
||||
* @param buf Pointer to the destination memory block
|
||||
* @param num Number of elements
|
||||
* @param size Size of one element
|
||||
* @return Size
|
||||
*/
|
||||
size_t read(void *buf, size_t num, size_t size)
|
||||
{
|
||||
if(!this->isValid())
|
||||
return 0;
|
||||
if(m_fp)
|
||||
return std::fread(buf, num, size, m_fp);
|
||||
else
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t maxSize = static_cast<size_t>(size * num);
|
||||
|
||||
while((pos < maxSize) && (m_mp_tell < m_mp_size))
|
||||
{
|
||||
reinterpret_cast<uint8_t *>(buf)[pos] = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
|
||||
m_mp_tell++;
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos / num;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get one byte and seek forward
|
||||
* @return Readed byte or EOF (a.k.a. -1)
|
||||
*/
|
||||
int getc()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return -1;
|
||||
if(m_fp)//If a file
|
||||
{
|
||||
return std::getc(m_fp);
|
||||
}
|
||||
else //If a memory block
|
||||
{
|
||||
if(m_mp_tell >= m_mp_size)
|
||||
return -1;
|
||||
int x = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
|
||||
m_mp_tell++;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns current offset of cursor in a file
|
||||
* @return Offset position
|
||||
*/
|
||||
size_t tell()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return 0;
|
||||
if(m_fp)//If a file
|
||||
return static_cast<size_t>(std::ftell(m_fp));
|
||||
else//If a memory block
|
||||
return m_mp_tell;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close the file
|
||||
*/
|
||||
void close()
|
||||
{
|
||||
if(m_fp)
|
||||
std::fclose(m_fp);
|
||||
|
||||
m_fp = NULL;
|
||||
m_mp = NULL;
|
||||
m_mp_size = 0;
|
||||
m_mp_tell = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Is file instance valid
|
||||
* @return true if vaild
|
||||
*/
|
||||
bool isValid()
|
||||
{
|
||||
return (m_fp) || (m_mp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Is End Of File?
|
||||
* @return true if end of file was reached
|
||||
*/
|
||||
bool eof()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return true;
|
||||
if(m_fp)
|
||||
return (std::feof(m_fp) != 0);
|
||||
else
|
||||
return m_mp_tell >= m_mp_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a current file name
|
||||
* @return File name of currently loaded file
|
||||
*/
|
||||
const std::string &fileName()
|
||||
{
|
||||
return m_file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve file size
|
||||
* @return Size of file in bytes
|
||||
*/
|
||||
size_t fileSize()
|
||||
{
|
||||
if(!this->isValid())
|
||||
return 0;
|
||||
if(!m_fp)
|
||||
return m_mp_size; //Size of memory block is well known
|
||||
size_t old_pos = this->tell();
|
||||
seek(0l, FileAndMemReader::END);
|
||||
size_t file_size = this->tell();
|
||||
seek(static_cast<long>(old_pos), FileAndMemReader::SET);
|
||||
return file_size;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* FILE_AND_MEM_READER_HHHH */
|
|
@ -1,214 +0,0 @@
|
|||
#ifndef bqw_fraction_h
|
||||
#define bqw_fraction_h
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
/* Fraction number handling.
|
||||
* Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/)
|
||||
*/
|
||||
|
||||
template<typename inttype=int>
|
||||
class fraction
|
||||
{
|
||||
inttype num1, num2;
|
||||
typedef fraction<inttype> self;
|
||||
void Optim();
|
||||
|
||||
#if 1
|
||||
inline void Debug(char, const self &) { }
|
||||
#else
|
||||
inline void Debug(char op, const self &b)
|
||||
{
|
||||
cerr << nom() << '/' << denom() << ' ' << op
|
||||
<< ' ' << b.nom() << '/' << denom()
|
||||
<< ":\n";
|
||||
}
|
||||
#endif
|
||||
public:
|
||||
void set(inttype n, inttype d) { num1=n; num2=d; Optim(); }
|
||||
|
||||
fraction() : num1(0), num2(1) { }
|
||||
fraction(inttype value) : num1(value), num2(1) { }
|
||||
fraction(inttype n, inttype d) : num1(n), num2(d) { }
|
||||
fraction(int value) : num1(value), num2(1) { }
|
||||
template<typename floattype>
|
||||
explicit fraction(const floattype value) { operator= (value); }
|
||||
inline double value() const {return nom() / (double)denom(); }
|
||||
inline long double valuel() const {return nom() / (long double)denom(); }
|
||||
self &operator+= (const inttype &value) { num1+=value*denom(); Optim(); return *this; }
|
||||
self &operator-= (const inttype &value) { num1-=value*denom(); Optim(); return *this; }
|
||||
self &operator*= (const inttype &value) { num1*=value; Optim(); return *this; }
|
||||
self &operator/= (const inttype &value) { num2*=value; Optim(); return *this; }
|
||||
self &operator+= (const self &b);
|
||||
self &operator-= (const self &b);
|
||||
self &operator*= (const self &b) { Debug('*',b);num1*=b.nom(); num2*=b.denom(); Optim(); return *this; }
|
||||
self &operator/= (const self &b) { Debug('/',b);num1*=b.denom(); num2*=b.nom(); Optim(); return *this; }
|
||||
self operator- () const { return self(-num1, num2); }
|
||||
|
||||
#define fraction_blah_func(op1, op2) \
|
||||
self operator op1 (const self &b) const { self tmp(*this); tmp op2 b; return tmp; }
|
||||
|
||||
fraction_blah_func( +, += )
|
||||
fraction_blah_func( -, -= )
|
||||
fraction_blah_func( /, /= )
|
||||
fraction_blah_func( *, *= )
|
||||
|
||||
#undef fraction_blah_func
|
||||
#define fraction_blah_func(op) \
|
||||
bool operator op(const self &b) const { return value() op b.value(); } \
|
||||
bool operator op(inttype b) const { return value() op b; }
|
||||
|
||||
fraction_blah_func( < )
|
||||
fraction_blah_func( > )
|
||||
fraction_blah_func( <= )
|
||||
fraction_blah_func( >= )
|
||||
|
||||
#undef fraction_blah_func
|
||||
|
||||
const inttype &nom() const { return num1; }
|
||||
const inttype &denom() const { return num2; }
|
||||
inline bool operator == (inttype b) const { return denom() == 1 && nom() == b; }
|
||||
inline bool operator != (inttype b) const { return denom() != 1 || nom() != b; }
|
||||
inline bool operator == (const self &b) const { return denom()==b.denom() && nom()==b.nom(); }
|
||||
inline bool operator != (const self &b) const { return denom()!=b.denom() || nom()!=b.nom(); }
|
||||
//operator bool () const { return nom() != 0; }
|
||||
inline bool negative() const { return (nom() < 0) ^ (denom() < 0); }
|
||||
|
||||
self &operator= (const inttype value) { num2=1; num1=value; return *this; }
|
||||
//self &operator= (int value) { num2=1; num1=value; return *this; }
|
||||
|
||||
self &operator= (double orig) { return *this = (long double)orig; }
|
||||
self &operator= (long double orig);
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable:4146)
|
||||
#endif
|
||||
|
||||
template<typename inttype>
|
||||
void fraction<inttype>::Optim()
|
||||
{
|
||||
/* Euclidean algorithm */
|
||||
inttype n1, n2, nn1, nn2;
|
||||
|
||||
nn1 = std::numeric_limits<inttype>::is_signed ? (num1 >= 0 ? num1 : -num1) : num1;
|
||||
nn2 = std::numeric_limits<inttype>::is_signed ? (num2 >= 0 ? num2 : -num2) : num2;
|
||||
|
||||
if(nn1 < nn2)
|
||||
n1 = num1, n2 = num2;
|
||||
else
|
||||
n1 = num2, n2 = num1;
|
||||
|
||||
if(!num1) { num2 = 1; return; }
|
||||
for(;;)
|
||||
{
|
||||
//fprintf(stderr, "%d/%d: n1=%d,n2=%d\n", nom(),denom(),n1,n2);
|
||||
inttype tmp = n2 % n1;
|
||||
if(!tmp)break;
|
||||
n2 = n1;
|
||||
n1 = tmp;
|
||||
}
|
||||
num1 /= n1;
|
||||
num2 /= n1;
|
||||
//fprintf(stderr, "result: %d/%d\n\n", nom(), denom());
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(default:4146)
|
||||
#endif
|
||||
|
||||
template<typename inttype>
|
||||
inline const fraction<inttype> abs(const fraction<inttype> &f)
|
||||
{
|
||||
return fraction<inttype>(abs(f.nom()), abs(f.denom()));
|
||||
}
|
||||
|
||||
#define fraction_blah_func(op) \
|
||||
template<typename inttype> \
|
||||
fraction<inttype> operator op \
|
||||
(const inttype bla, const fraction<inttype> &b) \
|
||||
{ return fraction<inttype> (bla) op b; }
|
||||
fraction_blah_func( + )
|
||||
fraction_blah_func( - )
|
||||
fraction_blah_func( * )
|
||||
fraction_blah_func( / )
|
||||
#undef fraction_blah_func
|
||||
|
||||
#define fraction_blah_func(op1, op2) \
|
||||
template<typename inttype> \
|
||||
fraction<inttype> &fraction<inttype>::operator op2 (const fraction<inttype> &b) \
|
||||
{ \
|
||||
inttype newnom = nom()*b.denom() op1 denom()*b.nom(); \
|
||||
num2 *= b.denom(); \
|
||||
num1 = newnom; \
|
||||
Optim(); \
|
||||
return *this; \
|
||||
}
|
||||
fraction_blah_func( +, += )
|
||||
fraction_blah_func( -, -= )
|
||||
#undef fraction_blah_func
|
||||
|
||||
template<typename inttype>
|
||||
fraction<inttype> &fraction<inttype>::operator= (long double orig)
|
||||
{
|
||||
if(orig == 0.0)
|
||||
{
|
||||
set(0, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inttype cf[25];
|
||||
for(int maxdepth=1; maxdepth<25; ++maxdepth)
|
||||
{
|
||||
inttype u,v;
|
||||
long double virhe, a=orig;
|
||||
int i, viim;
|
||||
|
||||
for(i = 0; i < maxdepth; ++i)
|
||||
{
|
||||
cf[i] = (inttype)a;
|
||||
if(cf[i]-1 > cf[i])break;
|
||||
a = 1.0 / (a - cf[i]);
|
||||
}
|
||||
|
||||
for(viim=i-1; i < maxdepth; ++i)
|
||||
cf[i] = 0;
|
||||
|
||||
u = cf[viim];
|
||||
v = 1;
|
||||
for(i = viim-1; i >= 0; --i)
|
||||
{
|
||||
inttype w = cf[i] * u + v;
|
||||
v = u;
|
||||
u = w;
|
||||
}
|
||||
|
||||
virhe = (orig - (u / (long double)v)) / orig;
|
||||
|
||||
set(u, v);
|
||||
//if(verbose > 4)
|
||||
// cerr << "Guess: " << *this << " - error = " << virhe*100 << "%\n";
|
||||
|
||||
if(virhe < 1e-8 && virhe > -1e-8)break;
|
||||
}
|
||||
|
||||
//if(verbose > 4)
|
||||
//{
|
||||
// cerr << "Fraction=" << orig << ": " << *this << endl;
|
||||
//}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
template<typename inttype>
|
||||
ostream &operator << (ostream &dest, const fraction<inttype> &m)
|
||||
{
|
||||
if(m.denom() == (inttype)1) return dest << m.nom();
|
||||
return dest << m.nom() << '/' << m.denom();
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
|
@ -110,6 +110,7 @@ struct opnInstMeta2
|
|||
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
|
||||
uint16_t ms_sound_koff;
|
||||
double fine_tune;
|
||||
int8_t midi_velocity_offset;
|
||||
#if 0
|
||||
opnInstMeta2() {}
|
||||
explicit opnInstMeta2(const opnInstMeta &d);
|
||||
|
@ -120,6 +121,16 @@ OPNDATA_BYTE_COMPARABLE(struct opnInstMeta2)
|
|||
#undef OPNDATA_BYTE_COMPARABLE
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* @brief Bank global setup
|
||||
*/
|
||||
struct OpnBankSetup
|
||||
{
|
||||
int volumeModel;
|
||||
int lfoEnable;
|
||||
int lfoFrequency;
|
||||
};
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* @brief Conversion of storage formats
|
||||
|
@ -127,11 +138,21 @@ OPNDATA_BYTE_COMPARABLE(struct opnInstMeta2)
|
|||
inline opnInstMeta2::opnInstMeta2(const opnInstMeta &d)
|
||||
: tone(d.tone), flags(d.flags),
|
||||
ms_sound_kon(d.ms_sound_kon), ms_sound_koff(d.ms_sound_koff),
|
||||
fine_tune(d.fine_tune)
|
||||
fine_tune(d.fine_tune), midi_velocity_offset(d.midi_velocity_offset)
|
||||
{
|
||||
opn[0] = ::opn[d.opnno1];
|
||||
opn[1] = ::opn[d.opnno2];
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert external instrument to internal instrument
|
||||
*/
|
||||
void cvt_OPNI_to_FMIns(opnInstMeta2 &dst, const struct OPN2_Instrument &src);
|
||||
|
||||
/**
|
||||
* @brief Convert internal instrument to external instrument
|
||||
*/
|
||||
void cvt_FMIns_to_OPNI(struct OPN2_Instrument &dst, const opnInstMeta2 &src);
|
||||
|
||||
#endif // OPNMIDI_OPNBANK_H
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -40,7 +40,7 @@ template <class T>
|
|||
class BasicBankMap
|
||||
{
|
||||
public:
|
||||
typedef uint16_t key_type; /* the bank identifier */
|
||||
typedef size_t key_type; /* the bank identifier */
|
||||
typedef T mapped_type;
|
||||
typedef std::pair<key_type, T> value_type;
|
||||
|
||||
|
|
82
src/sound/opnmidi/opnmidi_cvt.hpp
Normal file
82
src/sound/opnmidi/opnmidi_cvt.hpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
|
||||
*
|
||||
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "opnbank.h"
|
||||
|
||||
template <class WOPNI>
|
||||
static void cvt_generic_to_FMIns(opnInstMeta2 &ins, const WOPNI &in)
|
||||
{
|
||||
ins.tone = in.percussion_key_number;
|
||||
ins.flags = in.inst_flags;
|
||||
/* Junk, delete later */
|
||||
ins.fine_tune = 0.0;
|
||||
/* Junk, delete later */
|
||||
|
||||
ins.opn[0].fbalg = in.fbalg;
|
||||
ins.opn[0].lfosens = in.lfosens;
|
||||
ins.opn[0].finetune = in.note_offset;
|
||||
ins.midi_velocity_offset = in.midi_velocity_offset;
|
||||
|
||||
for(size_t op = 0; op < 4; op++)
|
||||
{
|
||||
ins.opn[0].OPS[op].data[0] = in.operators[op].dtfm_30;
|
||||
ins.opn[0].OPS[op].data[1] = in.operators[op].level_40;
|
||||
ins.opn[0].OPS[op].data[2] = in.operators[op].rsatk_50;
|
||||
ins.opn[0].OPS[op].data[3] = in.operators[op].amdecay1_60;
|
||||
ins.opn[0].OPS[op].data[4] = in.operators[op].decay2_70;
|
||||
ins.opn[0].OPS[op].data[5] = in.operators[op].susrel_80;
|
||||
ins.opn[0].OPS[op].data[6] = in.operators[op].ssgeg_90;
|
||||
}
|
||||
|
||||
ins.opn[1] = ins.opn[0];
|
||||
|
||||
ins.ms_sound_kon = in.delay_on_ms;
|
||||
ins.ms_sound_koff = in.delay_off_ms;
|
||||
}
|
||||
|
||||
template <class WOPNI>
|
||||
static void cvt_FMIns_to_generic(WOPNI &ins, const opnInstMeta2 &in)
|
||||
{
|
||||
ins.percussion_key_number = in.tone;
|
||||
ins.inst_flags = in.flags;
|
||||
|
||||
ins.fbalg = in.opn[0].fbalg;
|
||||
ins.lfosens = in.opn[0].lfosens;
|
||||
ins.note_offset = in.opn[0].finetune;
|
||||
|
||||
ins.midi_velocity_offset = in.midi_velocity_offset;
|
||||
|
||||
for(size_t op = 0; op < 4; op++)
|
||||
{
|
||||
ins.operators[op].dtfm_30 = in.opn[0].OPS[op].data[0];
|
||||
ins.operators[op].level_40 = in.opn[0].OPS[op].data[1];
|
||||
ins.operators[op].rsatk_50 = in.opn[0].OPS[op].data[2];
|
||||
ins.operators[op].amdecay1_60 = in.opn[0].OPS[op].data[3];
|
||||
ins.operators[op].decay2_70 = in.opn[0].OPS[op].data[4];
|
||||
ins.operators[op].susrel_80 = in.opn[0].OPS[op].data[5];
|
||||
ins.operators[op].ssgeg_90 = in.opn[0].OPS[op].data[6];
|
||||
}
|
||||
|
||||
ins.delay_on_ms = in.ms_sound_kon;
|
||||
ins.delay_off_ms = in.ms_sound_koff;
|
||||
}
|
|
@ -22,542 +22,203 @@
|
|||
*/
|
||||
|
||||
#include "opnmidi_private.hpp"
|
||||
|
||||
#ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
|
||||
# ifndef OPNMIDI_DISABLE_MUS_SUPPORT
|
||||
# include "opnmidi_mus2mid.h"
|
||||
# endif
|
||||
# ifndef OPNMIDI_DISABLE_XMI_SUPPORT
|
||||
# include "opnmidi_xmi2mid.h"
|
||||
# endif
|
||||
#endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
|
||||
|
||||
uint64_t OPNMIDIplay::ReadBEint(const void *buffer, size_t nbytes)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
|
||||
|
||||
for(unsigned n = 0; n < nbytes; ++n)
|
||||
result = (result << 8) + data[n];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t OPNMIDIplay::ReadLEint(const void *buffer, size_t nbytes)
|
||||
{
|
||||
uint64_t result = 0;
|
||||
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
|
||||
|
||||
for(unsigned n = 0; n < nbytes; ++n)
|
||||
result = result + static_cast<uint64_t>(data[n] << (n * 8));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//uint64_t OPNMIDIplay::ReadVarLenEx(size_t tk, bool &ok)
|
||||
//{
|
||||
// uint64_t result = 0;
|
||||
// ok = false;
|
||||
|
||||
// for(;;)
|
||||
// {
|
||||
// if(tk >= TrackData.size())
|
||||
// return 1;
|
||||
|
||||
// if(tk >= CurrentPosition.track.size())
|
||||
// return 2;
|
||||
|
||||
// size_t ptr = CurrentPosition.track[tk].ptr;
|
||||
|
||||
// if(ptr >= TrackData[tk].size())
|
||||
// return 3;
|
||||
|
||||
// unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++];
|
||||
// result = (result << 7) + (byte & 0x7F);
|
||||
|
||||
// if(!(byte & 0x80)) break;
|
||||
// }
|
||||
|
||||
// ok = true;
|
||||
// return result;
|
||||
//}
|
||||
#include "opnmidi_cvt.hpp"
|
||||
#include "wopn/wopn_file.h"
|
||||
|
||||
bool OPNMIDIplay::LoadBank(const std::string &filename)
|
||||
{
|
||||
fileReader file;
|
||||
FileAndMemReader file;
|
||||
file.openFile(filename.c_str());
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
bool OPNMIDIplay::LoadBank(const void *data, size_t size)
|
||||
{
|
||||
fileReader file;
|
||||
FileAndMemReader file;
|
||||
file.openData(data, (size_t)size);
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
size_t readU16BE(OPNMIDIplay::fileReader &fr, uint16_t &out)
|
||||
void cvt_OPNI_to_FMIns(opnInstMeta2 &ins, const OPN2_Instrument &in)
|
||||
{
|
||||
uint8_t arr[2];
|
||||
size_t ret = fr.read(arr, 1, 2);
|
||||
out = arr[1];
|
||||
out |= ((arr[0] << 8) & 0xFF00);
|
||||
return ret;
|
||||
return cvt_generic_to_FMIns(ins, in);
|
||||
}
|
||||
|
||||
size_t readS16BE(OPNMIDIplay::fileReader &fr, int16_t &out)
|
||||
void cvt_FMIns_to_OPNI(OPN2_Instrument &ins, const opnInstMeta2 &in)
|
||||
{
|
||||
uint8_t arr[2];
|
||||
size_t ret = fr.read(arr, 1, 2);
|
||||
out = *reinterpret_cast<signed char *>(&arr[0]);
|
||||
out *= 1 << 8;
|
||||
out |= arr[1];
|
||||
return ret;
|
||||
cvt_FMIns_to_generic(ins, in);
|
||||
}
|
||||
|
||||
int16_t toSint16BE(uint8_t *arr)
|
||||
{
|
||||
int16_t num = *reinterpret_cast<const int8_t *>(&arr[0]);
|
||||
num *= 1 << 8;
|
||||
num |= arr[1];
|
||||
return num;
|
||||
}
|
||||
|
||||
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 const char *wopn2_magic1 = "WOPN2-BANK\0";
|
||||
static const char *wopn2_magic2 = "WOPN2-B2NK\0";
|
||||
|
||||
#define WOPL_INST_SIZE_V1 65
|
||||
#define WOPL_INST_SIZE_V2 69
|
||||
|
||||
static const uint16_t latest_version = 2;
|
||||
|
||||
bool OPNMIDIplay::LoadBank(OPNMIDIplay::fileReader &fr)
|
||||
bool OPNMIDIplay::LoadBank(FileAndMemReader &fr)
|
||||
{
|
||||
int err = 0;
|
||||
WOPNFile *wopn = NULL;
|
||||
char *raw_file_data = NULL;
|
||||
size_t fsize;
|
||||
ADL_UNUSED(fsize);
|
||||
if(!fr.isValid())
|
||||
{
|
||||
errorStringOut = "Can't load bank file: Invalid data stream!";
|
||||
errorStringOut = "Custom bank: Invalid data stream!";
|
||||
return false;
|
||||
}
|
||||
|
||||
char magic[32];
|
||||
std::memset(magic, 0, 32);
|
||||
uint16_t version = 1;
|
||||
|
||||
uint16_t count_melodic_banks = 1;
|
||||
uint16_t count_percussive_banks = 1;
|
||||
|
||||
if(fr.read(magic, 1, 11) != 11)
|
||||
// Read complete bank file into the memory
|
||||
fsize = fr.fileSize();
|
||||
fr.seek(0, FileAndMemReader::SET);
|
||||
// Allocate necessary memory block
|
||||
raw_file_data = (char*)malloc(fsize);
|
||||
if(!raw_file_data)
|
||||
{
|
||||
errorStringOut = "Can't load bank file: Can't read magic number!";
|
||||
errorStringOut = "Custom bank: Out of memory before of read!";
|
||||
return false;
|
||||
}
|
||||
fr.read(raw_file_data, 1, fsize);
|
||||
|
||||
bool is1 = std::strncmp(magic, wopn2_magic1, 11) == 0;
|
||||
bool is2 = std::strncmp(magic, wopn2_magic2, 11) == 0;
|
||||
// Parse bank file from the memory
|
||||
wopn = WOPN_LoadBankFromMem((void*)raw_file_data, fsize, &err);
|
||||
//Free the buffer no more needed
|
||||
free(raw_file_data);
|
||||
|
||||
if(!is1 && !is2)
|
||||
// Check for any erros
|
||||
if(!wopn)
|
||||
{
|
||||
errorStringOut = "Can't load bank file: Invalid magic number!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(is2)
|
||||
{
|
||||
uint8_t ver[2];
|
||||
if(fr.read(ver, 1, 2) != 2)
|
||||
switch(err)
|
||||
{
|
||||
errorStringOut = "Can't load bank file: Can't read version number!";
|
||||
case WOPN_ERR_BAD_MAGIC:
|
||||
errorStringOut = "Custom bank: Invalid magic!";
|
||||
return false;
|
||||
}
|
||||
version = toUint16LE(ver);
|
||||
if(version < 2 || version > latest_version)
|
||||
{
|
||||
errorStringOut = "Can't load bank file: unsupported WOPN version!";
|
||||
case WOPN_ERR_UNEXPECTED_ENDING:
|
||||
errorStringOut = "Custom bank: Unexpected ending!";
|
||||
return false;
|
||||
case WOPN_ERR_INVALID_BANKS_COUNT:
|
||||
errorStringOut = "Custom bank: Invalid banks count!";
|
||||
return false;
|
||||
case WOPN_ERR_NEWER_VERSION:
|
||||
errorStringOut = "Custom bank: Version is newer than supported by this library!";
|
||||
return false;
|
||||
case WOPN_ERR_OUT_OF_MEMORY:
|
||||
errorStringOut = "Custom bank: Out of memory!";
|
||||
return false;
|
||||
default:
|
||||
errorStringOut = "Custom bank: Unknown error!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
opn.cleanInstrumentBanks();
|
||||
if((readU16BE(fr, count_melodic_banks) != 2) || (readU16BE(fr, count_percussive_banks) != 2))
|
||||
m_synth.m_insBankSetup.volumeModel = wopn->volume_model;
|
||||
m_synth.m_insBankSetup.lfoEnable = (wopn->lfo_freq & 8) != 0;
|
||||
m_synth.m_insBankSetup.lfoFrequency = wopn->lfo_freq & 7;
|
||||
m_setup.VolumeModel = OPNMIDI_VolumeModel_AUTO;
|
||||
m_setup.lfoEnable = -1;
|
||||
m_setup.lfoFrequency = -1;
|
||||
|
||||
m_synth.m_insBanks.clear();
|
||||
|
||||
uint16_t slots_counts[2] = {wopn->banks_count_melodic, wopn->banks_count_percussion};
|
||||
WOPNBank *slots_src_ins[2] = { wopn->banks_melodic, wopn->banks_percussive };
|
||||
|
||||
for(size_t ss = 0; ss < 2; ss++)
|
||||
{
|
||||
errorStringOut = "Can't load bank file: Can't read count of banks!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if((count_melodic_banks < 1) || (count_percussive_banks < 1))
|
||||
{
|
||||
errorStringOut = "Custom bank: Too few banks in this file!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(fr.read(&opn.regLFO, 1, 1) != 1)
|
||||
{
|
||||
errorStringOut = "Can't load bank file: Can't read LFO registry state!";
|
||||
return false;
|
||||
}
|
||||
|
||||
opn.cleanInstrumentBanks();
|
||||
|
||||
std::vector<OPN2::Bank *> banks;
|
||||
banks.reserve(count_melodic_banks + count_percussive_banks);
|
||||
|
||||
if(version >= 2)//Read bank meta-entries
|
||||
{
|
||||
for(uint16_t i = 0; i < count_melodic_banks; i++)
|
||||
for(size_t i = 0; i < slots_counts[ss]; i++)
|
||||
{
|
||||
uint8_t bank_meta[34];
|
||||
if(fr.read(bank_meta, 1, 34) != 34)
|
||||
size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) +
|
||||
(slots_src_ins[ss][i].bank_midi_lsb) +
|
||||
(ss ? size_t(OPN2::PercussionTag) : 0);
|
||||
OPN2::Bank &bank = m_synth.m_insBanks[bankno];
|
||||
for(int j = 0; j < 128; j++)
|
||||
{
|
||||
opn.cleanInstrumentBanks();
|
||||
errorStringOut = "Custom bank: Fail to read melodic bank meta-data!";
|
||||
return false;
|
||||
opnInstMeta2 &ins = bank.ins[j];
|
||||
std::memset(&ins, 0, sizeof(opnInstMeta2));
|
||||
WOPNInstrument &inIns = slots_src_ins[ss][i].ins[j];
|
||||
cvt_generic_to_FMIns(ins, inIns);
|
||||
}
|
||||
uint16_t bankno = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
|
||||
OPN2::Bank &bank = opn.dynamic_banks[bankno];
|
||||
//strncpy(bank.name, char_p(bank_meta), 32);
|
||||
banks.push_back(&bank);
|
||||
}
|
||||
|
||||
for(uint16_t i = 0; i < count_percussive_banks; i++)
|
||||
{
|
||||
uint8_t bank_meta[34];
|
||||
if(fr.read(bank_meta, 1, 34) != 34)
|
||||
{
|
||||
opn.cleanInstrumentBanks();
|
||||
errorStringOut = "Custom bank: Fail to read percussion bank meta-data!";
|
||||
return false;
|
||||
}
|
||||
uint16_t bankno = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]) + OPN2::PercussionTag;
|
||||
OPN2::Bank &bank = opn.dynamic_banks[bankno];
|
||||
//strncpy(bank.name, char_p(bank_meta), 32);
|
||||
banks.push_back(&bank);
|
||||
}
|
||||
}
|
||||
|
||||
size_t total = 128 * opn.dynamic_banks.size();
|
||||
|
||||
for(size_t i = 0; i < total; i++)
|
||||
{
|
||||
opnInstMeta2 &meta = banks[i / 128]->ins[i % 128];
|
||||
opnInstData &data = meta.opn[0];
|
||||
uint8_t idata[WOPL_INST_SIZE_V2];
|
||||
|
||||
size_t readSize = version >= 2 ? WOPL_INST_SIZE_V2 : WOPL_INST_SIZE_V1;
|
||||
if(fr.read(idata, 1, readSize) != readSize)
|
||||
{
|
||||
opn.cleanInstrumentBanks();
|
||||
errorStringOut = "Can't load bank file: Failed to read instrument data";
|
||||
return false;
|
||||
}
|
||||
data.finetune = toSint16BE(idata + 32);
|
||||
//Percussion instrument note number or a "fixed note sound"
|
||||
meta.tone = idata[34];
|
||||
data.fbalg = idata[35];
|
||||
data.lfosens = idata[36];
|
||||
for(size_t op = 0; op < 4; op++)
|
||||
{
|
||||
size_t off = 37 + op * 7;
|
||||
std::memcpy(data.OPS[op].data, idata + off, 7);
|
||||
}
|
||||
|
||||
meta.flags = 0;
|
||||
if(version >= 2)
|
||||
{
|
||||
meta.ms_sound_kon = toUint16BE(idata + 65);
|
||||
meta.ms_sound_koff = toUint16BE(idata + 67);
|
||||
if((meta.ms_sound_kon == 0) && (meta.ms_sound_koff == 0))
|
||||
meta.flags |= opnInstMeta::Flag_NoSound;
|
||||
}
|
||||
else
|
||||
{
|
||||
meta.ms_sound_kon = 1000;
|
||||
meta.ms_sound_koff = 500;
|
||||
}
|
||||
|
||||
meta.opn[1] = meta.opn[0];
|
||||
|
||||
/* Junk, delete later */
|
||||
meta.fine_tune = 0.0;
|
||||
/* Junk, delete later */
|
||||
}
|
||||
|
||||
applySetup();
|
||||
|
||||
WOPN_Free(wopn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
|
||||
|
||||
bool OPNMIDIplay::LoadMIDI_pre()
|
||||
{
|
||||
if(m_synth.m_insBanks.empty())
|
||||
{
|
||||
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
|
||||
return false;
|
||||
}
|
||||
|
||||
/**** Set all properties BEFORE starting of actial file reading! ****/
|
||||
resetMIDI();
|
||||
applySetup();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OPNMIDIplay::LoadMIDI_post()
|
||||
{
|
||||
MidiSequencer::FileFormat format = m_sequencer.getFormat();
|
||||
if(format == MidiSequencer::Format_CMF)
|
||||
{
|
||||
errorStringOut = "OPNMIDI doesn't supports CMF, use ADLMIDI to play this file!";
|
||||
/* As joke, why not to try implemented the converter of patches from OPL3 into OPN2? */
|
||||
return false;
|
||||
}
|
||||
else if(format == MidiSequencer::Format_RSXX)
|
||||
{
|
||||
m_synth.m_musicMode = OPN2::MODE_RSXX;
|
||||
m_synth.m_volumeScale = OPN2::VOLUME_Generic;
|
||||
m_synth.m_numChips = 2;
|
||||
}
|
||||
else if(format == MidiSequencer::Format_IMF)
|
||||
{
|
||||
errorStringOut = "OPNMIDI doesn't supports IMF, use ADLMIDI to play this file!";
|
||||
/* Same as for CMF */
|
||||
return false;
|
||||
}
|
||||
|
||||
m_setup.tick_skip_samples_delay = 0;
|
||||
m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPN2 chip
|
||||
m_chipChannels.clear();
|
||||
m_chipChannels.resize(m_synth.m_numChannels);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool OPNMIDIplay::LoadMIDI(const std::string &filename)
|
||||
{
|
||||
fileReader file;
|
||||
FileAndMemReader file;
|
||||
file.openFile(filename.c_str());
|
||||
if(!LoadMIDI(file))
|
||||
if(!LoadMIDI_pre())
|
||||
return false;
|
||||
if(!m_sequencer.loadMIDI(file))
|
||||
{
|
||||
errorStringOut = m_sequencer.getErrorString();
|
||||
return false;
|
||||
}
|
||||
if(!LoadMIDI_post())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OPNMIDIplay::LoadMIDI(const void *data, size_t size)
|
||||
{
|
||||
fileReader file;
|
||||
FileAndMemReader file;
|
||||
file.openData(data, size);
|
||||
return LoadMIDI(file);
|
||||
}
|
||||
|
||||
bool OPNMIDIplay::LoadMIDI(OPNMIDIplay::fileReader &fr)
|
||||
{
|
||||
size_t fsize;
|
||||
ADL_UNUSED(fsize);
|
||||
//! Temp buffer for conversion
|
||||
AdlMIDI_CPtr<uint8_t> cvt_buf;
|
||||
errorString.clear();
|
||||
|
||||
if(opn.dynamic_banks.empty())
|
||||
if(!LoadMIDI_pre())
|
||||
return false;
|
||||
if(!m_sequencer.loadMIDI(file))
|
||||
{
|
||||
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
|
||||
errorStringOut = m_sequencer.getErrorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!fr.isValid())
|
||||
{
|
||||
errorStringOut = "Invalid data stream!\n";
|
||||
#ifndef _WIN32
|
||||
errorStringOut += std::strerror(errno);
|
||||
#endif
|
||||
if(!LoadMIDI_post())
|
||||
return false;
|
||||
}
|
||||
|
||||
/**** Set all properties BEFORE starting of actial file reading! ****/
|
||||
applySetup();
|
||||
|
||||
atEnd = false;
|
||||
loopStart = true;
|
||||
invalidLoop = false;
|
||||
|
||||
bool is_GMF = false; // GMD/MUS files (ScummVM)
|
||||
bool is_RSXX = false; // RSXX, such as Cartooners
|
||||
|
||||
const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14
|
||||
char HeaderBuf[HeaderSize] = "";
|
||||
size_t DeltaTicks = 192, TrackCount = 1;
|
||||
|
||||
riffskip:
|
||||
fsize = fr.read(HeaderBuf, 1, HeaderSize);
|
||||
|
||||
if(std::memcmp(HeaderBuf, "RIFF", 4) == 0)
|
||||
{
|
||||
fr.seek(6l, SEEK_CUR);
|
||||
goto riffskip;
|
||||
}
|
||||
|
||||
if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0)
|
||||
{
|
||||
// GMD/MUS files (ScummVM)
|
||||
fr.seek(7 - static_cast<long>(HeaderSize), SEEK_CUR);
|
||||
is_GMF = true;
|
||||
}
|
||||
|
||||
#ifndef OPNMIDI_DISABLE_MUS_SUPPORT
|
||||
else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0)
|
||||
{
|
||||
// MUS/DMX files (Doom)
|
||||
fr.seek(0, SEEK_END);
|
||||
size_t mus_len = fr.tell();
|
||||
fr.seek(0, SEEK_SET);
|
||||
uint8_t *mus = (uint8_t *)malloc(mus_len);
|
||||
if(!mus)
|
||||
{
|
||||
errorStringOut = "Out of memory!";
|
||||
return false;
|
||||
}
|
||||
fr.read(mus, 1, mus_len);
|
||||
//Close source stream
|
||||
fr.close();
|
||||
|
||||
uint8_t *mid = NULL;
|
||||
uint32_t mid_len = 0;
|
||||
int m2mret = OpnMidi_mus2midi(mus, static_cast<uint32_t>(mus_len),
|
||||
&mid, &mid_len, 0);
|
||||
if(mus) free(mus);
|
||||
if(m2mret < 0)
|
||||
{
|
||||
errorStringOut = "Invalid MUS/DMX data format!";
|
||||
return false;
|
||||
}
|
||||
cvt_buf.reset(mid);
|
||||
//Open converted MIDI file
|
||||
fr.openData(mid, static_cast<size_t>(mid_len));
|
||||
//Re-Read header again!
|
||||
goto riffskip;
|
||||
}
|
||||
#endif //OPNMIDI_DISABLE_MUS_SUPPORT
|
||||
|
||||
#ifndef OPNMIDI_DISABLE_XMI_SUPPORT
|
||||
else if(std::memcmp(HeaderBuf, "FORM", 4) == 0)
|
||||
{
|
||||
if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0)
|
||||
{
|
||||
fr.close();
|
||||
errorStringOut = fr._fileName + ": Invalid format\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
fr.seek(0, SEEK_END);
|
||||
size_t mus_len = fr.tell();
|
||||
fr.seek(0, SEEK_SET);
|
||||
uint8_t *mus = (uint8_t*)malloc(mus_len);
|
||||
if(!mus)
|
||||
{
|
||||
errorStringOut = "Out of memory!";
|
||||
return false;
|
||||
}
|
||||
fr.read(mus, 1, mus_len);
|
||||
//Close source stream
|
||||
fr.close();
|
||||
|
||||
uint8_t *mid = NULL;
|
||||
uint32_t mid_len = 0;
|
||||
int m2mret = OpnMidi_xmi2midi(mus, static_cast<uint32_t>(mus_len),
|
||||
&mid, &mid_len, XMIDI_CONVERT_NOCONVERSION);
|
||||
if(mus) free(mus);
|
||||
if(m2mret < 0)
|
||||
{
|
||||
errorStringOut = "Invalid XMI data format!";
|
||||
return false;
|
||||
}
|
||||
cvt_buf.reset(mid);
|
||||
//Open converted MIDI file
|
||||
fr.openData(mid, static_cast<size_t>(mid_len));
|
||||
//Re-Read header again!
|
||||
goto riffskip;
|
||||
}
|
||||
#endif //OPNMIDI_DISABLE_XMI_SUPPORT
|
||||
|
||||
else
|
||||
{
|
||||
// Try to identify RSXX format
|
||||
if(HeaderBuf[0] == 0x7D)
|
||||
{
|
||||
fr.seek(0x6D, SEEK_SET);
|
||||
fr.read(HeaderBuf, 6, 1);
|
||||
if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0)
|
||||
{
|
||||
is_RSXX = true;
|
||||
fr.seek(0x7D, SEEK_SET);
|
||||
TrackCount = 1;
|
||||
DeltaTicks = 60;
|
||||
//opl.CartoonersVolumes = true;
|
||||
opn.m_musicMode = OPN2::MODE_RSXX;
|
||||
opn.m_volumeScale = OPN2::VOLUME_CMF;
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_RSXX)
|
||||
{
|
||||
if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0)
|
||||
{
|
||||
fr.close();
|
||||
errorStringOut = fr._fileName + ": Invalid format, Header signature is unknown!\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
/*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/
|
||||
TrackCount = (size_t)ReadBEint(HeaderBuf + 10, 2);
|
||||
DeltaTicks = (size_t)ReadBEint(HeaderBuf + 12, 2);
|
||||
}
|
||||
}
|
||||
|
||||
TrackData.clear();
|
||||
TrackData.resize(TrackCount, std::vector<uint8_t>());
|
||||
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
|
||||
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2);
|
||||
static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00};
|
||||
size_t totalGotten = 0;
|
||||
|
||||
for(size_t tk = 0; tk < TrackCount; ++tk)
|
||||
{
|
||||
// Read track header
|
||||
size_t TrackLength;
|
||||
{
|
||||
if(is_GMF || is_RSXX) // Take the rest of the file
|
||||
{
|
||||
size_t pos = fr.tell();
|
||||
fr.seek(0, SEEK_END);
|
||||
TrackLength = fr.tell() - pos;
|
||||
fr.seek(static_cast<long>(pos), SEEK_SET);
|
||||
}
|
||||
else
|
||||
{
|
||||
fsize = fr.read(HeaderBuf, 1, 8);
|
||||
if(std::memcmp(HeaderBuf, "MTrk", 4) != 0)
|
||||
{
|
||||
fr.close();
|
||||
errorStringOut = fr._fileName + ": Invalid format, MTrk signature is not found!\n";
|
||||
return false;
|
||||
}
|
||||
TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4);
|
||||
}
|
||||
|
||||
// Read track data
|
||||
TrackData[tk].resize(TrackLength);
|
||||
fsize = fr.read(&TrackData[tk][0], 1, TrackLength);
|
||||
totalGotten += fsize;
|
||||
|
||||
if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag.
|
||||
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
|
||||
if(is_RSXX)//Finalize raw track data with a zero
|
||||
TrackData[tk].push_back(0);
|
||||
|
||||
//bool ok = false;
|
||||
//// Read next event time
|
||||
//uint64_t tkDelay = ReadVarLenEx(tk, ok);
|
||||
//if(ok)
|
||||
// CurrentPosition.track[tk].delay = tkDelay;
|
||||
//else
|
||||
//{
|
||||
// std::stringstream msg;
|
||||
// msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")";
|
||||
// OPN2MIDI_ErrorString = msg.str();
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t tk = 0; tk < TrackCount; ++tk)
|
||||
totalGotten += TrackData[tk].size();
|
||||
|
||||
if(totalGotten == 0)
|
||||
{
|
||||
errorStringOut = fr._fileName + ": Empty track data";
|
||||
return false;
|
||||
}
|
||||
|
||||
//Build new MIDI events table
|
||||
if(!buildTrackData())
|
||||
{
|
||||
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
opn.Reset(m_setup.emulator, m_setup.PCM_RATE); // Reset OPN2 chip
|
||||
ch.clear();
|
||||
ch.resize(opn.NumChannels);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -48,23 +48,60 @@
|
|||
#include "chips/gx_opn2.h"
|
||||
#endif
|
||||
|
||||
static const unsigned opn2_emulatorSupport = 0
|
||||
#ifndef OPNMIDI_DISABLE_NUKED_EMULATOR
|
||||
| (1u << OPNMIDI_EMU_NUKED)
|
||||
#endif
|
||||
#ifndef OPNMIDI_DISABLE_MAME_EMULATOR
|
||||
| (1u << OPNMIDI_EMU_MAME)
|
||||
#endif
|
||||
#ifndef OPNMIDI_DISABLE_GENS_EMULATOR
|
||||
| (1u << OPNMIDI_EMU_GENS)
|
||||
#endif
|
||||
#ifndef OPNMIDI_DISABLE_GX_EMULATOR
|
||||
| (1u << OPNMIDI_EMU_GX)
|
||||
#endif
|
||||
;
|
||||
|
||||
static const uint8_t NoteChannels[6] = { 0, 1, 2, 4, 5, 6 };
|
||||
|
||||
static inline void getOpnChannel(uint32_t in_channel,
|
||||
unsigned &out_card,
|
||||
uint8_t &out_port,
|
||||
uint8_t &out_ch)
|
||||
//! Check emulator availability
|
||||
bool opn2_isEmulatorAvailable(int emulator)
|
||||
{
|
||||
out_card = in_channel / 6;
|
||||
uint8_t ch4 = in_channel % 6;
|
||||
out_port = ((ch4 < 3) ? 0 : 1);
|
||||
out_ch = ch4 % 3;
|
||||
return (opn2_emulatorSupport & (1u << (unsigned)emulator)) != 0;
|
||||
}
|
||||
|
||||
void OPN2::cleanInstrumentBanks()
|
||||
//! Find highest emulator
|
||||
int opn2_getHighestEmulator()
|
||||
{
|
||||
dynamic_banks.clear();
|
||||
int emu = -1;
|
||||
for(unsigned m = opn2_emulatorSupport; m > 0; m >>= 1)
|
||||
++emu;
|
||||
return emu;
|
||||
}
|
||||
|
||||
//! Find lowest emulator
|
||||
int opn2_getLowestEmulator()
|
||||
{
|
||||
int emu = -1;
|
||||
unsigned m = opn2_emulatorSupport;
|
||||
if(m > 0)
|
||||
{
|
||||
for(emu = 0; (m & 1) == 0; m >>= 1)
|
||||
++emu;
|
||||
}
|
||||
return emu;
|
||||
}
|
||||
|
||||
static const uint32_t g_noteChannelsMap[6] = { 0, 1, 2, 4, 5, 6 };
|
||||
|
||||
static inline void getOpnChannel(size_t in_channel,
|
||||
size_t &out_chip,
|
||||
uint8_t &out_port,
|
||||
uint32_t &out_ch)
|
||||
{
|
||||
out_chip = in_channel / 6;
|
||||
size_t ch4 = in_channel % 6;
|
||||
out_port = ((ch4 < 3) ? 0 : 1);
|
||||
out_ch = static_cast<uint32_t>(ch4 % 3);
|
||||
}
|
||||
|
||||
static opnInstMeta2 makeEmptyInstrument()
|
||||
|
@ -75,71 +112,128 @@ static opnInstMeta2 makeEmptyInstrument()
|
|||
return ins;
|
||||
}
|
||||
|
||||
const opnInstMeta2 OPN2::emptyInstrument = makeEmptyInstrument();
|
||||
const opnInstMeta2 OPN2::m_emptyInstrument = makeEmptyInstrument();
|
||||
|
||||
OPN2::OPN2() :
|
||||
regLFO(0),
|
||||
NumCards(1),
|
||||
m_regLFOSetup(0),
|
||||
m_numChips(1),
|
||||
m_scaleModulators(false),
|
||||
m_runAtPcmRate(false),
|
||||
m_softPanning(false),
|
||||
m_musicMode(MODE_MIDI),
|
||||
m_volumeScale(VOLUME_Generic)
|
||||
m_volumeScale(VOLUME_Generic),
|
||||
m_lfoEnable(false),
|
||||
m_lfoFrequency(0)
|
||||
{
|
||||
m_insBankSetup.volumeModel = OPN2::VOLUME_Generic;
|
||||
m_insBankSetup.lfoEnable = false;
|
||||
m_insBankSetup.lfoFrequency = 0;
|
||||
|
||||
// Initialize blank instruments banks
|
||||
cleanInstrumentBanks();
|
||||
m_insBanks.clear();
|
||||
}
|
||||
|
||||
OPN2::~OPN2()
|
||||
{
|
||||
ClearChips();
|
||||
clearChips();
|
||||
}
|
||||
|
||||
void OPN2::PokeO(size_t card, uint8_t port, uint8_t index, uint8_t value)
|
||||
bool OPN2::setupLocked()
|
||||
{
|
||||
cardsOP2[card]->writeReg(port, index, value);
|
||||
return (m_musicMode == MODE_CMF ||
|
||||
m_musicMode == MODE_IMF ||
|
||||
m_musicMode == MODE_RSXX);
|
||||
}
|
||||
|
||||
void OPN2::NoteOff(size_t c)
|
||||
void OPN2::writeReg(size_t chip, uint8_t port, uint8_t index, uint8_t value)
|
||||
{
|
||||
unsigned card;
|
||||
uint8_t port, cc;
|
||||
uint8_t ch4 = c % 6;
|
||||
getOpnChannel(uint16_t(c), card, port, cc);
|
||||
PokeO(card, 0, 0x28, NoteChannels[ch4]);
|
||||
m_chips[chip]->writeReg(port, index, value);
|
||||
}
|
||||
|
||||
void OPN2::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071
|
||||
void OPN2::writeRegI(size_t chip, uint8_t port, uint32_t index, uint32_t value)
|
||||
{
|
||||
unsigned card;
|
||||
uint8_t port, cc;
|
||||
uint8_t ch4 = c % 6;
|
||||
getOpnChannel(uint16_t(c), card, port, cc);
|
||||
m_chips[chip]->writeReg(port, static_cast<uint8_t>(index), static_cast<uint8_t>(value));
|
||||
}
|
||||
|
||||
uint16_t x2 = 0x0000;
|
||||
void OPN2::writePan(size_t chip, uint32_t index, uint32_t value)
|
||||
{
|
||||
m_chips[chip]->writePan(static_cast<uint16_t>(index), static_cast<uint8_t>(value));
|
||||
}
|
||||
|
||||
if(hertz < 0 || hertz > 262143) // Avoid infinite loop
|
||||
void OPN2::noteOff(size_t c)
|
||||
{
|
||||
size_t chip;
|
||||
uint8_t port;
|
||||
uint32_t cc;
|
||||
size_t ch4 = c % 6;
|
||||
getOpnChannel(c, chip, port, cc);
|
||||
writeRegI(chip, 0, 0x28, g_noteChannelsMap[ch4]);
|
||||
}
|
||||
|
||||
void OPN2::noteOn(size_t c, double hertz) // Hertz range: 0..131071
|
||||
{
|
||||
size_t chip;
|
||||
uint8_t port;
|
||||
uint32_t cc;
|
||||
size_t ch4 = c % 6;
|
||||
getOpnChannel(c, chip, port, cc);
|
||||
|
||||
if(hertz < 0) // Avoid infinite loop
|
||||
return;
|
||||
|
||||
while((hertz >= 1023.75) && (x2 < 0x3800))
|
||||
uint32_t octave = 0, ftone = 0, mul_offset = 0;
|
||||
const opnInstData &adli = m_insCache[c];
|
||||
|
||||
//Basic range until max of octaves reaching
|
||||
while((hertz >= 1023.75) && (octave < 0x3800))
|
||||
{
|
||||
hertz /= 2.0; // Calculate octave
|
||||
x2 += 0x800;
|
||||
octave += 0x800;
|
||||
}
|
||||
//Extended range, rely on frequency multiplication increment
|
||||
while(hertz >= 2036.75)
|
||||
{
|
||||
hertz /= 2.0; // Calculate octave
|
||||
mul_offset++;
|
||||
}
|
||||
ftone = octave + static_cast<uint32_t>(hertz + 0.5);
|
||||
|
||||
for(size_t op = 0; op < 4; op++)
|
||||
{
|
||||
uint32_t reg = adli.OPS[op].data[0];
|
||||
uint16_t address = static_cast<uint16_t>(0x30 + (op * 4) + cc);
|
||||
if(mul_offset > 0) // Increase frequency multiplication value
|
||||
{
|
||||
uint32_t dt = reg & 0xF0;
|
||||
uint32_t mul = reg & 0x0F;
|
||||
if((mul + mul_offset) > 0x0F)
|
||||
{
|
||||
mul_offset = 0;
|
||||
mul = 0x0F;
|
||||
}
|
||||
writeRegI(chip, port, address, uint8_t(dt | (mul + mul_offset)));
|
||||
}
|
||||
else
|
||||
{
|
||||
writeRegI(chip, port, address, uint8_t(reg));
|
||||
}
|
||||
}
|
||||
|
||||
x2 += static_cast<uint32_t>(hertz + 0.5);
|
||||
PokeO(card, port, 0xA4 + cc, (x2>>8) & 0xFF);//Set frequency and octave
|
||||
PokeO(card, port, 0xA0 + cc, x2 & 0xFF);
|
||||
PokeO(card, 0, 0x28, 0xF0 + NoteChannels[ch4]);
|
||||
pit[c] = static_cast<uint8_t>(x2 >> 8);
|
||||
writeRegI(chip, port, 0xA4 + cc, (ftone>>8) & 0xFF);//Set frequency and octave
|
||||
writeRegI(chip, port, 0xA0 + cc, ftone & 0xFF);
|
||||
writeRegI(chip, 0, 0x28, 0xF0 + g_noteChannelsMap[ch4]);
|
||||
}
|
||||
|
||||
void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
||||
void OPN2::touchNote(size_t c, uint8_t volume, uint8_t brightness)
|
||||
{
|
||||
if(volume > 127) volume = 127;
|
||||
|
||||
unsigned card;
|
||||
uint8_t port, cc;
|
||||
getOpnChannel(c, card, port, cc);
|
||||
size_t chip;
|
||||
uint8_t port;
|
||||
uint32_t cc;
|
||||
getOpnChannel(c, chip, port, cc);
|
||||
|
||||
const opnInstData &adli = ins[c];
|
||||
const opnInstData &adli = m_insCache[c];
|
||||
|
||||
uint8_t op_vol[4] =
|
||||
{
|
||||
|
@ -170,16 +264,16 @@ void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
|||
uint8_t alg = adli.fbalg & 0x07;
|
||||
for(uint8_t op = 0; op < 4; op++)
|
||||
{
|
||||
bool do_op = alg_do[alg][op] || ScaleModulators;
|
||||
uint8_t x = op_vol[op];
|
||||
uint8_t vol_res = do_op ? uint8_t(127 - (volume * (127 - (x&127)))/127) : x;
|
||||
bool do_op = alg_do[alg][op] || m_scaleModulators;
|
||||
uint32_t x = op_vol[op];
|
||||
uint32_t vol_res = do_op ? (127 - (static_cast<uint32_t>(volume) * (127 - (x & 127)))/127) : x;
|
||||
if(brightness != 127)
|
||||
{
|
||||
brightness = static_cast<uint8_t>(::round(127.0 * ::sqrt((static_cast<double>(brightness)) * (1.0 / 127.0))));
|
||||
brightness = static_cast<uint32_t>(::round(127.0 * ::sqrt((static_cast<double>(brightness)) * (1.0 / 127.0))));
|
||||
if(!do_op)
|
||||
vol_res = uint8_t(127 - (brightness * (127 - (uint32_t(vol_res) & 127))) / 127);
|
||||
vol_res = (127 - (brightness * (127 - (static_cast<uint32_t>(vol_res) & 127))) / 127);
|
||||
}
|
||||
PokeO(card, port, 0x40 + cc + (4 * op), vol_res);
|
||||
writeRegI(chip, port, 0x40 + cc + (4 * op), vol_res);
|
||||
}
|
||||
// Correct formula (ST3, AdPlug):
|
||||
// 63-((63-(instrvol))/63)*chanvol
|
||||
|
@ -189,57 +283,68 @@ void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
|||
// 63 + chanvol * (instrvol / 63.0 - 1)
|
||||
}
|
||||
|
||||
void OPN2::Patch(uint16_t c, const opnInstData &adli)
|
||||
void OPN2::setPatch(size_t c, const opnInstData &instrument)
|
||||
{
|
||||
unsigned card;
|
||||
uint8_t port, cc;
|
||||
getOpnChannel(uint16_t(c), card, port, cc);
|
||||
ins[c] = adli;
|
||||
#if 1 //Reg1-Op1, Reg1-Op2, Reg1-Op3, Reg1-Op4,....
|
||||
size_t chip;
|
||||
uint8_t port;
|
||||
uint32_t cc;
|
||||
getOpnChannel(c, chip, port, cc);
|
||||
m_insCache[c] = instrument;
|
||||
for(uint8_t d = 0; d < 7; d++)
|
||||
{
|
||||
for(uint8_t op = 0; op < 4; op++)
|
||||
PokeO(card, port, 0x30 + (0x10 * d) + (op * 4) + cc, adli.OPS[op].data[d]);
|
||||
writeRegI(chip, port, 0x30 + (0x10 * d) + (op * 4) + cc, instrument.OPS[op].data[d]);
|
||||
}
|
||||
#else //Reg1-Op1, Reg2-Op1, Reg3-Op1, Reg4-Op1,....
|
||||
for(uint8_t op = 0; op < 4; op++)
|
||||
{
|
||||
PokeO(card, port, 0x30 + (op * 4) + cc, adli.OPS[op].data[0]);
|
||||
PokeO(card, port, 0x40 + (op * 4) + cc, adli.OPS[op].data[1]);
|
||||
PokeO(card, port, 0x50 + (op * 4) + cc, adli.OPS[op].data[2]);
|
||||
PokeO(card, port, 0x60 + (op * 4) + cc, adli.OPS[op].data[3]);
|
||||
PokeO(card, port, 0x70 + (op * 4) + cc, adli.OPS[op].data[4]);
|
||||
PokeO(card, port, 0x80 + (op * 4) + cc, adli.OPS[op].data[5]);
|
||||
PokeO(card, port, 0x90 + (op * 4) + cc, adli.OPS[op].data[6]);
|
||||
}
|
||||
#endif
|
||||
|
||||
PokeO(card, port, 0xB0 + cc, adli.fbalg);//Feedback/Algorithm
|
||||
regBD[c] = (regBD[c] & 0xC0) | (adli.lfosens & 0x3F);
|
||||
PokeO(card, port, 0xB4 + cc, regBD[c]);//Panorame and LFO bits
|
||||
writeRegI(chip, port, 0xB0 + cc, instrument.fbalg);//Feedback/Algorithm
|
||||
m_regLFOSens[c] = (m_regLFOSens[c] & 0xC0) | (instrument.lfosens & 0x3F);
|
||||
writeRegI(chip, port, 0xB4 + cc, m_regLFOSens[c]);//Panorame and LFO bits
|
||||
}
|
||||
|
||||
void OPN2::Pan(unsigned c, unsigned value)
|
||||
void OPN2::setPan(size_t c, uint8_t value)
|
||||
{
|
||||
unsigned card;
|
||||
uint8_t port, cc;
|
||||
getOpnChannel(uint16_t(c), card, port, cc);
|
||||
const opnInstData &adli = ins[c];
|
||||
uint8_t val = (value & 0xC0) | (adli.lfosens & 0x3F);
|
||||
regBD[c] = val;
|
||||
PokeO(card, port, 0xB4 + cc, val);
|
||||
size_t chip;
|
||||
uint8_t port;
|
||||
uint32_t cc;
|
||||
getOpnChannel(c, chip, port, cc);
|
||||
const opnInstData &adli = m_insCache[c];
|
||||
uint8_t val = 0;
|
||||
if(m_softPanning)
|
||||
{
|
||||
val = (OPN_PANNING_BOTH & 0xC0) | (adli.lfosens & 0x3F);
|
||||
writePan(chip, c % 6, value);
|
||||
writeRegI(chip, port, 0xB4 + cc, val);
|
||||
}
|
||||
else
|
||||
{
|
||||
int panning = 0;
|
||||
if(value < 64 + 32) panning |= OPN_PANNING_LEFT;
|
||||
if(value >= 64 - 32) panning |= OPN_PANNING_RIGHT;
|
||||
val = (panning & 0xC0) | (adli.lfosens & 0x3F);
|
||||
writePan(chip, c % 6, 64);
|
||||
writeRegI(chip, port, 0xB4 + cc, val);
|
||||
}
|
||||
m_regLFOSens[c] = val;
|
||||
}
|
||||
|
||||
void OPN2::Silence() // Silence all OPL channels.
|
||||
void OPN2::silenceAll() // Silence all OPL channels.
|
||||
{
|
||||
for(unsigned c = 0; c < NumChannels; ++c)
|
||||
for(size_t c = 0; c < m_numChannels; ++c)
|
||||
{
|
||||
NoteOff(c);
|
||||
Touch_Real(c, 0);
|
||||
noteOff(c);
|
||||
touchNote(c, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
|
||||
void OPN2::commitLFOSetup()
|
||||
{
|
||||
uint8_t regLFOSetup = (m_lfoEnable ? 8 : 0) | (m_lfoFrequency & 7);
|
||||
m_regLFOSetup = regLFOSetup;
|
||||
for(size_t chip = 0; chip < m_numChips; ++chip)
|
||||
writeReg(chip, 0, 0x22, regLFOSetup);
|
||||
}
|
||||
|
||||
void OPN2::setVolumeScaleModel(OPNMIDI_VolumeModels volumeModel)
|
||||
{
|
||||
switch(volumeModel)
|
||||
{
|
||||
|
@ -250,8 +355,8 @@ void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
|
|||
m_volumeScale = OPN2::VOLUME_Generic;
|
||||
break;
|
||||
|
||||
case OPNMIDI_VolumeModel_CMF:
|
||||
m_volumeScale = OPN2::VOLUME_CMF;
|
||||
case OPNMIDI_VolumeModel_NativeOPN2:
|
||||
m_volumeScale = OPN2::VOLUME_NATIVE;
|
||||
break;
|
||||
|
||||
case OPNMIDI_VolumeModel_DMX:
|
||||
|
@ -268,70 +373,101 @@ void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
|
|||
}
|
||||
}
|
||||
|
||||
void OPN2::ClearChips()
|
||||
OPNMIDI_VolumeModels OPN2::getVolumeScaleModel()
|
||||
{
|
||||
for(size_t i = 0; i < cardsOP2.size(); i++)
|
||||
cardsOP2[i].reset(NULL);
|
||||
cardsOP2.clear();
|
||||
switch(m_volumeScale)
|
||||
{
|
||||
default:
|
||||
case OPN2::VOLUME_Generic:
|
||||
return OPNMIDI_VolumeModel_Generic;
|
||||
case OPN2::VOLUME_NATIVE:
|
||||
return OPNMIDI_VolumeModel_NativeOPN2;
|
||||
case OPN2::VOLUME_DMX:
|
||||
return OPNMIDI_VolumeModel_DMX;
|
||||
case OPN2::VOLUME_APOGEE:
|
||||
return OPNMIDI_VolumeModel_APOGEE;
|
||||
case OPN2::VOLUME_9X:
|
||||
return OPNMIDI_VolumeModel_9X;
|
||||
}
|
||||
}
|
||||
|
||||
void OPN2::Reset(int emulator, unsigned long PCM_RATE)
|
||||
void OPN2::clearChips()
|
||||
{
|
||||
ClearChips();
|
||||
ins.clear();
|
||||
pit.clear();
|
||||
regBD.clear();
|
||||
cardsOP2.resize(NumCards, AdlMIDI_SPtr<OPNChipBase>());
|
||||
for(size_t i = 0; i < m_chips.size(); i++)
|
||||
m_chips[i].reset(NULL);
|
||||
m_chips.clear();
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < cardsOP2.size(); i++)
|
||||
void OPN2::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
|
||||
{
|
||||
#if !defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
ADL_UNUSED(audioTickHandler);
|
||||
#endif
|
||||
clearChips();
|
||||
m_insCache.clear();
|
||||
m_regLFOSens.clear();
|
||||
m_chips.resize(m_numChips, AdlMIDI_SPtr<OPNChipBase>());
|
||||
|
||||
for(size_t i = 0; i < m_chips.size(); i++)
|
||||
{
|
||||
OPNChipBase *chip;
|
||||
|
||||
switch(emulator)
|
||||
{
|
||||
default:
|
||||
assert(false);
|
||||
abort();
|
||||
#ifndef OPNMIDI_DISABLE_MAME_EMULATOR
|
||||
case OPNMIDI_EMU_MAME:
|
||||
cardsOP2[i].reset(new MameOPN2());
|
||||
chip = new MameOPN2;
|
||||
break;
|
||||
#endif
|
||||
#ifndef OPNMIDI_DISABLE_NUKED_EMULATOR
|
||||
case OPNMIDI_EMU_NUKED:
|
||||
cardsOP2[i].reset(new NukedOPN2());
|
||||
chip = new NukedOPN2;
|
||||
break;
|
||||
#endif
|
||||
#ifndef OPNMIDI_DISABLE_GENS_EMULATOR
|
||||
case OPNMIDI_EMU_GENS:
|
||||
cardsOP2[i].reset(new GensOPN2());
|
||||
chip = new GensOPN2;
|
||||
break;
|
||||
#endif
|
||||
#ifndef OPNMIDI_DISABLE_GX_EMULATOR
|
||||
case OPNMIDI_EMU_GX:
|
||||
cardsOP2[i].reset(new GXOPN2());
|
||||
chip = new GXOPN2;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
cardsOP2[i]->setRate((uint32_t)PCM_RATE, 7670454);
|
||||
if(runAtPcmRate)
|
||||
cardsOP2[i]->setRunningAtPcmRate(true);
|
||||
m_chips[i].reset(chip);
|
||||
chip->setChipId((uint32_t)i);
|
||||
chip->setRate((uint32_t)PCM_RATE, 7670454);
|
||||
if(m_runAtPcmRate)
|
||||
chip->setRunningAtPcmRate(true);
|
||||
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
chip->setAudioTickHandlerInstance(audioTickHandler);
|
||||
#endif
|
||||
}
|
||||
|
||||
NumChannels = NumCards * 6;
|
||||
ins.resize(NumChannels, emptyInstrument.opn[0]);
|
||||
pit.resize(NumChannels, 0);
|
||||
regBD.resize(NumChannels, 0);
|
||||
m_numChannels = m_numChips * 6;
|
||||
m_insCache.resize(m_numChannels, m_emptyInstrument.opn[0]);
|
||||
m_regLFOSens.resize(m_numChannels, 0);
|
||||
|
||||
for(unsigned card = 0; card < NumCards; ++card)
|
||||
uint8_t regLFOSetup = (m_lfoEnable ? 8 : 0) | (m_lfoFrequency & 7);
|
||||
m_regLFOSetup = regLFOSetup;
|
||||
|
||||
for(size_t card = 0; card < m_numChips; ++card)
|
||||
{
|
||||
PokeO(card, 0, 0x22, regLFO);//push current LFO state
|
||||
PokeO(card, 0, 0x27, 0x00); //set Channel 3 normal mode
|
||||
PokeO(card, 0, 0x2B, 0x00); //Disable DAC
|
||||
writeReg(card, 0, 0x22, regLFOSetup);//push current LFO state
|
||||
writeReg(card, 0, 0x27, 0x00); //set Channel 3 normal mode
|
||||
writeReg(card, 0, 0x2B, 0x00); //Disable DAC
|
||||
//Shut up all channels
|
||||
PokeO(card, 0, 0x28, 0x00 ); //Note Off 0 channel
|
||||
PokeO(card, 0, 0x28, 0x01 ); //Note Off 1 channel
|
||||
PokeO(card, 0, 0x28, 0x02 ); //Note Off 2 channel
|
||||
PokeO(card, 0, 0x28, 0x04 ); //Note Off 3 channel
|
||||
PokeO(card, 0, 0x28, 0x05 ); //Note Off 4 channel
|
||||
PokeO(card, 0, 0x28, 0x06 ); //Note Off 5 channel
|
||||
writeReg(card, 0, 0x28, 0x00 ); //Note Off 0 channel
|
||||
writeReg(card, 0, 0x28, 0x01 ); //Note Off 1 channel
|
||||
writeReg(card, 0, 0x28, 0x02 ); //Note Off 2 channel
|
||||
writeReg(card, 0, 0x28, 0x04 ); //Note Off 3 channel
|
||||
writeReg(card, 0, 0x28, 0x05 ); //Note Off 4 channel
|
||||
writeReg(card, 0, 0x28, 0x06 ); //Note Off 5 channel
|
||||
}
|
||||
|
||||
Silence();
|
||||
silenceAll();
|
||||
}
|
||||
|
|
|
@ -25,31 +25,12 @@
|
|||
|
||||
std::string OPN2MIDI_ErrorString;
|
||||
|
||||
int opn2RefreshNumCards(OPN2_MIDIPlayer * /*device*/)
|
||||
{
|
||||
// OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
|
||||
//OPN uses 4-op instruments only
|
||||
// unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
|
||||
// for(unsigned a = 0; a < 256; ++a)
|
||||
// {
|
||||
// unsigned insno = banks[device->OpnBank][a];
|
||||
// if(insno == 198) continue;
|
||||
// ++n_total[a / 128];
|
||||
// if(adlins[insno].adlno1 != adlins[insno].adlno2)
|
||||
// ++n_fourop[a / 128];
|
||||
// }
|
||||
// Generator callback on audio rate ticks
|
||||
|
||||
// device->NumFourOps =
|
||||
// (n_fourop[0] >= n_total[0] * 7 / 8) ? device->NumCards * 6
|
||||
// : (n_fourop[0] < n_total[0] * 1 / 8) ? 0
|
||||
// : (device->NumCards == 1 ? 1 : device->NumCards * 4);
|
||||
// reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer)->opn.NumFourOps = device->NumFourOps;
|
||||
// if(n_fourop[0] >= n_total[0] * 15 / 16 && device->NumFourOps == 0)
|
||||
// {
|
||||
// OPN2MIDI_ErrorString = "ERROR: You have selected a bank that consists almost exclusively of four-op patches.\n"
|
||||
// " The results (silence + much cpu load) would be probably\n"
|
||||
// " not what you want, therefore ignoring the request.\n";
|
||||
// return -1;
|
||||
// }
|
||||
return 0;
|
||||
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
void opn2_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate)
|
||||
{
|
||||
reinterpret_cast<OPNMIDIplay *>(instance)->AudioTick(chipId, rate);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
622
src/sound/opnmidi/wopn/wopn_file.c
Normal file
622
src/sound/opnmidi/wopn/wopn_file.c
Normal file
|
@ -0,0 +1,622 @@
|
|||
/*
|
||||
* Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup
|
||||
*
|
||||
* Copyright (c) 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 "wopn_file.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *wopn2_magic1 = "WOPN2-BANK\0";
|
||||
static const char *wopn2_magic2 = "WOPN2-B2NK\0";
|
||||
static const char *opni_magic1 = "WOPN2-INST\0";
|
||||
static const char *opni_magic2 = "WOPN2-IN2T\0";
|
||||
|
||||
static const uint16_t wopn_latest_version = 2;
|
||||
|
||||
enum
|
||||
{
|
||||
WOPN_INST_SIZE_V1 = 65,
|
||||
WOPN_INST_SIZE_V2 = 69
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks)
|
||||
{
|
||||
WOPNFile *file = NULL;
|
||||
if(melodic_banks == 0)
|
||||
return NULL;
|
||||
if(percussive_banks == 0)
|
||||
return NULL;
|
||||
file = (WOPNFile*)calloc(1, sizeof(WOPNFile));
|
||||
if(!file)
|
||||
return NULL;
|
||||
file->banks_count_melodic = melodic_banks;
|
||||
file->banks_count_percussion = percussive_banks;
|
||||
file->banks_melodic = (WOPNBank*)calloc(1, sizeof(WOPNBank) * melodic_banks );
|
||||
file->banks_percussive = (WOPNBank*)calloc(1, sizeof(WOPNBank) * percussive_banks );
|
||||
return file;
|
||||
}
|
||||
|
||||
void WOPN_Free(WOPNFile *file)
|
||||
{
|
||||
if(file)
|
||||
{
|
||||
if(file->banks_melodic)
|
||||
free(file->banks_melodic);
|
||||
if(file->banks_percussive)
|
||||
free(file->banks_percussive);
|
||||
free(file);
|
||||
}
|
||||
}
|
||||
|
||||
int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2)
|
||||
{
|
||||
int res = 1;
|
||||
|
||||
res &= (bank1->version == bank2->version);
|
||||
res &= (bank1->lfo_freq == bank2->lfo_freq);
|
||||
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(WOPNBank)) == 0);
|
||||
if(res)
|
||||
{
|
||||
for(i = 0; i < bank1->banks_count_percussion; i++)
|
||||
res &= (memcmp(&bank1->banks_percussive[i], &bank2->banks_percussive[i], sizeof(WOPNBank)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void WOPN_parseInstrument(WOPNInstrument *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_offset = toSint16BE(cursor + 32);
|
||||
ins->midi_velocity_offset = 0; /* TODO: for future version > 2 */
|
||||
ins->percussion_key_number = cursor[34];
|
||||
ins->inst_flags = 0; /* TODO: for future version > 2 */
|
||||
ins->fbalg = cursor[35];
|
||||
ins->lfosens = cursor[36];
|
||||
for(l = 0; l < 4; l++)
|
||||
{
|
||||
size_t off = 37 + (size_t)(l) * 7;
|
||||
ins->operators[l].dtfm_30 = cursor[off + 0];
|
||||
ins->operators[l].level_40 = cursor[off + 1];
|
||||
ins->operators[l].rsatk_50 = cursor[off + 2];
|
||||
ins->operators[l].amdecay1_60 = cursor[off + 3];
|
||||
ins->operators[l].decay2_70 = cursor[off + 4];
|
||||
ins->operators[l].susrel_80 = cursor[off + 5];
|
||||
ins->operators[l].ssgeg_90 = cursor[off + 6];
|
||||
}
|
||||
if((version >= 2) && has_sounding_delays)
|
||||
{
|
||||
ins->delay_on_ms = toUint16BE(cursor + 65);
|
||||
ins->delay_off_ms = toUint16BE(cursor + 67);
|
||||
|
||||
/* Null delays indicate the blank instrument in version 2 */
|
||||
if((version < 3) && ins->delay_on_ms == 0 && ins->delay_off_ms == 0)
|
||||
ins->inst_flags |= WOPN_Ins_IsBlank;
|
||||
}
|
||||
}
|
||||
|
||||
static void WOPN_writeInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays)
|
||||
{
|
||||
int l;
|
||||
strncpy((char*)cursor, ins->inst_name, 32);
|
||||
fromSint16BE(ins->note_offset, cursor + 32);
|
||||
cursor[34] = ins->percussion_key_number;
|
||||
cursor[35] = ins->fbalg;
|
||||
cursor[36] = ins->lfosens;
|
||||
for(l = 0; l < 4; l++)
|
||||
{
|
||||
size_t off = 37 + (size_t)(l) * 7;
|
||||
cursor[off + 0] = ins->operators[l].dtfm_30;
|
||||
cursor[off + 1] = ins->operators[l].level_40;
|
||||
cursor[off + 2] = ins->operators[l].rsatk_50;
|
||||
cursor[off + 3] = ins->operators[l].amdecay1_60;
|
||||
cursor[off + 4] = ins->operators[l].decay2_70;
|
||||
cursor[off + 5] = ins->operators[l].susrel_80;
|
||||
cursor[off + 6] = ins->operators[l].ssgeg_90;
|
||||
}
|
||||
if((version >= 2) && has_sounding_delays)
|
||||
{
|
||||
if((version < 3) && (ins->inst_flags & WOPN_Ins_IsBlank) != 0)
|
||||
{
|
||||
/* Null delays indicate the blank instrument in version 2 */
|
||||
fromUint16BE(0, cursor + 65);
|
||||
fromUint16BE(0, cursor + 67);
|
||||
}
|
||||
else
|
||||
{
|
||||
fromUint16BE(ins->delay_on_ms, cursor + 65);
|
||||
fromUint16BE(ins->delay_off_ms, cursor + 67);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error)
|
||||
{
|
||||
WOPNFile *outFile = NULL;
|
||||
uint16_t i = 0, j = 0, k = 0;
|
||||
uint16_t version = 0;
|
||||
uint16_t count_melodic_banks = 1;
|
||||
uint16_t count_percussive_banks = 1;
|
||||
uint8_t *cursor = (uint8_t *)mem;
|
||||
|
||||
WOPNBank *bankslots[2];
|
||||
uint16_t bankslots_sizes[2];
|
||||
|
||||
#define SET_ERROR(err) \
|
||||
{\
|
||||
WOPN_Free(outFile);\
|
||||
if(error)\
|
||||
{\
|
||||
*error = err;\
|
||||
}\
|
||||
}
|
||||
|
||||
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
|
||||
|
||||
if(!cursor)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_NULL_POINTER);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
{/* Magic number */
|
||||
if(length < 11)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
|
||||
return NULL;
|
||||
}
|
||||
if(memcmp(cursor, wopn2_magic1, 11) == 0)
|
||||
{
|
||||
version = 1;
|
||||
}
|
||||
else if(memcmp(cursor, wopn2_magic2, 11) != 0)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_BAD_MAGIC);
|
||||
return NULL;
|
||||
}
|
||||
GO_FORWARD(11);
|
||||
}
|
||||
|
||||
if (version == 0)
|
||||
{/* Version code */
|
||||
if(length < 2)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
|
||||
return NULL;
|
||||
}
|
||||
version = toUint16LE(cursor);
|
||||
if(version > wopn_latest_version)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_NEWER_VERSION);
|
||||
return NULL;
|
||||
}
|
||||
GO_FORWARD(2);
|
||||
}
|
||||
|
||||
{/* Header of WOPN */
|
||||
uint8_t head[5];
|
||||
if(length < 5)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(head, cursor, 5);
|
||||
count_melodic_banks = toUint16BE(head);
|
||||
count_percussive_banks = toUint16BE(head + 2);
|
||||
GO_FORWARD(5);
|
||||
|
||||
outFile = WOPN_Init(count_melodic_banks, count_percussive_banks);
|
||||
if(!outFile)
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_OUT_OF_MEMORY);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outFile->version = version;
|
||||
outFile->lfo_freq = head[4];
|
||||
outFile->volume_model = 0;
|
||||
}
|
||||
|
||||
bankslots_sizes[0] = count_melodic_banks;
|
||||
bankslots[0] = outFile->banks_melodic;
|
||||
bankslots_sizes[1] = count_percussive_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(WOPN_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 > 1)
|
||||
insSize = WOPN_INST_SIZE_V2;
|
||||
else
|
||||
insSize = WOPN_INST_SIZE_V1;
|
||||
for(i = 0; i < 2; i++)
|
||||
{
|
||||
if(length < (insSize * 128) * (size_t)bankslots_sizes[i])
|
||||
{
|
||||
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for(j = 0; j < bankslots_sizes[i]; j++)
|
||||
{
|
||||
for(k = 0; k < 128; k++)
|
||||
{
|
||||
WOPNInstrument *ins = &bankslots[i][j].ins[k];
|
||||
WOPN_parseInstrument(ins, cursor, version, 1);
|
||||
GO_FORWARD(insSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef GO_FORWARD
|
||||
#undef SET_ERROR
|
||||
|
||||
return outFile;
|
||||
}
|
||||
|
||||
int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length)
|
||||
{
|
||||
uint16_t version = 0;
|
||||
uint8_t *cursor = (uint8_t *)mem;
|
||||
uint16_t ins_size;
|
||||
|
||||
if(!cursor)
|
||||
return WOPN_ERR_NULL_POINTER;
|
||||
|
||||
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
|
||||
|
||||
{/* Magic number */
|
||||
if(length < 11)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
if(memcmp(cursor, opni_magic1, 11) == 0)
|
||||
version = 1;
|
||||
else if(memcmp(cursor, opni_magic2, 11) != 0)
|
||||
return WOPN_ERR_BAD_MAGIC;
|
||||
GO_FORWARD(11);
|
||||
}
|
||||
|
||||
if (version == 0) {/* Version code */
|
||||
if(length < 2)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
version = toUint16LE(cursor);
|
||||
if(version > wopn_latest_version)
|
||||
return WOPN_ERR_NEWER_VERSION;
|
||||
GO_FORWARD(2);
|
||||
}
|
||||
|
||||
file->version = version;
|
||||
|
||||
{/* is drum flag */
|
||||
if(length < 1)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
file->is_drum = *cursor;
|
||||
GO_FORWARD(1);
|
||||
}
|
||||
|
||||
if(version > 1)
|
||||
/* Skip sounding delays are not part of single-instrument file
|
||||
* two sizes of uint16_t will be subtracted */
|
||||
ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2);
|
||||
else
|
||||
ins_size = WOPN_INST_SIZE_V1;
|
||||
|
||||
if(length < ins_size)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
|
||||
WOPN_parseInstrument(&file->inst, cursor, version, 0);
|
||||
GO_FORWARD(ins_size);
|
||||
|
||||
return WOPN_ERR_OK;
|
||||
#undef GO_FORWARD
|
||||
}
|
||||
|
||||
size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version)
|
||||
{
|
||||
size_t final_size = 0;
|
||||
size_t ins_size = 0;
|
||||
|
||||
if(version == 0)
|
||||
version = wopn_latest_version;
|
||||
|
||||
if(!file)
|
||||
return 0;
|
||||
final_size += 11 + 2 + 2 + 2 + 1;
|
||||
/*
|
||||
* Magic number,
|
||||
* Version,
|
||||
* Count of melodic banks,
|
||||
* Count of percussive banks,
|
||||
* Chip specific flags
|
||||
*/
|
||||
|
||||
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 >= 2)
|
||||
ins_size = WOPN_INST_SIZE_V2;
|
||||
else
|
||||
ins_size = WOPN_INST_SIZE_V1;
|
||||
/* Melodic instruments */
|
||||
final_size += (ins_size * 128) * file->banks_count_melodic;
|
||||
/* Percussive instruments */
|
||||
final_size += (ins_size * 128) * file->banks_count_percussion;
|
||||
|
||||
return final_size;
|
||||
}
|
||||
|
||||
size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version)
|
||||
{
|
||||
size_t final_size = 0;
|
||||
size_t ins_size = 0;
|
||||
|
||||
if(version == 0)
|
||||
version = wopn_latest_version;
|
||||
|
||||
if(!file)
|
||||
return 0;
|
||||
final_size += 11 + 1;
|
||||
/*
|
||||
* Magic number,
|
||||
* is percussive instrument
|
||||
*/
|
||||
|
||||
/* Version */
|
||||
if (version > 1)
|
||||
final_size += 2;
|
||||
|
||||
if(version > 1)
|
||||
/* Skip sounding delays are not part of single-instrument file
|
||||
* two sizes of uint16_t will be subtracted */
|
||||
ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2);
|
||||
else
|
||||
ins_size = WOPN_INST_SIZE_V1;
|
||||
final_size += ins_size;
|
||||
|
||||
return final_size;
|
||||
}
|
||||
|
||||
int WOPN_SaveBankToMem(WOPNFile *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_percussive = force_gm ? 1 : file->banks_count_percussion;
|
||||
|
||||
WOPNBank *bankslots[2];
|
||||
uint16_t bankslots_sizes[2];
|
||||
|
||||
if(version == 0)
|
||||
version = wopn_latest_version;
|
||||
|
||||
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
|
||||
|
||||
if(length < 11)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
if(version > 1)
|
||||
memcpy(cursor, wopn2_magic2, 11);
|
||||
else
|
||||
memcpy(cursor, wopn2_magic1, 11);
|
||||
GO_FORWARD(11);
|
||||
|
||||
if(version > 1)
|
||||
{
|
||||
if(length < 2)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
fromUint16LE(version, cursor);
|
||||
GO_FORWARD(2);
|
||||
}
|
||||
|
||||
if(length < 2)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
fromUint16BE(banks_melodic, cursor);
|
||||
GO_FORWARD(2);
|
||||
|
||||
if(length < 2)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
fromUint16BE(banks_percussive, cursor);
|
||||
GO_FORWARD(2);
|
||||
|
||||
if(length < 1)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
cursor[0] = file->lfo_freq;
|
||||
GO_FORWARD(1);
|
||||
|
||||
bankslots[0] = file->banks_melodic;
|
||||
bankslots_sizes[0] = banks_melodic;
|
||||
bankslots[1] = file->banks_percussive;
|
||||
bankslots_sizes[1] = banks_percussive;
|
||||
|
||||
if(version >= 2)
|
||||
{
|
||||
for(i = 0; i < 2; i++)
|
||||
{
|
||||
for(j = 0; j < bankslots_sizes[i]; j++)
|
||||
{
|
||||
if(length < 34)
|
||||
return WOPN_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 >= 2)
|
||||
ins_size = WOPN_INST_SIZE_V2;
|
||||
else
|
||||
ins_size = WOPN_INST_SIZE_V1;
|
||||
for(i = 0; i < 2; i++)
|
||||
{
|
||||
if(length < (ins_size * 128) * (size_t)bankslots_sizes[i])
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
|
||||
for(j = 0; j < bankslots_sizes[i]; j++)
|
||||
{
|
||||
for(k = 0; k < 128; k++)
|
||||
{
|
||||
WOPNInstrument *ins = &bankslots[i][j].ins[k];
|
||||
WOPN_writeInstrument(ins, cursor, version, 1);
|
||||
GO_FORWARD(ins_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WOPN_ERR_OK;
|
||||
#undef GO_FORWARD
|
||||
}
|
||||
|
||||
int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version)
|
||||
{
|
||||
uint8_t *cursor = (uint8_t *)dest_mem;
|
||||
uint16_t ins_size;
|
||||
|
||||
if(!cursor)
|
||||
return WOPN_ERR_NULL_POINTER;
|
||||
|
||||
if(version == 0)
|
||||
version = wopn_latest_version;
|
||||
|
||||
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
|
||||
|
||||
{/* Magic number */
|
||||
if(length < 11)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
if(version > 1)
|
||||
memcpy(cursor, opni_magic2, 11);
|
||||
else
|
||||
memcpy(cursor, opni_magic1, 11);
|
||||
GO_FORWARD(11);
|
||||
}
|
||||
|
||||
if (version > 1)
|
||||
{/* Version code */
|
||||
if(length < 2)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
fromUint16LE(version, cursor);
|
||||
GO_FORWARD(2);
|
||||
}
|
||||
|
||||
{/* is drum flag */
|
||||
if(length < 1)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
*cursor = file->is_drum;
|
||||
GO_FORWARD(1);
|
||||
}
|
||||
|
||||
if(version > 1)
|
||||
/* Skip sounding delays are not part of single-instrument file
|
||||
* two sizes of uint16_t will be subtracted */
|
||||
ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2);
|
||||
else
|
||||
ins_size = WOPN_INST_SIZE_V1;
|
||||
|
||||
if(length < ins_size)
|
||||
return WOPN_ERR_UNEXPECTED_ENDING;
|
||||
|
||||
WOPN_writeInstrument(&file->inst, cursor, version, 0);
|
||||
GO_FORWARD(ins_size);
|
||||
|
||||
return WOPN_ERR_OK;
|
||||
#undef GO_FORWARD
|
||||
}
|
250
src/sound/opnmidi/wopn/wopn_file.h
Normal file
250
src/sound/opnmidi/wopn/wopn_file.h
Normal file
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup
|
||||
*
|
||||
* Copyright (c) 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 WOPN_FILE_H
|
||||
#define WOPN_FILE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !defined(__STDC_VERSION__) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ < 199901L)) \
|
||||
|| defined(__STRICT_ANSI__) || !defined(__cplusplus)
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed short int int16_t;
|
||||
typedef unsigned short int uint16_t;
|
||||
#endif
|
||||
|
||||
/* Volume scaling model implemented in the libOPNMIDI */
|
||||
typedef enum WOPN_VolumeModel
|
||||
{
|
||||
WOPN_VM_Generic = 0
|
||||
} WOPN_VolumeModel;
|
||||
|
||||
typedef enum WOPN_InstrumentFlags
|
||||
{
|
||||
/* Is pseudo eight-operator (two 4-operator voices) instrument */
|
||||
WOPN_Ins_Pseudo8op = 0x01,
|
||||
/* Is a blank instrument entry */
|
||||
WOPN_Ins_IsBlank = 0x02,
|
||||
|
||||
/* Mask of the flags range */
|
||||
WOPN_Ins_ALL_MASK = 0x03
|
||||
} WOPN_InstrumentFlags;
|
||||
|
||||
/* Error codes */
|
||||
typedef enum WOPN_ErrorCodes
|
||||
{
|
||||
WOPN_ERR_OK = 0,
|
||||
/* Magic number is not maching */
|
||||
WOPN_ERR_BAD_MAGIC,
|
||||
/* Too short file */
|
||||
WOPN_ERR_UNEXPECTED_ENDING,
|
||||
/* Zero banks count */
|
||||
WOPN_ERR_INVALID_BANKS_COUNT,
|
||||
/* Version of file is newer than supported by current version of library */
|
||||
WOPN_ERR_NEWER_VERSION,
|
||||
/* Out of memory */
|
||||
WOPN_ERR_OUT_OF_MEMORY,
|
||||
/* Given null pointer memory data */
|
||||
WOPN_ERR_NULL_POINTER
|
||||
} WOPN_ErrorCodes;
|
||||
|
||||
/* OPN2 Oerators data */
|
||||
typedef struct WOPNOperator
|
||||
{
|
||||
/* Detune and frequency multiplication register data */
|
||||
uint8_t dtfm_30;
|
||||
/* Total level register data */
|
||||
uint8_t level_40;
|
||||
/* Rate scale and attack register data */
|
||||
uint8_t rsatk_50;
|
||||
/* Amplitude modulation enable and Decay-1 register data */
|
||||
uint8_t amdecay1_60;
|
||||
/* Decay-2 register data */
|
||||
uint8_t decay2_70;
|
||||
/* Sustain and Release register data */
|
||||
uint8_t susrel_80;
|
||||
/* SSG-EG register data */
|
||||
uint8_t ssgeg_90;
|
||||
} WOPNOperator;
|
||||
|
||||
/* Instrument entry */
|
||||
typedef struct WOPNInstrument
|
||||
{
|
||||
/* 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_offset;
|
||||
/* Reserved */
|
||||
int8_t midi_velocity_offset;
|
||||
/* Percussion MIDI base tone number at which this drum will be played */
|
||||
uint8_t percussion_key_number;
|
||||
/* Enum WOPN_InstrumentFlags */
|
||||
uint8_t inst_flags;
|
||||
/* Feedback and Algorithm register data */
|
||||
uint8_t fbalg;
|
||||
/* LFO Sensitivity register data */
|
||||
uint8_t lfosens;
|
||||
/* Operators register data */
|
||||
WOPNOperator 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;
|
||||
} WOPNInstrument;
|
||||
|
||||
/* Bank entry */
|
||||
typedef struct WOPNBank
|
||||
{
|
||||
/* 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 */
|
||||
WOPNInstrument ins[128];
|
||||
} WOPNBank;
|
||||
|
||||
/* Instrument data file */
|
||||
typedef struct OPNIFile
|
||||
{
|
||||
/* Version of instrument file */
|
||||
uint16_t version;
|
||||
/* Is this a percussion instrument */
|
||||
uint8_t is_drum;
|
||||
/* Instrument data */
|
||||
WOPNInstrument inst;
|
||||
} OPNIFile;
|
||||
|
||||
/* Bank data file */
|
||||
typedef struct WOPNFile
|
||||
{
|
||||
/* 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;
|
||||
/* Chip global LFO enable flag and frequency register data */
|
||||
uint8_t lfo_freq;
|
||||
/* Reserved (Enum WOPN_VolumeModel) */
|
||||
uint8_t volume_model;
|
||||
/* dynamically allocated data Melodic banks array */
|
||||
WOPNBank *banks_melodic;
|
||||
/* dynamically allocated data Percussive banks array */
|
||||
WOPNBank *banks_percussive;
|
||||
} WOPNFile;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize blank WOPN 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 WOPN data structure or NULL when out of memory or incorrectly given banks counts
|
||||
*/
|
||||
extern WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks);
|
||||
|
||||
/**
|
||||
* @brief Clean up WOPN data file (all allocated bank arrays will be fried too)
|
||||
* @param file pointer to heap-allocated WOPN data structure
|
||||
*/
|
||||
extern void WOPN_Free(WOPNFile *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 WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Load WOPN bank file from the memory.
|
||||
* WOPN data structure will be allocated. (don't forget to clear it with WOPN_Free() after use!)
|
||||
* @param mem Pointer to memory block contains raw WOPN 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 WOPN file data structure or NULL if any error has occouped
|
||||
*/
|
||||
extern WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error);
|
||||
|
||||
/**
|
||||
* @brief Load WOPI instrument file from the memory.
|
||||
* You must allocate OPNIFile structure by yourself and give the pointer to it.
|
||||
* @param file Pointer to destinition OPNIFile 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 WOPN_ErrorCodes enumeration
|
||||
*/
|
||||
extern int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Calculate the size of the output memory block
|
||||
* @param file Heap-allocated WOPN file data structure
|
||||
* @param version Destinition version of the file
|
||||
* @return Size of the raw WOPN file data
|
||||
*/
|
||||
extern size_t WOPN_CalculateBankFileSize(WOPNFile *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 WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version);
|
||||
|
||||
/**
|
||||
* @brief Write raw WOPN into given memory block
|
||||
* @param file Heap-allocated WOPN file data structure
|
||||
* @param dest_mem Destinition memory block pointer
|
||||
* @param length Length of destinition memory block
|
||||
* @param version Wanted WOPN version
|
||||
* @param force_gm Force GM set in saved bank file
|
||||
* @return Error code or 0 on success
|
||||
*/
|
||||
extern int WOPN_SaveBankToMem(WOPNFile *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 WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WOPN_FILE_H */
|
|
@ -2224,6 +2224,10 @@ ADVSNDMNU_RUNPCMRATE = "Run emulator at PCM rate";
|
|||
ADVSNDMNU_ADLNUMCHIPS = "Number of emulated OPL chips";
|
||||
ADVSNDMNU_VLMODEL = "Volume model";
|
||||
ADVSNDMNU_OPNNUMCHIPS = "Number of emulated OPN chips";
|
||||
ADVSNDMNU_ADLCUSTOMBANK = "Use custom WOPL bank";
|
||||
ADVSNDMNU_OPLBANKFILE = "WOPL Bank file";
|
||||
ADVSNDMNU_OPNCUSTOMBANK = "Use custom WOPN bank";
|
||||
ADVSNDMNU_OPNBANKFILE = "WOPN Bank file";
|
||||
|
||||
// ADLMIDI's emulation cores
|
||||
|
||||
|
|
|
@ -1691,6 +1691,17 @@ OptionMenu ADLBankMenu protected
|
|||
Title "$ADVSNDMNU_OPLBANK"
|
||||
}
|
||||
|
||||
OptionMenu ADLMIDICustomBanksMenu protected
|
||||
{
|
||||
Title "$ADVSNDMNU_OPLBANKFILE"
|
||||
}
|
||||
|
||||
OptionMenu OPNMIDICustomBanksMenu protected
|
||||
{
|
||||
Title "$ADVSNDMNU_OPNBANKFILE"
|
||||
}
|
||||
|
||||
|
||||
/*=======================================
|
||||
*
|
||||
* Module Replayer Options Menu
|
||||
|
@ -1840,11 +1851,16 @@ OptionMenu ModReplayerOptions protected
|
|||
OptionMenu ADLOptions protected
|
||||
{
|
||||
Title "$ADVSNDMNU_ADLMIDI"
|
||||
LabeledSubmenu "$ADVSNDMNU_OPLBANK", "adl_bank", "ADLBankMenu"
|
||||
Option "$ADVSNDMNU_OPLCORES", "adl_emulator_id", "ADLOplCores"
|
||||
Option "$ADVSNDMNU_RUNPCMRATE", "adl_run_at_pcm_rate", "OnOff"
|
||||
Slider "$ADVSNDMNU_ADLNUMCHIPS", "adl_chips_count", 1, 32, 1, 0
|
||||
Option "$ADVSNDMNU_OPLFULLPAN", "adl_fullpan", "OnOff"
|
||||
Option "$ADVSNDMNU_VLMODEL", "adl_volume_model", "AdlVolumeModels"
|
||||
StaticText ""
|
||||
LabeledSubmenu "$ADVSNDMNU_OPLBANK", "adl_bank", "ADLBankMenu"
|
||||
StaticText ""
|
||||
Option "$ADVSNDMNU_ADLCUSTOMBANK", "adl_use_custom_bank", "OnOff"
|
||||
LabeledSubmenu "$ADVSNDMNU_OPLBANKFILE", "adl_custom_bank", "ADLMIDICustomBanksMenu"
|
||||
}
|
||||
|
||||
OptionMenu OPNOptions protected
|
||||
|
@ -1853,6 +1869,10 @@ OptionMenu ModReplayerOptions protected
|
|||
Option "$ADVSNDMNU_OPNCORES", "opn_emulator_id", "OpnCores"
|
||||
Option "$ADVSNDMNU_RUNPCMRATE", "opn_run_at_pcm_rate", "OnOff"
|
||||
Slider "$ADVSNDMNU_OPNNUMCHIPS", "opn_chips_count", 1, 32, 1, 0
|
||||
Option "$ADVSNDMNU_OPLFULLPAN", "opn_fullpan", "OnOff"
|
||||
StaticText ""
|
||||
Option "$ADVSNDMNU_OPNCUSTOMBANK", "opn_use_custom_bank", "OnOff"
|
||||
LabeledSubmenu "$ADVSNDMNU_OPNBANKFILE", "opn_custom_bank", "OPNMIDICustomBanksMenu"
|
||||
}
|
||||
|
||||
/*=======================================
|
||||
|
@ -1944,7 +1964,7 @@ OptionMenu VideoModeMenu protected
|
|||
OptionMenu CustomResolutionMenu protected
|
||||
{
|
||||
Title "$VIDMNU_RESPRESETTTL"
|
||||
|
||||
|
||||
StaticText "$VIDMNU_RESPRESETHEAD"
|
||||
StaticText ""
|
||||
StaticText "$VIDMNU_ASPECT43"
|
||||
|
@ -2250,7 +2270,7 @@ OptionMenu "GLTextureGLOptions" protected
|
|||
Option "$GLTEXMNU_TEXFILTER", gl_texture_filter, "FilterModes"
|
||||
Option "$GLTEXMNU_ANISOTROPIC", gl_texture_filter_anisotropic, "Anisotropy"
|
||||
Option "$GLTEXMNU_ENABLEHIRES", gl_texture_usehires, "YesNo"
|
||||
|
||||
|
||||
ifOption(MMX)
|
||||
{
|
||||
Option "$GLTEXMNU_HQRESIZE", gl_texture_hqresize, "HqResizeModes"
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue