mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-01 08:31:45 +00:00
Merge branch 'adlMIDI'
# Conflicts: # src/sound/musicformats/music_midistream.cpp
This commit is contained in:
commit
fd801b8b94
21 changed files with 21287 additions and 11 deletions
|
@ -834,6 +834,14 @@ set( FASTMATH_SOURCES
|
|||
gl/models/gl_models.cpp
|
||||
r_data/models/models.cpp
|
||||
r_data/matrix.cpp
|
||||
sound/adlmidi/adldata.cpp
|
||||
sound/adlmidi/adlmidi.cpp
|
||||
sound/adlmidi/adlmidi_load.cpp
|
||||
sound/adlmidi/adlmidi_midiplay.cpp
|
||||
sound/adlmidi/adlmidi_opl3.cpp
|
||||
sound/adlmidi/adlmidi_private.cpp
|
||||
sound/adlmidi/dbopl.cpp
|
||||
sound/adlmidi/nukedopl3.c
|
||||
|
||||
)
|
||||
|
||||
|
@ -1136,6 +1144,7 @@ set (PCH_SOURCES
|
|||
sound/i_music.cpp
|
||||
sound/i_sound.cpp
|
||||
sound/i_soundfont.cpp
|
||||
sound/mididevices/music_adlmidi_mididevice.cpp
|
||||
sound/mididevices/music_opldumper_mididevice.cpp
|
||||
sound/mididevices/music_opl_mididevice.cpp
|
||||
sound/mididevices/music_timiditypp_mididevice.cpp
|
||||
|
@ -1349,6 +1358,7 @@ install(TARGETS zdoom
|
|||
COMPONENT "Game executable")
|
||||
|
||||
source_group("Audio Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound/.+")
|
||||
source_group("Audio Files\\ADL MIDI" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound//.+")
|
||||
source_group("Audio Files\\OPL Synth" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound/oplsynth/.+")
|
||||
source_group("Audio Files\\OPL Synth\\DOSBox" FILES sound/oplsynth/dosbox/opl.cpp sound/oplsynth/dosbox/opl.h)
|
||||
source_group("Audio Files\\Timidity" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound/timidity/.+")
|
||||
|
|
10492
src/sound/adlmidi/adldata.cpp
Normal file
10492
src/sound/adlmidi/adldata.cpp
Normal file
File diff suppressed because it is too large
Load diff
65
src/sound/adlmidi/adldata.hh
Normal file
65
src/sound/adlmidi/adldata.hh
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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) 2016 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLDATA_H
|
||||
#define ADLDATA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern const 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[];
|
||||
|
||||
extern const struct adlinsdata
|
||||
{
|
||||
enum { Flag_Pseudo4op = 0x01, Flag_NoSound = 0x02 };
|
||||
|
||||
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;
|
||||
double voice2_fine_tune;
|
||||
} adlins[];
|
||||
int maxAdlBanks();
|
||||
extern const unsigned short banks[][256];
|
||||
extern const char* const banknames[];
|
||||
|
||||
/**
|
||||
* @brief Bank global setup
|
||||
*/
|
||||
extern const struct AdlBankSetup
|
||||
{
|
||||
int volumeModel;
|
||||
bool deepTremolo;
|
||||
bool deepVibrato;
|
||||
bool adLibPercussions;
|
||||
bool scaleModulators;
|
||||
} adlbanksetup[];
|
||||
|
||||
#endif //ADLDATA_H
|
869
src/sound/adlmidi/adlmidi.cpp
Normal file
869
src/sound/adlmidi/adlmidi.cpp
Normal file
|
@ -0,0 +1,869 @@
|
|||
/*
|
||||
* 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 "adlmidi_private.hpp"
|
||||
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
#define MaxCards 1
|
||||
#define MaxCards_STR "1" //Why not just "#MaxCards" ? Watcom fails to pass this with "syntax error" :-P
|
||||
#else
|
||||
#define MaxCards 100
|
||||
#define MaxCards_STR "100"
|
||||
#endif
|
||||
|
||||
static ADL_Version adl_version = {
|
||||
ADLMIDI_VERSION_MAJOR,
|
||||
ADLMIDI_VERSION_MINOR,
|
||||
ADLMIDI_VERSION_PATCHLEVEL
|
||||
};
|
||||
|
||||
/*---------------------------EXPORTS---------------------------*/
|
||||
|
||||
ADLMIDI_EXPORT struct ADL_MIDIPlayer *adl_init(long sample_rate)
|
||||
{
|
||||
ADL_MIDIPlayer *midi_device;
|
||||
midi_device = (ADL_MIDIPlayer *)malloc(sizeof(ADL_MIDIPlayer));
|
||||
if(!midi_device)
|
||||
{
|
||||
ADLMIDI_ErrorString = "Can't initialize ADLMIDI: out of memory!";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MIDIplay *player = new MIDIplay(static_cast<unsigned long>(sample_rate));
|
||||
if(!player)
|
||||
{
|
||||
free(midi_device);
|
||||
ADLMIDI_ErrorString = "Can't initialize ADLMIDI: out of memory!";
|
||||
return NULL;
|
||||
}
|
||||
midi_device->adl_midiPlayer = player;
|
||||
adlRefreshNumCards(midi_device);
|
||||
return midi_device;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numCards)
|
||||
{
|
||||
if(device == NULL)
|
||||
return -2;
|
||||
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.NumCards = static_cast<unsigned int>(numCards);
|
||||
if(play->m_setup.NumCards < 1 || play->m_setup.NumCards > MaxCards)
|
||||
{
|
||||
play->setErrorString("number of chips may only be 1.." MaxCards_STR ".\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
play->opl.NumCards = play->m_setup.NumCards;
|
||||
adl_reset(device);
|
||||
|
||||
return adlRefreshNumCards(device);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_getNumChips(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(device == NULL)
|
||||
return -2;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(play)
|
||||
return (int)play->m_setup.NumCards;
|
||||
return -2;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank)
|
||||
{
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
ADL_UNUSED(device);
|
||||
ADL_UNUSED(bank);
|
||||
ADLMIDI_ErrorString = "This build of libADLMIDI has no embedded banks. Please load bank by using of adl_openBankFile() or adl_openBankData() functions instead of adl_setBank()";
|
||||
return -1;
|
||||
#else
|
||||
const uint32_t NumBanks = static_cast<uint32_t>(maxAdlBanks());
|
||||
int32_t bankno = bank;
|
||||
|
||||
if(bankno < 0)
|
||||
bankno = 0;
|
||||
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(static_cast<uint32_t>(bankno) >= NumBanks)
|
||||
{
|
||||
#if 0 //ndef __WATCOMC__
|
||||
std::stringstream s;
|
||||
s << "bank number may only be 0.." << (NumBanks - 1) << ".\n";
|
||||
play->setErrorString(s.str());
|
||||
#else
|
||||
play->setErrorString("Selected embedded bank is not exists!\n");
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
play->m_setup.AdlBank = static_cast<uint32_t>(bankno);
|
||||
play->opl.setEmbeddedBank(play->m_setup.AdlBank);
|
||||
play->applySetup();
|
||||
|
||||
return adlRefreshNumCards(device);
|
||||
#endif
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_getBanksCount()
|
||||
{
|
||||
return maxAdlBanks();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const char *const *adl_getBankNames()
|
||||
{
|
||||
return banknames;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_setNumFourOpsChn(ADL_MIDIPlayer *device, int ops4)
|
||||
{
|
||||
if(!device)
|
||||
return -1;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if((unsigned int)ops4 > 6 * play->m_setup.NumCards)
|
||||
{
|
||||
#if 0 //ndef __WATCOMC__
|
||||
std::stringstream s;
|
||||
s << "number of four-op channels may only be 0.." << (6 * (play->m_setup.NumCards)) << " when " << play->m_setup.NumCards << " OPL3 cards are used.\n";
|
||||
play->setErrorString(s.str());
|
||||
#else
|
||||
play->setErrorString("number of four-op channels may not be more than 6 channels per each OPL3 chip!\n");
|
||||
#endif
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
play->m_setup.NumFourOps = static_cast<unsigned int>(ops4);
|
||||
play->opl.NumFourOps = play->m_setup.NumFourOps;
|
||||
|
||||
return 0; //adlRefreshNumCards(device);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_getNumFourOpsChn(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return -1;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(play)
|
||||
return (int)play->m_setup.NumFourOps;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setPercMode(ADL_MIDIPlayer *device, int percmod)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.AdlPercussionMode = percmod;
|
||||
play->opl.AdlPercussionMode = play->m_setup.AdlPercussionMode;
|
||||
play->opl.updateFlags();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setHVibrato(ADL_MIDIPlayer *device, int hvibro)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.HighVibratoMode = hvibro;
|
||||
play->opl.HighVibratoMode = play->m_setup.HighVibratoMode;
|
||||
play->opl.updateDeepFlags();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setHTremolo(ADL_MIDIPlayer *device, int htremo)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.HighTremoloMode = htremo;
|
||||
play->opl.HighTremoloMode = play->m_setup.HighTremoloMode;
|
||||
play->opl.updateDeepFlags();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setScaleModulators(ADL_MIDIPlayer *device, int smod)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.ScaleModulators = smod;
|
||||
play->opl.ScaleModulators = play->m_setup.ScaleModulators;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.loopingIsEnabled = (loopEn != 0);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int logvol)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.LogarithmicVolumes = logvol;
|
||||
play->opl.LogarithmicVolumes = play->m_setup.LogarithmicVolumes;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel)
|
||||
{
|
||||
if(!device) return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.VolumeModel = volumeModel;
|
||||
play->opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(volumeModel));
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_openBankFile(struct ADL_MIDIPlayer *device, const char *filePath)
|
||||
{
|
||||
if(device && device->adl_midiPlayer)
|
||||
{
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.tick_skip_samples_delay = 0;
|
||||
if(!play->LoadBank(filePath))
|
||||
{
|
||||
std::string err = play->getErrorString();
|
||||
if(err.empty())
|
||||
play->setErrorString("ADL MIDI: Can't load file");
|
||||
return -1;
|
||||
}
|
||||
else return adlRefreshNumCards(device);
|
||||
}
|
||||
|
||||
ADLMIDI_ErrorString = "Can't load file: ADLMIDI is not initialized";
|
||||
return -1;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_openBankData(struct ADL_MIDIPlayer *device, const void *mem, unsigned long size)
|
||||
{
|
||||
if(device && device->adl_midiPlayer)
|
||||
{
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.tick_skip_samples_delay = 0;
|
||||
if(!play->LoadBank(mem, static_cast<size_t>(size)))
|
||||
{
|
||||
std::string err = play->getErrorString();
|
||||
if(err.empty())
|
||||
play->setErrorString("ADL MIDI: Can't load data from memory");
|
||||
return -1;
|
||||
}
|
||||
else return adlRefreshNumCards(device);
|
||||
}
|
||||
|
||||
ADLMIDI_ErrorString = "Can't load file: ADL MIDI is not initialized";
|
||||
return -1;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, const char *filePath)
|
||||
{
|
||||
if(device && device->adl_midiPlayer)
|
||||
{
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.tick_skip_samples_delay = 0;
|
||||
if(!play->LoadMIDI(filePath))
|
||||
{
|
||||
std::string err = play->getErrorString();
|
||||
if(err.empty())
|
||||
play->setErrorString("ADL MIDI: Can't load file");
|
||||
return -1;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
ADLMIDI_ErrorString = "Can't load file: ADL MIDI is not initialized";
|
||||
return -1;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigned long size)
|
||||
{
|
||||
if(device && device->adl_midiPlayer)
|
||||
{
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.tick_skip_samples_delay = 0;
|
||||
if(!play->LoadMIDI(mem, static_cast<size_t>(size)))
|
||||
{
|
||||
std::string err = play->getErrorString();
|
||||
if(err.empty())
|
||||
play->setErrorString("ADL MIDI: Can't load data from memory");
|
||||
return -1;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
ADLMIDI_ErrorString = "Can't load file: ADL MIDI is not initialized";
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_emulatorName()
|
||||
{
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
return "DosBox";
|
||||
#else
|
||||
return "Nuked";
|
||||
#endif
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_linkedLibraryVersion()
|
||||
{
|
||||
return ADLMIDI_VERSION;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const ADL_Version *adl_linkedVersion()
|
||||
{
|
||||
return &adl_version;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_errorString()
|
||||
{
|
||||
return ADLMIDI_ErrorString.c_str();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_errorInfo(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return adl_errorString();
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!play)
|
||||
return adl_errorString();
|
||||
return play->getErrorString().c_str();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_getMusicTitle(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return "";
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!play)
|
||||
return "";
|
||||
return play->musTitle.c_str();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_close(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(device->adl_midiPlayer)
|
||||
delete reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
device->adl_midiPlayer = NULL;
|
||||
free(device);
|
||||
device = NULL;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_reset(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->m_setup.tick_skip_samples_delay = 0;
|
||||
play->opl.Reset(play->m_setup.PCM_RATE);
|
||||
play->ch.clear();
|
||||
play->ch.resize(play->opl.NumChannels);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT double adl_totalTimeLength(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return -1.0;
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->timeLength();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT double adl_loopStartTime(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return -1.0;
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->getLoopStart();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT double adl_loopEndTime(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return -1.0;
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->getLoopEnd();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return -1.0;
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->tell();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->seek(seconds);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->rewind();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo)
|
||||
{
|
||||
if(!device || (tempo <= 0.0))
|
||||
return;
|
||||
reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->setTempo(tempo);
|
||||
}
|
||||
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return "";
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musTitle.c_str();
|
||||
}
|
||||
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return "";
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musCopyright.c_str();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return 0;
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musTrackTitles.size();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, size_t index)
|
||||
{
|
||||
if(!device)
|
||||
return 0;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(index >= play->musTrackTitles.size())
|
||||
return "INVALID";
|
||||
return play->musTrackTitles[index].c_str();
|
||||
}
|
||||
|
||||
|
||||
ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return 0;
|
||||
return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musMarkers.size();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, size_t index)
|
||||
{
|
||||
struct Adl_MarkerEntry marker;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!device || !play || (index >= play->musMarkers.size()))
|
||||
{
|
||||
marker.label = "INVALID";
|
||||
marker.pos_time = 0.0;
|
||||
marker.pos_ticks = 0;
|
||||
return marker;
|
||||
}
|
||||
else
|
||||
{
|
||||
MIDIplay::MIDI_MarkerEntry &mk = play->musMarkers[index];
|
||||
marker.label = mk.label.c_str();
|
||||
marker.pos_time = mk.pos_time;
|
||||
marker.pos_ticks = (unsigned long)mk.pos_ticks;
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEventHook rawEventHook, void *userData)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->hooks.onEvent = rawEventHook;
|
||||
play->hooks.onEvent_userData = userData;
|
||||
}
|
||||
|
||||
/* Set note hook */
|
||||
ADLMIDI_EXPORT void adl_setNoteHook(struct ADL_MIDIPlayer *device, ADL_NoteHook noteHook, void *userData)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->hooks.onNote = noteHook;
|
||||
play->hooks.onNote_userData = userData;
|
||||
}
|
||||
|
||||
/* Set debug message hook */
|
||||
ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_DebugMessageHook debugMessageHook, void *userData)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
play->hooks.onDebugMessage = debugMessageHook;
|
||||
play->hooks.onDebugMessage_userData = userData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline static void SendStereoAudio(int &samples_requested,
|
||||
ssize_t &in_size,
|
||||
short *_in,
|
||||
ssize_t out_pos,
|
||||
short *_out)
|
||||
{
|
||||
if(!in_size)
|
||||
return;
|
||||
size_t offset = static_cast<size_t>(out_pos);
|
||||
size_t inSamples = static_cast<size_t>(in_size * 2);
|
||||
size_t maxSamples = static_cast<size_t>(samples_requested) - offset;
|
||||
size_t toCopy = std::min(maxSamples, inSamples);
|
||||
std::memcpy(_out + out_pos, _in, toCopy * sizeof(short));
|
||||
}
|
||||
|
||||
|
||||
ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out)
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
(void)device;
|
||||
(void)sampleCount;
|
||||
(void)out;
|
||||
return 0;
|
||||
#else
|
||||
sampleCount -= sampleCount % 2; //Avoid even sample requests
|
||||
if(sampleCount < 0)
|
||||
return 0;
|
||||
if(!device)
|
||||
return 0;
|
||||
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
MIDIplay::Setup &setup = player->m_setup;
|
||||
|
||||
ssize_t gotten_len = 0;
|
||||
ssize_t n_periodCountStereo = 512;
|
||||
//ssize_t n_periodCountPhys = n_periodCountStereo * 2;
|
||||
int left = sampleCount;
|
||||
bool hasSkipped = setup.tick_skip_samples_delay > 0;
|
||||
|
||||
while(left > 0)
|
||||
{
|
||||
{//...
|
||||
const double eat_delay = setup.delay < setup.maxdelay ? setup.delay : setup.maxdelay;
|
||||
if(hasSkipped)
|
||||
{
|
||||
size_t samples = setup.tick_skip_samples_delay > sampleCount ? sampleCount : setup.tick_skip_samples_delay;
|
||||
n_periodCountStereo = samples / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
setup.delay -= eat_delay;
|
||||
setup.carry += setup.PCM_RATE * eat_delay;
|
||||
n_periodCountStereo = static_cast<ssize_t>(setup.carry);
|
||||
setup.carry -= n_periodCountStereo;
|
||||
}
|
||||
|
||||
//if(setup.SkipForward > 0)
|
||||
// setup.SkipForward -= 1;
|
||||
//else
|
||||
{
|
||||
if((player->atEnd) && (setup.delay <= 0.0))
|
||||
break;//Stop to fetch samples at reaching the song end with disabled loop
|
||||
|
||||
ssize_t leftSamples = left / 2;
|
||||
if(n_periodCountStereo > leftSamples)
|
||||
{
|
||||
setup.tick_skip_samples_delay = (n_periodCountStereo - leftSamples) * 2;
|
||||
n_periodCountStereo = leftSamples;
|
||||
}
|
||||
//! Count of stereo samples
|
||||
ssize_t in_generatedStereo = (n_periodCountStereo > 512) ? 512 : n_periodCountStereo;
|
||||
//! Total count of samples
|
||||
ssize_t in_generatedPhys = in_generatedStereo * 2;
|
||||
//! Unsigned total sample count
|
||||
//fill buffer with zeros
|
||||
int16_t *out_buf = player->outBuf;
|
||||
std::memset(out_buf, 0, static_cast<size_t>(in_generatedPhys) * sizeof(int16_t));
|
||||
unsigned int chips = player->opl.NumCards;
|
||||
if(chips == 1)
|
||||
{
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
player->opl.cards[0].GenerateArr(out_buf, &in_generatedStereo);
|
||||
in_generatedPhys = in_generatedStereo * 2;
|
||||
#else
|
||||
OPL3_GenerateStream(&player->opl.cards[0], out_buf, static_cast<Bit32u>(in_generatedStereo));
|
||||
#endif
|
||||
}
|
||||
else if(n_periodCountStereo > 0)
|
||||
{
|
||||
/* Generate data from every chip and mix result */
|
||||
for(unsigned card = 0; card < chips; ++card)
|
||||
{
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
player->opl.cards[card].GenerateArrMix(out_buf, &in_generatedStereo);
|
||||
in_generatedPhys = in_generatedStereo * 2;
|
||||
#else
|
||||
OPL3_GenerateStreamMix(&player->opl.cards[card], out_buf, static_cast<Bit32u>(in_generatedStereo));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/* Process it */
|
||||
SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out);
|
||||
|
||||
left -= (int)in_generatedPhys;
|
||||
gotten_len += (in_generatedPhys) /* - setup.stored_samples*/;
|
||||
}
|
||||
|
||||
if(hasSkipped)
|
||||
{
|
||||
setup.tick_skip_samples_delay -= n_periodCountStereo * 2;
|
||||
hasSkipped = setup.tick_skip_samples_delay > 0;
|
||||
}
|
||||
else
|
||||
setup.delay = player->Tick(eat_delay, setup.mindelay);
|
||||
|
||||
}//...
|
||||
}
|
||||
|
||||
return static_cast<int>(gotten_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
ADLMIDI_EXPORT int adl_generate(struct ADL_MIDIPlayer *device, int sampleCount, short *out)
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
(void)device;
|
||||
(void)sampleCount;
|
||||
(void)out;
|
||||
return 0;
|
||||
#else
|
||||
sampleCount -= sampleCount % 2; //Avoid even sample requests
|
||||
if(sampleCount < 0)
|
||||
return 0;
|
||||
if(!device)
|
||||
return 0;
|
||||
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
MIDIplay::Setup &setup = player->m_setup;
|
||||
|
||||
ssize_t gotten_len = 0;
|
||||
ssize_t n_periodCountStereo = 512;
|
||||
|
||||
int left = sampleCount;
|
||||
double delay = double(sampleCount) / double(setup.PCM_RATE);
|
||||
|
||||
while(left > 0)
|
||||
{
|
||||
{//...
|
||||
const double eat_delay = delay < setup.maxdelay ? delay : setup.maxdelay;
|
||||
delay -= eat_delay;
|
||||
setup.carry += setup.PCM_RATE * eat_delay;
|
||||
n_periodCountStereo = static_cast<ssize_t>(setup.carry);
|
||||
setup.carry -= n_periodCountStereo;
|
||||
|
||||
{
|
||||
ssize_t leftSamples = left / 2;
|
||||
if(n_periodCountStereo > leftSamples)
|
||||
n_periodCountStereo = leftSamples;
|
||||
//! Count of stereo samples
|
||||
ssize_t in_generatedStereo = (n_periodCountStereo > 512) ? 512 : n_periodCountStereo;
|
||||
//! Total count of samples
|
||||
ssize_t in_generatedPhys = in_generatedStereo * 2;
|
||||
//! Unsigned total sample count
|
||||
//fill buffer with zeros
|
||||
int16_t *out_buf = player->outBuf;
|
||||
std::memset(out_buf, 0, static_cast<size_t>(in_generatedPhys) * sizeof(int16_t));
|
||||
unsigned int chips = player->opl.NumCards;
|
||||
if(chips == 1)
|
||||
{
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
player->opl.cards[0].GenerateArr(out_buf, &in_generatedStereo);
|
||||
in_generatedPhys = in_generatedStereo * 2;
|
||||
#else
|
||||
OPL3_GenerateStream(&player->opl.cards[0], out_buf, static_cast<Bit32u>(in_generatedStereo));
|
||||
#endif
|
||||
}
|
||||
else if(n_periodCountStereo > 0)
|
||||
{
|
||||
/* Generate data from every chip and mix result */
|
||||
for(unsigned card = 0; card < chips; ++card)
|
||||
{
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
player->opl.cards[card].GenerateArrMix(out_buf, &in_generatedStereo);
|
||||
in_generatedPhys = in_generatedStereo * 2;
|
||||
#else
|
||||
OPL3_GenerateStreamMix(&player->opl.cards[card], out_buf, static_cast<Bit32u>(in_generatedStereo));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/* Process it */
|
||||
SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out);
|
||||
|
||||
left -= (int)in_generatedPhys;
|
||||
gotten_len += (in_generatedPhys) /* - setup.stored_samples*/;
|
||||
}
|
||||
|
||||
player->TickIteratos(eat_delay);
|
||||
}//...
|
||||
}
|
||||
|
||||
return static_cast<int>(gotten_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double seconds, double granuality)
|
||||
{
|
||||
if(!device)
|
||||
return -1.0;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return -1.0;
|
||||
return player->Tick(seconds, granuality);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return 1;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return 1;
|
||||
return (int)player->atEnd;
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_panic(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_panic();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_resetState(struct ADL_MIDIPlayer *device)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_ResetState();
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT int adl_rt_noteOn(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note, ADL_UInt8 velocity)
|
||||
{
|
||||
if(!device)
|
||||
return 0;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return 0;
|
||||
return (int)player->realTime_NoteOn(channel, note, velocity);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_noteOff(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_NoteOff(channel, note);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_noteAfterTouch(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note, ADL_UInt8 atVal)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_NoteAfterTouch(channel, note, atVal);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_channelAfterTouch(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 atVal)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_ChannelAfterTouch(channel, atVal);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_controllerChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 type, ADL_UInt8 value)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_Controller(channel, type, value);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_patchChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 patch)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_PatchChange(channel, patch);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_pitchBend(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt16 pitch)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_PitchBend(channel, pitch);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_pitchBendML(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 msb, ADL_UInt8 lsb)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_PitchBend(channel, msb, lsb);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_bankChangeLSB(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 lsb)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_BankChangeLSB(channel, lsb);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_bankChangeMSB(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 msb)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_BankChangeMSB(channel, msb);
|
||||
}
|
||||
|
||||
ADLMIDI_EXPORT void adl_rt_bankChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_SInt16 bank)
|
||||
{
|
||||
if(!device)
|
||||
return;
|
||||
MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
if(!player)
|
||||
return;
|
||||
player->realTime_BankChange(channel, (uint16_t)bank);
|
||||
}
|
293
src/sound/adlmidi/adlmidi.h
Normal file
293
src/sound/adlmidi/adlmidi.h
Normal file
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_H
|
||||
#define ADLMIDI_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ADLMIDI_VERSION_MAJOR 1
|
||||
#define ADLMIDI_VERSION_MINOR 3
|
||||
#define ADLMIDI_VERSION_PATCHLEVEL 1
|
||||
|
||||
#define ADLMIDI_TOSTR(s) #s
|
||||
#define ADLMIDI_VERSION \
|
||||
ADLMIDI_TOSTR(OPNMIDI_VERSION_MAJOR) "." \
|
||||
ADLMIDI_TOSTR(OPNMIDI_VERSION_MINOR) "." \
|
||||
ADLMIDI_TOSTR(OPNMIDI_VERSION_PATCHLEVEL)
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
|
||||
#include <stdint.h>
|
||||
typedef uint8_t ADL_UInt8;
|
||||
typedef uint16_t ADL_UInt16;
|
||||
typedef int8_t ADL_SInt8;
|
||||
typedef int16_t ADL_SInt16;
|
||||
#else
|
||||
typedef unsigned char ADL_UInt8;
|
||||
typedef unsigned short ADL_UInt16;
|
||||
typedef char ADL_SInt8;
|
||||
typedef short ADL_SInt16;
|
||||
#endif
|
||||
|
||||
enum ADLMIDI_VolumeModels
|
||||
{
|
||||
ADLMIDI_VolumeModel_AUTO = 0,
|
||||
ADLMIDI_VolumeModel_Generic,
|
||||
ADLMIDI_VolumeModel_CMF,
|
||||
ADLMIDI_VolumeModel_DMX,
|
||||
ADLMIDI_VolumeModel_APOGEE,
|
||||
ADLMIDI_VolumeModel_9X
|
||||
};
|
||||
|
||||
struct ADL_MIDIPlayer
|
||||
{
|
||||
void *adl_midiPlayer;
|
||||
};
|
||||
|
||||
/* DEPRECATED */
|
||||
#define adl_setNumCards adl_setNumChips
|
||||
|
||||
/* Sets number of emulated chips (from 1 to 100). Emulation of multiple chips exchanges polyphony limits*/
|
||||
extern int adl_setNumChips(struct ADL_MIDIPlayer *device, int numCards);
|
||||
|
||||
/* Get current number of emulated chips */
|
||||
extern int adl_getNumChips(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/* Sets a number of the patches bank from 0 to N banks. Is recommended to call adl_reset() to apply changes to already-loaded file player or real-time. */
|
||||
extern int adl_setBank(struct ADL_MIDIPlayer *device, int bank);
|
||||
|
||||
/* Returns total number of available banks */
|
||||
extern int adl_getBanksCount();
|
||||
|
||||
/* Returns pointer to array of names of every bank */
|
||||
extern const char *const *adl_getBankNames();
|
||||
|
||||
/*Sets number of 4-operator channels between all chips.
|
||||
By default, it is automatically re-calculating every bank change.
|
||||
If you want to specify custom number of four operator channels,
|
||||
please call this function after bank change (adl_setBank() or adl_openBank()),
|
||||
otherwise, value will be overwritten by auto-calculated.*/
|
||||
extern int adl_setNumFourOpsChn(struct ADL_MIDIPlayer *device, int ops4);
|
||||
|
||||
/*Get current total count of 4-operator channels between all chips*/
|
||||
extern int adl_getNumFourOpsChn(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Override Enable(1) or Disable(0) AdLib percussion mode. -1 - use bank default AdLib percussion mode*/
|
||||
extern void adl_setPercMode(struct ADL_MIDIPlayer *device, int percmod);
|
||||
|
||||
/*Override Enable(1) or Disable(0) deep vibrato state. -1 - use bank default vibrato state*/
|
||||
extern void adl_setHVibrato(struct ADL_MIDIPlayer *device, int hvibro);
|
||||
|
||||
/*Override Enable(1) or Disable(0) deep tremolo state. -1 - use bank default tremolo state*/
|
||||
extern void adl_setHTremolo(struct ADL_MIDIPlayer *device, int htremo);
|
||||
|
||||
/*Override Enable(1) or Disable(0) scaling of modulator volumes. -1 - use bank default scaling of modulator volumes*/
|
||||
extern void adl_setScaleModulators(struct ADL_MIDIPlayer *device, int smod);
|
||||
|
||||
/*Enable or disable built-in loop (built-in loop supports 'loopStart' and 'loopEnd' tags to loop specific part)*/
|
||||
extern void adl_setLoopEnabled(struct ADL_MIDIPlayer *device, int loopEn);
|
||||
|
||||
/*Enable or disable Logariphmic volume changer */
|
||||
extern void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int logvol);
|
||||
|
||||
/*Set different volume range model */
|
||||
extern void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel);
|
||||
|
||||
/*Load WOPL bank file from File System. Is recommended to call adl_reset() to apply changes to already-loaded file player or real-time.*/
|
||||
extern int adl_openBankFile(struct ADL_MIDIPlayer *device, const char *filePath);
|
||||
|
||||
/*Load WOPL bank file from memory data*/
|
||||
extern int adl_openBankData(struct ADL_MIDIPlayer *device, const void *mem, unsigned long size);
|
||||
|
||||
|
||||
/*Returns name of currently used OPL3 emulator*/
|
||||
extern const char *adl_emulatorName();
|
||||
|
||||
typedef struct {
|
||||
ADL_UInt16 major;
|
||||
ADL_UInt16 minor;
|
||||
ADL_UInt16 patch;
|
||||
} ADL_Version;
|
||||
|
||||
/*Returns string which contains a version number*/
|
||||
extern const char *adl_linkedLibraryVersion();
|
||||
|
||||
/*Returns structure which contains a version number of library */
|
||||
extern const ADL_Version *adl_linkedVersion();
|
||||
|
||||
/*Returns string which contains last error message of initialization*/
|
||||
extern const char *adl_errorString();
|
||||
|
||||
/*Returns string which contains last error message on specific device*/
|
||||
extern const char *adl_errorInfo(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Initialize ADLMIDI Player device*/
|
||||
extern struct ADL_MIDIPlayer *adl_init(long sample_rate);
|
||||
|
||||
/*Load MIDI file from File System*/
|
||||
extern int adl_openFile(struct ADL_MIDIPlayer *device, const char *filePath);
|
||||
|
||||
/*Load MIDI file from memory data*/
|
||||
extern int adl_openData(struct ADL_MIDIPlayer *device, const void *mem, unsigned long size);
|
||||
|
||||
/*Resets MIDI player*/
|
||||
extern void adl_reset(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Get total time length of current song*/
|
||||
extern double adl_totalTimeLength(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Get loop start time if presented. -1 means MIDI file has no loop points */
|
||||
extern double adl_loopStartTime(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Get loop end time if presented. -1 means MIDI file has no loop points */
|
||||
extern double adl_loopEndTime(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Get current time position in seconds*/
|
||||
extern double adl_positionTell(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Jump to absolute time position in seconds*/
|
||||
extern void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds);
|
||||
|
||||
/*Reset MIDI track position to begin */
|
||||
extern void adl_positionRewind(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Set tempo multiplier: 1.0 - original tempo, >1 - play faster, <1 - play slower */
|
||||
extern void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo);
|
||||
|
||||
/*Close and delete ADLMIDI device*/
|
||||
extern void adl_close(struct ADL_MIDIPlayer *device);
|
||||
|
||||
|
||||
|
||||
/**META**/
|
||||
|
||||
/*Returns string which contains a music title*/
|
||||
extern const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Returns string which contains a copyright string*/
|
||||
extern const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Returns count of available track titles: NOTE: there are CAN'T be associated with channel in any of event or note hooks */
|
||||
extern size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Get track title by index*/
|
||||
extern const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, size_t index);
|
||||
|
||||
struct Adl_MarkerEntry
|
||||
{
|
||||
const char *label;
|
||||
double pos_time;
|
||||
unsigned long pos_ticks;
|
||||
};
|
||||
|
||||
/*Returns count of available markers*/
|
||||
extern size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Returns the marker entry*/
|
||||
extern struct Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, size_t index);
|
||||
|
||||
|
||||
/*Take a sample buffer and iterate MIDI timers */
|
||||
extern int adl_play(struct ADL_MIDIPlayer *device, int sampleCount, short out[]);
|
||||
|
||||
/*Generate audio output from chip emulators without iteration of MIDI timers.*/
|
||||
extern int adl_generate(struct ADL_MIDIPlayer *device, int sampleCount, short *out);
|
||||
|
||||
/**
|
||||
* @brief Periodic tick handler.
|
||||
* @param device
|
||||
* @param seconds seconds since last call
|
||||
* @param granularity don't expect intervals smaller than this, in seconds
|
||||
* @return desired number of seconds until next call
|
||||
*
|
||||
* Use it for Hardware OPL3 mode or when you want to process events differently from adl_play() function.
|
||||
* DON'T USE IT TOGETHER WITH adl_play()!!!
|
||||
*/
|
||||
extern double adl_tickEvents(struct ADL_MIDIPlayer *device, double seconds, double granuality);
|
||||
|
||||
/*Returns 1 if music position has reached end*/
|
||||
extern int adl_atEnd(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/**RealTime**/
|
||||
|
||||
/*Force Off all notes on all channels*/
|
||||
extern void adl_panic(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Reset states of all controllers on all MIDI channels*/
|
||||
extern void adl_rt_resetState(struct ADL_MIDIPlayer *device);
|
||||
|
||||
/*Turn specific MIDI note ON*/
|
||||
extern int adl_rt_noteOn(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note, ADL_UInt8 velocity);
|
||||
|
||||
/*Turn specific MIDI note OFF*/
|
||||
extern void adl_rt_noteOff(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note);
|
||||
|
||||
/*Set note after-touch*/
|
||||
extern void adl_rt_noteAfterTouch(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note, ADL_UInt8 atVal);
|
||||
/*Set channel after-touch*/
|
||||
extern void adl_rt_channelAfterTouch(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 atVal);
|
||||
|
||||
/*Apply controller change*/
|
||||
extern void adl_rt_controllerChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 type, ADL_UInt8 value);
|
||||
|
||||
/*Apply patch change*/
|
||||
extern void adl_rt_patchChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 patch);
|
||||
|
||||
/*Apply pitch bend change*/
|
||||
extern void adl_rt_pitchBend(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt16 pitch);
|
||||
/*Apply pitch bend change*/
|
||||
extern void adl_rt_pitchBendML(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 msb, ADL_UInt8 lsb);
|
||||
|
||||
/*Change LSB of the bank*/
|
||||
extern void adl_rt_bankChangeLSB(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 lsb);
|
||||
/*Change MSB of the bank*/
|
||||
extern void adl_rt_bankChangeMSB(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 msb);
|
||||
/*Change bank by absolute signed value*/
|
||||
extern void adl_rt_bankChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_SInt16 bank);
|
||||
|
||||
|
||||
/**Hooks**/
|
||||
|
||||
typedef void (*ADL_RawEventHook)(void *userdata, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 channel, const ADL_UInt8 *data, size_t len);
|
||||
typedef void (*ADL_NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend);
|
||||
typedef void (*ADL_DebugMessageHook)(void *userdata, const char *fmt, ...);
|
||||
|
||||
/* Set raw MIDI event hook */
|
||||
extern void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEventHook rawEventHook, void *userData);
|
||||
|
||||
/* Set note hook */
|
||||
extern void adl_setNoteHook(struct ADL_MIDIPlayer *device, ADL_NoteHook noteHook, void *userData);
|
||||
|
||||
/* Set debug message hook */
|
||||
extern void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_DebugMessageHook debugMessageHook, void *userData);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ADLMIDI_H */
|
57
src/sound/adlmidi/adlmidi.hpp
Normal file
57
src/sound/adlmidi/adlmidi.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_HPP
|
||||
#define ADLMIDI_HPP
|
||||
|
||||
#include "adlmidi.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
class OPL3;
|
||||
class MIDIplay;
|
||||
|
||||
class AdlInstrumentTester
|
||||
{
|
||||
uint32_t cur_gm;
|
||||
uint32_t ins_idx;
|
||||
std::vector<uint32_t> adl_ins_list;
|
||||
OPL3 *opl;
|
||||
MIDIplay * play;
|
||||
|
||||
public:
|
||||
AdlInstrumentTester(ADL_MIDIPlayer *device);
|
||||
virtual ~AdlInstrumentTester();
|
||||
|
||||
// Find list of adlib instruments that supposedly implement this GM
|
||||
void FindAdlList();
|
||||
void Touch(unsigned c, unsigned volume);
|
||||
void DoNote(int note);
|
||||
void NextGM(int offset);
|
||||
void NextAdl(int offset);
|
||||
bool HandleInputChar(char ch);
|
||||
};
|
||||
|
||||
#endif //ADLMIDI_HPP
|
||||
|
693
src/sound/adlmidi/adlmidi_load.cpp
Normal file
693
src/sound/adlmidi/adlmidi_load.cpp
Normal file
|
@ -0,0 +1,693 @@
|
|||
/*
|
||||
* 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 "adlmidi_private.hpp"
|
||||
|
||||
#include "adlmidi_mus2mid.h"
|
||||
#include "adlmidi_xmi2mid.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(const std::string &filename)
|
||||
{
|
||||
fileReader file;
|
||||
file.openFile(filename.c_str());
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(const void *data, size_t size)
|
||||
{
|
||||
fileReader file;
|
||||
file.openData(data, size);
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* WOPL-needed misc functions */
|
||||
static uint16_t toUint16LE(const uint8_t *arr)
|
||||
{
|
||||
uint16_t num = arr[0];
|
||||
num |= ((arr[1] << 8) & 0xFF00);
|
||||
return num;
|
||||
}
|
||||
|
||||
static uint16_t toUint16BE(const uint8_t *arr)
|
||||
{
|
||||
uint16_t num = arr[1];
|
||||
num |= ((arr[0] << 8) & 0xFF00);
|
||||
return num;
|
||||
}
|
||||
|
||||
static int16_t toSint16BE(const uint8_t *arr)
|
||||
{
|
||||
int16_t num = *reinterpret_cast<const int8_t *>(&arr[0]);
|
||||
num *= 1 << 8;
|
||||
num |= arr[1];
|
||||
return num;
|
||||
}
|
||||
|
||||
static const char *wopl3_magic = "WOPL3-BANK\0";
|
||||
static const uint16_t wopl_latest_version = 3;
|
||||
|
||||
#define WOPL_INST_SIZE_V2 62
|
||||
#define WOPL_INST_SIZE_V3 66
|
||||
|
||||
enum WOPL_InstrumentFlags
|
||||
{
|
||||
WOPL_Flags_NONE = 0,
|
||||
WOPL_Flag_Enable4OP = 0x01,
|
||||
WOPL_Flag_Pseudo4OP = 0x02
|
||||
};
|
||||
|
||||
struct WOPL_Inst
|
||||
{
|
||||
bool fourOps;
|
||||
char padding[7];
|
||||
struct adlinsdata adlins;
|
||||
struct adldata op[2];
|
||||
uint16_t ms_sound_kon;
|
||||
uint16_t ms_sound_koff;
|
||||
};
|
||||
|
||||
static bool readInstrument(MIDIplay::fileReader &file, WOPL_Inst &ins, uint16_t &version, bool isPercussion = false)
|
||||
{
|
||||
uint8_t idata[WOPL_INST_SIZE_V3];
|
||||
if(version >= 3)
|
||||
{
|
||||
if(file.read(idata, 1, WOPL_INST_SIZE_V3) != WOPL_INST_SIZE_V3)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(file.read(idata, 1, WOPL_INST_SIZE_V2) != WOPL_INST_SIZE_V2)
|
||||
return false;
|
||||
}
|
||||
|
||||
//strncpy(ins.name, char_p(idata), 32);
|
||||
ins.op[0].finetune = (int8_t)toSint16BE(idata + 32);
|
||||
ins.op[1].finetune = (int8_t)toSint16BE(idata + 34);
|
||||
//ins.velocity_offset = int8_t(idata[36]);
|
||||
|
||||
ins.adlins.voice2_fine_tune = 0.0;
|
||||
int8_t voice2_fine_tune = int8_t(idata[37]);
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
if(voice2_fine_tune == 1)
|
||||
ins.adlins.voice2_fine_tune = 0.000025;
|
||||
else if(voice2_fine_tune == -1)
|
||||
ins.adlins.voice2_fine_tune = -0.000025;
|
||||
else
|
||||
ins.adlins.voice2_fine_tune = ((voice2_fine_tune * 15.625) / 1000.0);
|
||||
}
|
||||
|
||||
ins.adlins.tone = isPercussion ? idata[38] : 0;
|
||||
|
||||
uint8_t flags = idata[39];
|
||||
ins.adlins.flags = (flags & WOPL_Flag_Enable4OP) && (flags & WOPL_Flag_Pseudo4OP) ? adlinsdata::Flag_Pseudo4op : 0;
|
||||
ins.fourOps = (flags & WOPL_Flag_Enable4OP) || (flags & WOPL_Flag_Pseudo4OP);
|
||||
|
||||
ins.op[0].feedconn = (idata[40]);
|
||||
ins.op[1].feedconn = (idata[41]);
|
||||
|
||||
for(size_t op = 0, slt = 0; op < 4; op++, slt++)
|
||||
{
|
||||
size_t off = 42 + size_t(op) * 5;
|
||||
// ins.setAVEKM(op, idata[off + 0]);//AVEKM
|
||||
// ins.setAtDec(op, idata[off + 2]);//AtDec
|
||||
// ins.setSusRel(op, idata[off + 3]);//SusRel
|
||||
// ins.setWaveForm(op, idata[off + 4]);//WaveForm
|
||||
// ins.setKSLL(op, idata[off + 1]);//KSLL
|
||||
ins.op[slt].carrier_E862 =
|
||||
((static_cast<uint32_t>(idata[off + 4]) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(idata[off + 3]) << 16) & 0x00FF0000) //SusRel
|
||||
| ((static_cast<uint32_t>(idata[off + 2]) << 8) & 0x0000FF00) //AtDec
|
||||
| ((static_cast<uint32_t>(idata[off + 0]) << 0) & 0x000000FF); //AVEKM
|
||||
ins.op[slt].carrier_40 = idata[off + 1];//KSLL
|
||||
|
||||
op++;
|
||||
off = 42 + size_t(op) * 5;
|
||||
ins.op[slt].modulator_E862 =
|
||||
((static_cast<uint32_t>(idata[off + 4]) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(idata[off + 3]) << 16) & 0x00FF0000) //SusRel
|
||||
| ((static_cast<uint32_t>(idata[off + 2]) << 8) & 0x0000FF00) //AtDec
|
||||
| ((static_cast<uint32_t>(idata[off + 0]) << 0) & 0x000000FF); //AVEKM
|
||||
ins.op[slt].modulator_40 = idata[off + 1];//KSLL
|
||||
}
|
||||
|
||||
if(version >= 3)
|
||||
{
|
||||
ins.ms_sound_kon = toUint16BE(idata + 62);
|
||||
ins.ms_sound_koff = toUint16BE(idata + 64);
|
||||
}
|
||||
else
|
||||
{
|
||||
ins.ms_sound_kon = 1000;
|
||||
ins.ms_sound_koff = 500;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
|
||||
{
|
||||
size_t fsize;
|
||||
ADL_UNUSED(fsize);
|
||||
if(!fr.isValid())
|
||||
{
|
||||
errorStringOut = "Custom bank: Invalid data stream!";
|
||||
return false;
|
||||
}
|
||||
|
||||
char magic[32];
|
||||
std::memset(magic, 0, 32);
|
||||
|
||||
uint16_t version = 0;
|
||||
|
||||
uint16_t count_melodic_banks = 1;
|
||||
uint16_t count_percusive_banks = 1;
|
||||
|
||||
if(fr.read(magic, 1, 11) != 11)
|
||||
{
|
||||
errorStringOut = "Custom bank: Can't read magic number!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(std::strncmp(magic, wopl3_magic, 11) != 0)
|
||||
{
|
||||
errorStringOut = "Custom bank: Invalid magic number!";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t version_raw[2];
|
||||
if(fr.read(version_raw, 1, 2) != 2)
|
||||
{
|
||||
errorStringOut = "Custom bank: Can't read version!";
|
||||
return false;
|
||||
}
|
||||
|
||||
version = toUint16LE(version_raw);
|
||||
if(version > wopl_latest_version)
|
||||
{
|
||||
errorStringOut = "Custom bank: Unsupported WOPL version!";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t head[6];
|
||||
std::memset(head, 0, 6);
|
||||
if(fr.read(head, 1, 6) != 6)
|
||||
{
|
||||
errorStringOut = "Custom bank: Can't read header!";
|
||||
return false;
|
||||
}
|
||||
|
||||
count_melodic_banks = toUint16BE(head);
|
||||
count_percusive_banks = toUint16BE(head + 2);
|
||||
|
||||
if((count_melodic_banks < 1) || (count_percusive_banks < 1))
|
||||
{
|
||||
errorStringOut = "Custom bank: Too few banks in this file!";
|
||||
return false;
|
||||
}
|
||||
|
||||
/*UNUSED YET*/
|
||||
bool default_deep_vibrato = ((head[4]>>0) & 0x01);
|
||||
bool default_deep_tremolo = ((head[4]>>1) & 0x01);
|
||||
|
||||
//5'th byte reserved for Deep-Tremolo and Deep-Vibrato flags
|
||||
m_setup.HighTremoloMode = default_deep_tremolo;
|
||||
m_setup.HighVibratoMode = default_deep_vibrato;
|
||||
//6'th byte reserved for ADLMIDI's default volume model
|
||||
m_setup.VolumeModel = (int)head[5];
|
||||
|
||||
opl.dynamic_melodic_banks.clear();
|
||||
opl.dynamic_percussion_banks.clear();
|
||||
|
||||
opl.setEmbeddedBank(m_setup.AdlBank);
|
||||
|
||||
if(version >= 2)//Read bank meta-entries
|
||||
{
|
||||
for(uint16_t i = 0; i < count_melodic_banks; i++)
|
||||
{
|
||||
uint8_t bank_meta[34];
|
||||
if(fr.read(bank_meta, 1, 34) != 34)
|
||||
{
|
||||
errorStringOut = "Custom bank: Fail to read melodic bank meta-data!";
|
||||
return false;
|
||||
}
|
||||
uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
|
||||
size_t offset = opl.dynamic_melodic_banks.size();
|
||||
opl.dynamic_melodic_banks[bank] = offset;
|
||||
//strncpy(bankMeta.name, char_p(bank_meta), 32);
|
||||
}
|
||||
|
||||
for(uint16_t i = 0; i < count_percusive_banks; i++)
|
||||
{
|
||||
uint8_t bank_meta[34];
|
||||
if(fr.read(bank_meta, 1, 34) != 34)
|
||||
{
|
||||
errorStringOut = "Custom bank: Fail to read percussion bank meta-data!";
|
||||
return false;
|
||||
}
|
||||
uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
|
||||
size_t offset = opl.dynamic_percussion_banks.size();
|
||||
opl.dynamic_percussion_banks[bank] = offset;
|
||||
//strncpy(bankMeta.name, char_p(bank_meta), 32);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t total = 128 * count_melodic_banks;
|
||||
bool readPercussion = false;
|
||||
|
||||
tryAgain:
|
||||
for(uint16_t i = 0; i < total; i++)
|
||||
{
|
||||
WOPL_Inst ins;
|
||||
std::memset(&ins, 0, sizeof(WOPL_Inst));
|
||||
if(!readInstrument(fr, ins, version, readPercussion))
|
||||
{
|
||||
opl.setEmbeddedBank(m_setup.AdlBank);
|
||||
errorStringOut = "Custom bank: Fail to read instrument!";
|
||||
return false;
|
||||
}
|
||||
ins.adlins.ms_sound_kon = ins.ms_sound_kon;
|
||||
ins.adlins.ms_sound_koff = ins.ms_sound_koff;
|
||||
ins.adlins.adlno1 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
|
||||
opl.dynamic_instruments.push_back(ins.op[0]);
|
||||
ins.adlins.adlno2 = ins.adlins.adlno1;
|
||||
if(ins.fourOps)
|
||||
{
|
||||
ins.adlins.adlno2 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
|
||||
opl.dynamic_instruments.push_back(ins.op[1]);
|
||||
}
|
||||
opl.dynamic_metainstruments.push_back(ins.adlins);
|
||||
}
|
||||
|
||||
if(!readPercussion)
|
||||
{
|
||||
total = 128 * count_percusive_banks;
|
||||
readPercussion = true;
|
||||
goto tryAgain;
|
||||
}
|
||||
|
||||
opl.AdlBank = ~0u; // Use dynamic banks!
|
||||
//Percussion offset is count of instruments multipled to count of melodic banks
|
||||
opl.dynamic_percussion_offset = 128 * count_melodic_banks;
|
||||
|
||||
applySetup();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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_metainstruments.size() < 256))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**** 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_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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0)
|
||||
{
|
||||
opl.dynamic_instruments.clear();
|
||||
opl.dynamic_metainstruments.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 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]);*/
|
||||
struct adldata adl;
|
||||
struct adlinsdata adlins;
|
||||
adl.modulator_E862 =
|
||||
((static_cast<uint32_t>(InsData[8] & 0x07) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(InsData[6]) << 16) & 0x00FF0000) //Sustain/Release
|
||||
| ((static_cast<uint32_t>(InsData[4]) << 8) & 0x0000FF00) //Attack/Decay
|
||||
| ((static_cast<uint32_t>(InsData[0]) << 0) & 0x000000FF); //MultKEVA
|
||||
adl.carrier_E862 =
|
||||
((static_cast<uint32_t>(InsData[9] & 0x07) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(InsData[7]) << 16) & 0x00FF0000) //Sustain/Release
|
||||
| ((static_cast<uint32_t>(InsData[5]) << 8) & 0x0000FF00) //Attack/Decay
|
||||
| ((static_cast<uint32_t>(InsData[1]) << 0) & 0x000000FF); //MultKEVA
|
||||
adl.modulator_40 = InsData[2];
|
||||
adl.carrier_40 = InsData[3];
|
||||
adl.feedconn = InsData[10] & 0x0F;
|
||||
adl.finetune = 0;
|
||||
adlins.adlno1 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag);
|
||||
adlins.adlno2 = adlins.adlno1;
|
||||
adlins.ms_sound_kon = 1000;
|
||||
adlins.ms_sound_koff = 500;
|
||||
adlins.tone = 0;
|
||||
adlins.flags = 0;
|
||||
adlins.voice2_fine_tune = 0.0;
|
||||
opl.dynamic_metainstruments.push_back(adlins);
|
||||
opl.dynamic_instruments.push_back(adl);
|
||||
}
|
||||
|
||||
fr.seeku(mus_start, SEEK_SET);
|
||||
TrackCount = 1;
|
||||
DeltaTicks = (size_t)ticks;
|
||||
opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead
|
||||
//std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo);
|
||||
opl.LogarithmicVolumes = true;
|
||||
opl.AdlPercussionMode = true;
|
||||
opl.m_musicMode = OPL3::MODE_CMF;
|
||||
opl.m_volumeScale = OPL3::VOLUME_CMF;
|
||||
}
|
||||
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.LogarithmicVolumes = true;
|
||||
//opl.CartoonersVolumes = true;
|
||||
opl.m_musicMode = OPL3::MODE_RSXX;
|
||||
opl.m_volumeScale = OPL3::VOLUME_CMF;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
TrackData.clear();
|
||||
TrackData.resize(TrackCount, std::vector<uint8_t>());
|
||||
//CurrentPosition.track.clear();
|
||||
//CurrentPosition.track.resize(TrackCount);
|
||||
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
|
||||
//Tempo = 1000000l * InvDeltaTicks;
|
||||
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks));
|
||||
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 (ALPHA!!!)
|
||||
if(!buildTrackData())
|
||||
{
|
||||
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
|
||||
return false;
|
||||
}
|
||||
|
||||
opl.Reset(m_setup.PCM_RATE); // Reset AdLib
|
||||
//opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously)
|
||||
ch.clear();
|
||||
ch.resize(opl.NumChannels);
|
||||
return true;
|
||||
}
|
2672
src/sound/adlmidi/adlmidi_midiplay.cpp
Normal file
2672
src/sound/adlmidi/adlmidi_midiplay.cpp
Normal file
File diff suppressed because it is too large
Load diff
49
src/sound/adlmidi/adlmidi_mus2mid.h
Normal file
49
src/sound/adlmidi/adlmidi_mus2mid.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* MUS2MIDI: DMX (DOOM) MUS to MIDI Library Header
|
||||
*
|
||||
* Copyright (C) 2014-2016 Bret Curtis
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library 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 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef MUSLIB_H
|
||||
#define MUSLIB_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __DJGPP__
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef signed long int32_t;
|
||||
typedef unsigned long uint32_t;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
int AdlMidi_mus2midi(uint8_t *in, uint32_t insize,
|
||||
uint8_t **out, uint32_t *outsize,
|
||||
uint16_t frequency);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MUSLIB_H */
|
626
src/sound/adlmidi/adlmidi_opl3.cpp
Normal file
626
src/sound/adlmidi/adlmidi_opl3.cpp
Normal file
|
@ -0,0 +1,626 @@
|
|||
/*
|
||||
* 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 "adlmidi_private.hpp"
|
||||
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
static const unsigned OPLBase = 0x388;
|
||||
#endif
|
||||
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
/*
|
||||
Dummy data which replaces adldata.cpp banks database
|
||||
*/
|
||||
|
||||
const struct adldata adl[] =
|
||||
{
|
||||
{0, 0, (unsigned char)'\0', (unsigned char)'\0', (unsigned char)'\0', 0}
|
||||
};
|
||||
|
||||
const struct adlinsdata adlins[] =
|
||||
{
|
||||
{0, 0, 0, 0, 0, 0, 0.0}
|
||||
};
|
||||
|
||||
int maxAdlBanks()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const unsigned short banks[][256] = {{0}};
|
||||
const char *const banknames[] = {"<Embedded banks are disabled>"};
|
||||
const AdlBankSetup adlbanksetup[] = {{0, 1, 1, 0, 0}};
|
||||
#endif
|
||||
|
||||
static const unsigned short Operators[23 * 2] =
|
||||
{
|
||||
// Channels 0-2
|
||||
0x000, 0x003, 0x001, 0x004, 0x002, 0x005, // operators 0, 3, 1, 4, 2, 5
|
||||
// Channels 3-5
|
||||
0x008, 0x00B, 0x009, 0x00C, 0x00A, 0x00D, // operators 6, 9, 7,10, 8,11
|
||||
// Channels 6-8
|
||||
0x010, 0x013, 0x011, 0x014, 0x012, 0x015, // operators 12,15, 13,16, 14,17
|
||||
// Same for second card
|
||||
0x100, 0x103, 0x101, 0x104, 0x102, 0x105, // operators 18,21, 19,22, 20,23
|
||||
0x108, 0x10B, 0x109, 0x10C, 0x10A, 0x10D, // operators 24,27, 25,28, 26,29
|
||||
0x110, 0x113, 0x111, 0x114, 0x112, 0x115, // operators 30,33, 31,34, 32,35
|
||||
// Channel 18
|
||||
0x010, 0x013, // operators 12,15
|
||||
// Channel 19
|
||||
0x014, 0xFFF, // operator 16
|
||||
// Channel 19
|
||||
0x012, 0xFFF, // operator 14
|
||||
// Channel 19
|
||||
0x015, 0xFFF, // operator 17
|
||||
// Channel 19
|
||||
0x011, 0xFFF
|
||||
}; // operator 13
|
||||
|
||||
static const unsigned short Channels[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)
|
||||
0x006, 0x007, 0x008, 0xFFF, 0xFFF
|
||||
}; // <- hw percussions, 0xFFF = no support for pitch/pan
|
||||
|
||||
/*
|
||||
In OPL3 mode:
|
||||
0 1 2 6 7 8 9 10 11 16 17 18
|
||||
op0 op1 op2 op12 op13 op14 op18 op19 op20 op30 op31 op32
|
||||
op3 op4 op5 op15 op16 op17 op21 op22 op23 op33 op34 op35
|
||||
3 4 5 13 14 15
|
||||
op6 op7 op8 op24 op25 op26
|
||||
op9 op10 op11 op27 op28 op29
|
||||
Ports:
|
||||
+0 +1 +2 +10 +11 +12 +100 +101 +102 +110 +111 +112
|
||||
+3 +4 +5 +13 +14 +15 +103 +104 +105 +113 +114 +115
|
||||
+8 +9 +A +108 +109 +10A
|
||||
+B +C +D +10B +10C +10D
|
||||
|
||||
Percussion:
|
||||
bassdrum = op(0): 0xBD bit 0x10, operators 12 (0x10) and 15 (0x13) / channels 6, 6b
|
||||
snare = op(3): 0xBD bit 0x08, operators 16 (0x14) / channels 7b
|
||||
tomtom = op(4): 0xBD bit 0x04, operators 14 (0x12) / channels 8
|
||||
cym = op(5): 0xBD bit 0x02, operators 17 (0x17) / channels 8b
|
||||
hihat = op(2): 0xBD bit 0x01, operators 13 (0x11) / channels 7
|
||||
|
||||
|
||||
In OPTi mode ("extended FM" in 82C924, 82C925, 82C931 chips):
|
||||
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
||||
op0 op4 op6 op10 op12 op16 op18 op22 op24 op28 op30 op34 op36 op38 op40 op42 op44 op46
|
||||
op1 op5 op7 op11 op13 op17 op19 op23 op25 op29 op31 op35 op37 op39 op41 op43 op45 op47
|
||||
op2 op8 op14 op20 op26 op32
|
||||
op3 op9 op15 op21 op27 op33 for a total of 6 quad + 12 dual
|
||||
Ports: ???
|
||||
*/
|
||||
|
||||
|
||||
const adlinsdata &OPL3::GetAdlMetaIns(size_t n)
|
||||
{
|
||||
return (n & DynamicMetaInstrumentTag) ?
|
||||
dynamic_metainstruments[n & ~DynamicMetaInstrumentTag]
|
||||
: adlins[n];
|
||||
}
|
||||
|
||||
size_t OPL3::GetAdlMetaNumber(size_t midiins)
|
||||
{
|
||||
return (AdlBank == ~0u) ?
|
||||
(midiins | DynamicMetaInstrumentTag)
|
||||
: banks[AdlBank][midiins];
|
||||
}
|
||||
|
||||
const adldata &OPL3::GetAdlIns(size_t insno)
|
||||
{
|
||||
return (insno & DynamicInstrumentTag)
|
||||
? dynamic_instruments[insno & ~DynamicInstrumentTag]
|
||||
: adl[insno];
|
||||
}
|
||||
|
||||
void OPL3::setEmbeddedBank(unsigned int bank)
|
||||
{
|
||||
AdlBank = bank;
|
||||
//Embedded banks are supports 128:128 GM set only
|
||||
dynamic_percussion_offset = 128;
|
||||
dynamic_melodic_banks.clear();
|
||||
dynamic_percussion_banks.clear();
|
||||
}
|
||||
|
||||
|
||||
OPL3::OPL3() :
|
||||
dynamic_percussion_offset(128),
|
||||
DynamicInstrumentTag(0x8000u),
|
||||
DynamicMetaInstrumentTag(0x4000000u),
|
||||
NumCards(1),
|
||||
AdlBank(0),
|
||||
NumFourOps(0),
|
||||
HighTremoloMode(false),
|
||||
HighVibratoMode(false),
|
||||
AdlPercussionMode(false),
|
||||
LogarithmicVolumes(false),
|
||||
//CartoonersVolumes(false),
|
||||
m_musicMode(MODE_MIDI),
|
||||
m_volumeScale(VOLUME_Generic)
|
||||
{}
|
||||
|
||||
void OPL3::Poke(size_t card, uint32_t index, uint32_t value)
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
(void)card;
|
||||
unsigned o = index >> 8;
|
||||
unsigned port = OPLBase + o * 2;
|
||||
|
||||
#ifdef __DJGPP__
|
||||
outportb(port, index);
|
||||
for(unsigned c = 0; c < 6; ++c) inportb(port);
|
||||
outportb(port + 1, value);
|
||||
for(unsigned c = 0; c < 35; ++c) inportb(port);
|
||||
#endif//__DJGPP__
|
||||
|
||||
#ifdef __WATCOMC__
|
||||
outp(port, index);
|
||||
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//ADLMIDI_HW_OPL
|
||||
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
cards[card].WriteReg(index, static_cast<uint8_t>(value));
|
||||
#else
|
||||
OPL3_WriteReg(&cards[card], static_cast<Bit16u>(index), static_cast<Bit8u>(value));
|
||||
#endif
|
||||
|
||||
#endif//ADLMIDI_HW_OPL
|
||||
}
|
||||
|
||||
void OPL3::PokeN(size_t card, uint16_t index, uint8_t value)
|
||||
{
|
||||
#ifdef ADLMIDI_HW_OPL
|
||||
(void)card;
|
||||
unsigned o = index >> 8;
|
||||
unsigned port = OPLBase + o * 2;
|
||||
|
||||
#ifdef __DJGPP__
|
||||
outportb(port, index);
|
||||
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);
|
||||
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
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
cards[card].WriteReg(static_cast<Bit32u>(index), value);
|
||||
#else
|
||||
OPL3_WriteReg(&cards[card], index, value);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void OPL3::NoteOff(size_t c)
|
||||
{
|
||||
size_t card = c / 23, cc = c % 23;
|
||||
|
||||
if(cc >= 18)
|
||||
{
|
||||
regBD[card] &= ~(0x10 >> (cc - 18));
|
||||
Poke(card, 0xBD, regBD[card]);
|
||||
return;
|
||||
}
|
||||
|
||||
Poke(card, 0xB0 + Channels[cc], pit[c] & 0xDF);
|
||||
}
|
||||
|
||||
void OPL3::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071
|
||||
{
|
||||
unsigned card = c / 23, cc = c % 23;
|
||||
unsigned x = 0x2000;
|
||||
|
||||
if(hertz < 0 || hertz > 131071) // Avoid infinite loop
|
||||
return;
|
||||
|
||||
while(hertz >= 1023.5)
|
||||
{
|
||||
hertz /= 2.0; // Calculate octave
|
||||
x += 0x400;
|
||||
}
|
||||
|
||||
x += static_cast<unsigned int>(hertz + 0.5);
|
||||
unsigned chn = Channels[cc];
|
||||
|
||||
if(cc >= 18)
|
||||
{
|
||||
regBD[card] |= (0x10 >> (cc - 18));
|
||||
Poke(card, 0x0BD, regBD[card]);
|
||||
x &= ~0x2000u;
|
||||
//x |= 0x800; // for test
|
||||
}
|
||||
|
||||
if(chn != 0xFFF)
|
||||
{
|
||||
Poke(card, 0xA0 + chn, x & 0xFF);
|
||||
Poke(card, 0xB0 + chn, pit[c] = static_cast<uint8_t>(x >> 8));
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
|
||||
{
|
||||
if(volume > 63)
|
||||
volume = 63;
|
||||
|
||||
size_t card = c / 23, cc = c % 23;
|
||||
size_t i = ins[c];
|
||||
uint16_t o1 = Operators[cc * 2 + 0];
|
||||
uint16_t o2 = Operators[cc * 2 + 1];
|
||||
const adldata &adli = GetAdlIns(i);
|
||||
uint8_t x = adli.modulator_40, y = adli.carrier_40;
|
||||
uint16_t mode = 1; // 2-op AM
|
||||
|
||||
if(four_op_category[c] == 0 || four_op_category[c] == 3)
|
||||
{
|
||||
mode = adli.feedconn & 1; // 2-op FM or 2-op AM
|
||||
}
|
||||
else if(four_op_category[c] == 1 || four_op_category[c] == 2)
|
||||
{
|
||||
size_t i0, i1;
|
||||
|
||||
if(four_op_category[c] == 1)
|
||||
{
|
||||
i0 = i;
|
||||
i1 = ins[c + 3];
|
||||
mode = 2; // 4-op xx-xx ops 1&2
|
||||
}
|
||||
else
|
||||
{
|
||||
i0 = ins[c - 3];
|
||||
i1 = i;
|
||||
mode = 6; // 4-op xx-xx ops 3&4
|
||||
}
|
||||
|
||||
mode += (GetAdlIns(i0).feedconn & 1) + (GetAdlIns(i1).feedconn & 1) * 2;
|
||||
}
|
||||
|
||||
static const bool do_ops[10][2] =
|
||||
{
|
||||
{ false, true }, /* 2 op FM */
|
||||
{ true, true }, /* 2 op AM */
|
||||
{ false, false }, /* 4 op FM-FM ops 1&2 */
|
||||
{ true, false }, /* 4 op AM-FM ops 1&2 */
|
||||
{ false, true }, /* 4 op FM-AM ops 1&2 */
|
||||
{ true, false }, /* 4 op AM-AM ops 1&2 */
|
||||
{ false, true }, /* 4 op FM-FM ops 3&4 */
|
||||
{ false, true }, /* 4 op AM-FM ops 3&4 */
|
||||
{ false, true }, /* 4 op FM-AM ops 3&4 */
|
||||
{ true, true } /* 4 op AM-AM ops 3&4 */
|
||||
};
|
||||
|
||||
if(m_musicMode == MODE_RSXX)
|
||||
{
|
||||
Poke(card, 0x40 + o1, x);
|
||||
if(o2 != 0xFFF)
|
||||
Poke(card, 0x40 + o2, y - volume / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool do_modulator = do_ops[ mode ][ 0 ] || ScaleModulators;
|
||||
bool do_carrier = do_ops[ mode ][ 1 ] || 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;
|
||||
|
||||
if(brightness != 127)
|
||||
{
|
||||
brightness = static_cast<uint8_t>(::round(127.0 * ::sqrt((static_cast<double>(brightness)) * (1.0 / 127.0))) / 2.0);
|
||||
if(!do_modulator)
|
||||
modulator = (modulator | 63) - brightness + brightness * (modulator & 63) / 63;
|
||||
if(!do_carrier)
|
||||
carrier = (carrier | 63) - brightness + brightness * (carrier & 63) / 63;
|
||||
}
|
||||
|
||||
Poke(card, 0x40 + o1, modulator);
|
||||
if(o2 != 0xFFF)
|
||||
Poke(card, 0x40 + o2, carrier);
|
||||
}
|
||||
|
||||
// Correct formula (ST3, AdPlug):
|
||||
// 63-((63-(instrvol))/63)*chanvol
|
||||
// Reduces to (tested identical):
|
||||
// 63 - chanvol + chanvol*instrvol/63
|
||||
// Also (slower, floats):
|
||||
// 63 + chanvol * (instrvol / 63.0 - 1)
|
||||
}
|
||||
|
||||
/*
|
||||
void OPL3::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127
|
||||
{
|
||||
if(LogarithmicVolumes)
|
||||
Touch_Real(c, volume * 127 / (127 * 127 * 127) / 2);
|
||||
else
|
||||
{
|
||||
// The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A)
|
||||
Touch_Real(c, volume > 8725 ? static_cast<unsigned int>(std::log(volume) * 11.541561 + (0.5 - 104.22845)) : 0);
|
||||
// The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A)
|
||||
//Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0);
|
||||
}
|
||||
}*/
|
||||
|
||||
void OPL3::Patch(uint16_t c, size_t i)
|
||||
{
|
||||
uint16_t card = c / 23, cc = c % 23;
|
||||
static const uint8_t data[4] = {0x20, 0x60, 0x80, 0xE0};
|
||||
ins[c] = i;
|
||||
uint16_t o1 = Operators[cc * 2 + 0];
|
||||
uint16_t o2 = Operators[cc * 2 + 1];
|
||||
const adldata &adli = GetAdlIns(i);
|
||||
unsigned x = adli.modulator_E862, y = adli.carrier_E862;
|
||||
|
||||
for(unsigned a = 0; a < 4; ++a, x >>= 8, y >>= 8)
|
||||
{
|
||||
Poke(card, data[a] + o1, x & 0xFF);
|
||||
if(o2 != 0xFFF)
|
||||
Poke(card, data[a] + o2, y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::Pan(unsigned c, unsigned value)
|
||||
{
|
||||
unsigned card = c / 23, cc = c % 23;
|
||||
|
||||
if(Channels[cc] != 0xFFF)
|
||||
Poke(card, 0xC0 + Channels[cc], GetAdlIns(ins[c]).feedconn | value);
|
||||
}
|
||||
|
||||
void OPL3::Silence() // Silence all OPL channels.
|
||||
{
|
||||
for(unsigned c = 0; c < NumChannels; ++c)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::updateDeepFlags()
|
||||
{
|
||||
for(unsigned card = 0; card < NumCards; ++card)
|
||||
{
|
||||
Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80
|
||||
+ HighVibratoMode * 0x40
|
||||
+ AdlPercussionMode * 0x20));
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel)
|
||||
{
|
||||
switch(volumeModel)
|
||||
{
|
||||
case ADLMIDI_VolumeModel_AUTO://Do nothing until restart playing
|
||||
break;
|
||||
|
||||
case ADLMIDI_VolumeModel_Generic:
|
||||
m_volumeScale = OPL3::VOLUME_Generic;
|
||||
break;
|
||||
|
||||
case ADLMIDI_VolumeModel_CMF:
|
||||
LogarithmicVolumes = true;
|
||||
m_volumeScale = OPL3::VOLUME_CMF;
|
||||
break;
|
||||
|
||||
case ADLMIDI_VolumeModel_DMX:
|
||||
m_volumeScale = OPL3::VOLUME_DMX;
|
||||
break;
|
||||
|
||||
case ADLMIDI_VolumeModel_APOGEE:
|
||||
m_volumeScale = OPL3::VOLUME_APOGEE;
|
||||
break;
|
||||
|
||||
case ADLMIDI_VolumeModel_9X:
|
||||
m_volumeScale = OPL3::VOLUME_9X;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OPL3::Reset(unsigned long PCM_RATE)
|
||||
{
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
DBOPL::Handler emptyChip; //Constructors inside are will initialize necessary fields
|
||||
#else
|
||||
_opl3_chip emptyChip;
|
||||
std::memset(&emptyChip, 0, sizeof(_opl3_chip));
|
||||
#endif
|
||||
cards.clear();
|
||||
#endif
|
||||
(void)PCM_RATE;
|
||||
ins.clear();
|
||||
pit.clear();
|
||||
regBD.clear();
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
cards.resize(NumCards, emptyChip);
|
||||
#endif
|
||||
NumChannels = NumCards * 23;
|
||||
ins.resize(NumChannels, 189);
|
||||
pit.resize(NumChannels, 0);
|
||||
regBD.resize(NumCards, 0);
|
||||
four_op_category.resize(NumChannels, 0);
|
||||
|
||||
for(unsigned p = 0, a = 0; a < NumCards; ++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;
|
||||
}
|
||||
|
||||
static const uint16_t data[] =
|
||||
{
|
||||
0x004, 96, 0x004, 128, // Pulse timer
|
||||
0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable
|
||||
0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions
|
||||
};
|
||||
unsigned fours = NumFourOps;
|
||||
|
||||
for(unsigned card = 0; card < NumCards; ++card)
|
||||
{
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
# ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
cards[card].Init(PCM_RATE);
|
||||
# else
|
||||
OPL3_Reset(&cards[card], static_cast<Bit32u>(PCM_RATE));
|
||||
# endif
|
||||
#endif
|
||||
|
||||
for(unsigned a = 0; a < 18; ++a) Poke(card, 0xB0 + Channels[a], 0x00);
|
||||
for(unsigned a = 0; a < sizeof(data) / sizeof(*data); a += 2)
|
||||
PokeN(card, data[a], static_cast<uint8_t>(data[a + 1]));
|
||||
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);
|
||||
//fprintf(stderr, "Card %u: %u four-ops.\n", card, fours_this_card);
|
||||
fours -= fours_this_card;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
83
src/sound/adlmidi/adlmidi_private.cpp
Normal file
83
src/sound/adlmidi/adlmidi_private.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 "adlmidi_private.hpp"
|
||||
|
||||
std::string ADLMIDI_ErrorString;
|
||||
|
||||
int adlRefreshNumCards(ADL_MIDIPlayer *device)
|
||||
{
|
||||
unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
|
||||
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
|
||||
|
||||
//Automatically calculate how much 4-operator channels is necessary
|
||||
if(play->opl.AdlBank == ~0u)
|
||||
{
|
||||
//For custom bank
|
||||
for(size_t a = 0; a < play->opl.dynamic_metainstruments.size(); ++a)
|
||||
{
|
||||
size_t div = (a >= play->opl.dynamic_percussion_offset) ? 1 : 0;
|
||||
++n_total[div];
|
||||
adlinsdata &ins = play->opl.dynamic_metainstruments[a];
|
||||
if((ins.adlno1 != ins.adlno2) && ((ins.flags & adlinsdata::Flag_Pseudo4op) == 0))
|
||||
++n_fourop[div];
|
||||
}
|
||||
|
||||
play->m_setup.NumFourOps =
|
||||
(n_fourop[0] >= 128 * 7 / 8) ? play->m_setup.NumCards * 6
|
||||
: (n_fourop[0] < 128 * 1 / 8) ? (n_fourop[1] > 0 ? 4 : 0)
|
||||
: (play->m_setup.NumCards == 1 ? 1 : play->m_setup.NumCards * 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
//For embedded bank
|
||||
for(unsigned a = 0; a < 256; ++a)
|
||||
{
|
||||
unsigned insno = banks[play->m_setup.AdlBank][a];
|
||||
if(insno == 198)
|
||||
continue;
|
||||
++n_total[a / 128];
|
||||
const adlinsdata &ins = adlins[insno];
|
||||
if((ins.adlno1 != ins.adlno2) && ((ins.flags & adlinsdata::Flag_Pseudo4op) == 0))
|
||||
++n_fourop[a / 128];
|
||||
}
|
||||
|
||||
play->m_setup.NumFourOps =
|
||||
(n_fourop[0] >= (n_total[0] % 128) * 7 / 8) ? play->m_setup.NumCards * 6
|
||||
: (n_fourop[0] < (n_total[0] % 128) * 1 / 8) ? 0
|
||||
: (play->m_setup.NumCards == 1 ? 1 : play->m_setup.NumCards * 4);
|
||||
}
|
||||
|
||||
play->opl.NumFourOps = play->m_setup.NumFourOps;
|
||||
|
||||
if(n_fourop[0] >= n_total[0] * 15 / 16 && play->m_setup.NumFourOps == 0)
|
||||
{
|
||||
play->setErrorString("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;
|
||||
}
|
||||
|
996
src/sound/adlmidi/adlmidi_private.hpp
Normal file
996
src/sound/adlmidi/adlmidi_private.hpp
Normal file
|
@ -0,0 +1,996 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_PRIVATE_HPP
|
||||
#define ADLMIDI_PRIVATE_HPP
|
||||
|
||||
// Setup compiler defines useful for exporting required public API symbols in gme.cpp
|
||||
#ifndef ADLMIDI_EXPORT
|
||||
# if defined (_WIN32) && defined(ADLMIDI_BUILD_DLL)
|
||||
# define ADLMIDI_EXPORT __declspec(dllexport)
|
||||
# elif defined (LIBADLMIDI_VISIBILITY)
|
||||
# define ADLMIDI_EXPORT __attribute__((visibility ("default")))
|
||||
# else
|
||||
# define ADLMIDI_EXPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(__WATCOMC__)
|
||||
# undef NO_OLDNAMES
|
||||
# include <stdint.h>
|
||||
# ifdef _MSC_VER
|
||||
# ifdef _WIN64
|
||||
typedef __int64 ssize_t;
|
||||
# else
|
||||
typedef __int32 ssize_t;
|
||||
# endif
|
||||
# define NOMINMAX //Don't override std::min and std::max
|
||||
# else
|
||||
# ifdef _WIN64
|
||||
typedef int64_t ssize_t;
|
||||
# else
|
||||
typedef int32_t ssize_t;
|
||||
# endif
|
||||
# endif
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(__DJGPP__) || (defined(__WATCOMC__) && (defined(__DOS__) || defined(__DOS4G__) || defined(__DOS4GNZ__)))
|
||||
#define ADLMIDI_HW_OPL
|
||||
#include <conio.h>
|
||||
#ifdef __DJGPP__
|
||||
#include <pc.h>
|
||||
#include <dpmi.h>
|
||||
#include <go32.h>
|
||||
#include <sys/farptr.h>
|
||||
#include <dos.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
//#ifdef __WATCOMC__
|
||||
//#include <myset.h> //TODO: Implemnet a workaround for OpenWatcom to fix a crash while using those containers
|
||||
//#include <mymap.h>
|
||||
//#else
|
||||
#include <map>
|
||||
#include <set>
|
||||
//#endif
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <vector> // vector
|
||||
#include <deque> // deque
|
||||
#include <cmath> // exp, log, ceil
|
||||
#if defined(__WATCOMC__)
|
||||
#include <math.h> // round, sqrt
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits> // numeric_limit
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#include <deque>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable:4319)
|
||||
#pragma warning(disable:4267)
|
||||
#pragma warning(disable:4244)
|
||||
#pragma warning(disable:4146)
|
||||
#endif
|
||||
|
||||
#include "fraction.hpp"
|
||||
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
#ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
#include "dbopl.h"
|
||||
#else
|
||||
#include "nukedopl3.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "adldata.hh"
|
||||
#include "adlmidi.h" //Main API
|
||||
#ifndef ADLMIDI_DISABLE_CPP_EXTRAS
|
||||
#include "adlmidi.hpp" //Extra C++ API
|
||||
#endif
|
||||
|
||||
#define ADL_UNUSED(x) (void)x
|
||||
|
||||
extern std::string ADLMIDI_ErrorString;
|
||||
|
||||
/*
|
||||
Smart pointer for C heaps, created with malloc() call.
|
||||
FAQ: Why not std::shared_ptr? Because of Android NDK now doesn't supports it
|
||||
*/
|
||||
template<class PTR>
|
||||
class AdlMIDI_CPtr
|
||||
{
|
||||
PTR *m_p;
|
||||
public:
|
||||
AdlMIDI_CPtr() : m_p(NULL) {}
|
||||
~AdlMIDI_CPtr()
|
||||
{
|
||||
reset(NULL);
|
||||
}
|
||||
|
||||
void reset(PTR *p = NULL)
|
||||
{
|
||||
if(m_p)
|
||||
free(m_p);
|
||||
m_p = p;
|
||||
}
|
||||
|
||||
PTR *get()
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
PTR &operator*()
|
||||
{
|
||||
return *m_p;
|
||||
}
|
||||
PTR *operator->()
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
};
|
||||
|
||||
class MIDIplay;
|
||||
struct ADL_MIDIPlayer;
|
||||
class OPL3
|
||||
{
|
||||
public:
|
||||
friend class MIDIplay;
|
||||
friend class AdlInstrumentTester;
|
||||
uint32_t NumChannels;
|
||||
char ____padding[4];
|
||||
ADL_MIDIPlayer *_parent;
|
||||
#ifndef ADLMIDI_HW_OPL
|
||||
# ifdef ADLMIDI_USE_DOSBOX_OPL
|
||||
std::vector<DBOPL::Handler> cards;
|
||||
# else
|
||||
std::vector<_opl3_chip> cards;
|
||||
# endif
|
||||
#endif
|
||||
private:
|
||||
std::vector<size_t> ins; // index to adl[], cached, needed by Touch()
|
||||
std::vector<uint8_t> pit; // value poked to B0, cached, needed by NoteOff)(
|
||||
std::vector<uint8_t> regBD;
|
||||
|
||||
friend int adlRefreshNumCards(ADL_MIDIPlayer *device);
|
||||
//! Meta information about every instrument
|
||||
std::vector<adlinsdata> dynamic_metainstruments; // Replaces adlins[] when CMF file
|
||||
//! Raw instrument data ready to be sent to the chip
|
||||
std::vector<adldata> dynamic_instruments; // Replaces adl[] when CMF file
|
||||
size_t dynamic_percussion_offset;
|
||||
|
||||
typedef std::map<uint16_t, size_t> BankMap;
|
||||
BankMap dynamic_melodic_banks;
|
||||
BankMap dynamic_percussion_banks;
|
||||
const unsigned DynamicInstrumentTag /* = 0x8000u*/,
|
||||
DynamicMetaInstrumentTag /* = 0x4000000u*/;
|
||||
const adlinsdata &GetAdlMetaIns(size_t n);
|
||||
size_t GetAdlMetaNumber(size_t midiins);
|
||||
const adldata &GetAdlIns(size_t insno);
|
||||
public:
|
||||
void setEmbeddedBank(unsigned int bank);
|
||||
|
||||
//! Total number of running concurrent emulated chips
|
||||
unsigned int NumCards;
|
||||
//! Currently running embedded bank number. "~0" means usign of the custom bank.
|
||||
unsigned int AdlBank;
|
||||
//! Total number of needed four-operator channels in all running chips
|
||||
unsigned int NumFourOps;
|
||||
//! Turn global Deep Tremolo mode on
|
||||
bool HighTremoloMode;
|
||||
//! Turn global Deep Vibrato mode on
|
||||
bool HighVibratoMode;
|
||||
//! Use AdLib percussion mode
|
||||
bool AdlPercussionMode;
|
||||
//! Carriers-only are scaled by default by volume level. This flag will tell to scale modulators too.
|
||||
bool ScaleModulators;
|
||||
//! Required to play CMF files. Can be turned on by using of "CMF" volume model
|
||||
bool LogarithmicVolumes;
|
||||
// ! Required to play EA-MUS files [REPLACED WITH "m_musicMode", DEPRECATED!!!]
|
||||
//bool CartoonersVolumes;
|
||||
enum MusicMode
|
||||
{
|
||||
MODE_MIDI,
|
||||
MODE_IMF,
|
||||
MODE_CMF,
|
||||
MODE_RSXX
|
||||
} m_musicMode;
|
||||
//! Just a padding. Reserved.
|
||||
char ___padding2[3];
|
||||
//! Volume models enum
|
||||
enum VolumesScale
|
||||
{
|
||||
VOLUME_Generic,
|
||||
VOLUME_CMF,
|
||||
VOLUME_DMX,
|
||||
VOLUME_APOGEE,
|
||||
VOLUME_9X
|
||||
} m_volumeScale;
|
||||
|
||||
OPL3();
|
||||
char ____padding3[8];
|
||||
std::vector<char> four_op_category; // 1 = quad-master, 2 = quad-slave, 0 = regular
|
||||
// 3 = percussion BassDrum
|
||||
// 4 = percussion Snare
|
||||
// 5 = percussion Tom
|
||||
// 6 = percussion Crash cymbal
|
||||
// 7 = percussion Hihat
|
||||
// 8 = percussion slave
|
||||
|
||||
void Poke(size_t card, uint32_t index, uint32_t value);
|
||||
void PokeN(size_t card, uint16_t index, uint8_t value);
|
||||
|
||||
void NoteOff(size_t c);
|
||||
void NoteOn(unsigned c, double hertz);
|
||||
void Touch_Real(unsigned c, unsigned volume, uint8_t brightness = 127);
|
||||
//void Touch(unsigned c, unsigned volume)
|
||||
|
||||
void Patch(uint16_t c, size_t i);
|
||||
void Pan(unsigned c, unsigned value);
|
||||
void Silence();
|
||||
void updateFlags();
|
||||
void updateDeepFlags();
|
||||
void ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel);
|
||||
void Reset(unsigned long PCM_RATE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Hooks of the internal events
|
||||
*/
|
||||
struct MIDIEventHooks
|
||||
{
|
||||
MIDIEventHooks() :
|
||||
onEvent(NULL),
|
||||
onEvent_userData(NULL),
|
||||
onNote(NULL),
|
||||
onNote_userData(NULL),
|
||||
onDebugMessage(NULL),
|
||||
onDebugMessage_userData(NULL)
|
||||
{}
|
||||
//! Raw MIDI event hook
|
||||
typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len);
|
||||
RawEventHook onEvent;
|
||||
void *onEvent_userData;
|
||||
|
||||
//! Note on/off hooks
|
||||
typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend);
|
||||
NoteHook onNote;
|
||||
void *onNote_userData;
|
||||
|
||||
//! Library internal debug messages
|
||||
typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...);
|
||||
DebugMessageHook onDebugMessage;
|
||||
void *onDebugMessage_userData;
|
||||
};
|
||||
|
||||
|
||||
class MIDIplay
|
||||
{
|
||||
friend void adl_reset(struct ADL_MIDIPlayer*);
|
||||
public:
|
||||
MIDIplay(unsigned long sampleRate = 22050);
|
||||
|
||||
~MIDIplay()
|
||||
{}
|
||||
|
||||
void applySetup();
|
||||
|
||||
/**********************Internal structures and classes**********************/
|
||||
|
||||
/**
|
||||
* @brief A little class gives able to read filedata from disk and also from a memory segment
|
||||
*/
|
||||
class fileReader
|
||||
{
|
||||
public:
|
||||
enum relTo
|
||||
{
|
||||
SET = 0,
|
||||
CUR = 1,
|
||||
END = 2
|
||||
};
|
||||
|
||||
fileReader()
|
||||
{
|
||||
fp = NULL;
|
||||
mp = NULL;
|
||||
mp_size = 0;
|
||||
mp_tell = 0;
|
||||
}
|
||||
~fileReader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void openFile(const char *path)
|
||||
{
|
||||
#if !defined(_WIN32) || defined(__WATCOMC__)
|
||||
fp = std::fopen(path, "rb");
|
||||
#else
|
||||
wchar_t widePath[MAX_PATH];
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH);
|
||||
widePath[size] = '\0';
|
||||
fp = _wfopen(widePath, L"rb");
|
||||
#endif
|
||||
_fileName = path;
|
||||
mp = NULL;
|
||||
mp_size = 0;
|
||||
mp_tell = 0;
|
||||
}
|
||||
|
||||
void openData(const void *mem, size_t lenght)
|
||||
{
|
||||
fp = NULL;
|
||||
mp = mem;
|
||||
mp_size = lenght;
|
||||
mp_tell = 0;
|
||||
}
|
||||
|
||||
void seek(long pos, int rel_to)
|
||||
{
|
||||
if(fp)
|
||||
std::fseek(fp, pos, rel_to);
|
||||
else
|
||||
{
|
||||
switch(rel_to)
|
||||
{
|
||||
case SET:
|
||||
mp_tell = static_cast<size_t>(pos);
|
||||
break;
|
||||
|
||||
case END:
|
||||
mp_tell = mp_size - static_cast<size_t>(pos);
|
||||
break;
|
||||
|
||||
case CUR:
|
||||
mp_tell = mp_tell + static_cast<size_t>(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
if(mp_tell > mp_size)
|
||||
mp_tell = mp_size;
|
||||
}
|
||||
}
|
||||
|
||||
inline void seeku(uint64_t pos, int rel_to)
|
||||
{
|
||||
seek(static_cast<long>(pos), rel_to);
|
||||
}
|
||||
|
||||
size_t read(void *buf, size_t num, size_t size)
|
||||
{
|
||||
if(fp)
|
||||
return std::fread(buf, num, size, fp);
|
||||
else
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t maxSize = static_cast<size_t>(size * num);
|
||||
|
||||
while((pos < maxSize) && (mp_tell < mp_size))
|
||||
{
|
||||
reinterpret_cast<unsigned char *>(buf)[pos] = reinterpret_cast<unsigned const char *>(mp)[mp_tell];
|
||||
mp_tell++;
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
int getc()
|
||||
{
|
||||
if(fp)
|
||||
return std::getc(fp);
|
||||
else
|
||||
{
|
||||
if(mp_tell >= mp_size) return -1;
|
||||
int x = reinterpret_cast<unsigned const char *>(mp)[mp_tell];
|
||||
mp_tell++;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
size_t tell()
|
||||
{
|
||||
if(fp)
|
||||
return static_cast<size_t>(std::ftell(fp));
|
||||
else
|
||||
return mp_tell;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if(fp) std::fclose(fp);
|
||||
|
||||
fp = NULL;
|
||||
mp = NULL;
|
||||
mp_size = 0;
|
||||
mp_tell = 0;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
return (fp) || (mp);
|
||||
}
|
||||
|
||||
bool eof()
|
||||
{
|
||||
if(fp)
|
||||
return std::feof(fp);
|
||||
else
|
||||
return mp_tell >= mp_size;
|
||||
}
|
||||
std::string _fileName;
|
||||
std::FILE *fp;
|
||||
const void *mp;
|
||||
size_t mp_size;
|
||||
size_t mp_tell;
|
||||
};
|
||||
|
||||
// Persistent settings for each MIDI channel
|
||||
struct MIDIchannel
|
||||
{
|
||||
uint16_t portamento;
|
||||
uint8_t bank_lsb, bank_msb;
|
||||
uint8_t patch;
|
||||
uint8_t volume, expression;
|
||||
uint8_t panning, vibrato, sustain;
|
||||
char ____padding[6];
|
||||
double bend, bendsense;
|
||||
double vibpos, vibspeed, vibdepth;
|
||||
int64_t vibdelay;
|
||||
uint8_t lastlrpn, lastmrpn;
|
||||
bool nrpn;
|
||||
uint8_t brightness;
|
||||
bool is_xg_percussion;
|
||||
struct NoteInfo
|
||||
{
|
||||
// Current pressure
|
||||
uint8_t vol;
|
||||
char ____padding[1];
|
||||
// Tone selected on noteon:
|
||||
int16_t tone;
|
||||
char ____padding2[4];
|
||||
// Patch selected on noteon; index to banks[AdlBank][]
|
||||
size_t midiins;
|
||||
// Index to physical adlib data structure, adlins[]
|
||||
size_t insmeta;
|
||||
struct Phys
|
||||
{
|
||||
//! ins, inde to adl[]
|
||||
size_t insId;
|
||||
//! Is this voice must be detunable?
|
||||
bool pseudo4op;
|
||||
|
||||
bool operator==(const Phys &oth) const
|
||||
{
|
||||
return (insId == oth.insId) && (pseudo4op == oth.pseudo4op);
|
||||
}
|
||||
bool operator!=(const Phys &oth) const
|
||||
{
|
||||
return !operator==(oth);
|
||||
}
|
||||
};
|
||||
typedef std::map<uint16_t, Phys> PhysMap;
|
||||
// List of OPL3 channels it is currently occupying.
|
||||
std::map<uint16_t /*adlchn*/, Phys> phys;
|
||||
};
|
||||
typedef std::map<uint8_t, NoteInfo> activenotemap_t;
|
||||
typedef activenotemap_t::iterator activenoteiterator;
|
||||
char ____padding2[5];
|
||||
activenotemap_t activenotes;
|
||||
void reset()
|
||||
{
|
||||
portamento = 0;
|
||||
bank_lsb = 0;
|
||||
bank_msb = 0;
|
||||
patch = 0;
|
||||
volume = 100;
|
||||
expression = 127;
|
||||
panning = 0x30;
|
||||
vibrato = 0;
|
||||
sustain = 0;
|
||||
bend = 0.0;
|
||||
bendsense = 2 / 8192.0;
|
||||
vibpos = 0;
|
||||
vibspeed = 2 * 3.141592653 * 5.0;
|
||||
vibdepth = 0.5 / 127;
|
||||
vibdelay = 0;
|
||||
lastlrpn = 0;
|
||||
lastmrpn = 0;
|
||||
nrpn = false;
|
||||
brightness = 127;
|
||||
is_xg_percussion = false;
|
||||
}
|
||||
MIDIchannel()
|
||||
: activenotes()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Additional information about OPL3 channels
|
||||
struct AdlChannel
|
||||
{
|
||||
// For collisions
|
||||
struct Location
|
||||
{
|
||||
uint16_t MidCh;
|
||||
uint8_t note;
|
||||
bool operator==(const Location &b) const
|
||||
{
|
||||
return MidCh == b.MidCh && note == b.note;
|
||||
}
|
||||
bool operator< (const Location &b) const
|
||||
{
|
||||
return MidCh < b.MidCh || (MidCh == b.MidCh && note < b.note);
|
||||
}
|
||||
char ____padding[1];
|
||||
};
|
||||
struct LocationData
|
||||
{
|
||||
bool sustained;
|
||||
char ____padding[7];
|
||||
MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[]
|
||||
int64_t kon_time_until_neglible;
|
||||
int64_t vibdelay;
|
||||
};
|
||||
typedef std::map<Location, LocationData> users_t;
|
||||
users_t users;
|
||||
|
||||
// If the channel is keyoff'd
|
||||
int64_t koff_time_until_neglible;
|
||||
// For channel allocation:
|
||||
AdlChannel(): users(), koff_time_until_neglible(0) { }
|
||||
void AddAge(int64_t ms);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief MIDI Event utility container
|
||||
*/
|
||||
class MidiEvent
|
||||
{
|
||||
public:
|
||||
MidiEvent();
|
||||
|
||||
enum Types
|
||||
{
|
||||
T_UNKNOWN = 0x00,
|
||||
T_NOTEOFF = 0x08,//size == 2
|
||||
T_NOTEON = 0x09,//size == 2
|
||||
T_NOTETOUCH = 0x0A,//size == 2
|
||||
T_CTRLCHANGE = 0x0B,//size == 2
|
||||
T_PATCHCHANGE = 0x0C,//size == 1
|
||||
T_CHANAFTTOUCH = 0x0D,//size == 1
|
||||
T_WHEEL = 0x0E,//size == 2
|
||||
|
||||
T_SYSEX = 0xF0,//size == len
|
||||
T_SYSCOMSPOSPTR = 0xF2,//size == 2
|
||||
T_SYSCOMSNGSEL = 0xF3,//size == 1
|
||||
T_SYSEX2 = 0xF7,//size == len
|
||||
T_SPECIAL = 0xFF
|
||||
};
|
||||
enum SubTypes
|
||||
{
|
||||
ST_SEQNUMBER = 0x00,//size == 2
|
||||
ST_TEXT = 0x01,//size == len
|
||||
ST_COPYRIGHT = 0x02,//size == len
|
||||
ST_SQTRKTITLE = 0x03,//size == len
|
||||
ST_INSTRTITLE = 0x04,//size == len
|
||||
ST_LYRICS = 0x05,//size == len
|
||||
ST_MARKER = 0x06,//size == len
|
||||
ST_CUEPOINT = 0x07,//size == len
|
||||
ST_DEVICESWITCH = 0x09,//size == len <CUSTOM>
|
||||
ST_MIDICHPREFIX = 0x20,//size == 1
|
||||
|
||||
ST_ENDTRACK = 0x2F,//size == 0
|
||||
ST_TEMPOCHANGE = 0x51,//size == 3
|
||||
ST_SMPTEOFFSET = 0x54,//size == 5
|
||||
ST_TIMESIGNATURE = 0x55, //size == 4
|
||||
ST_KEYSIGNATURE = 0x59,//size == 2
|
||||
ST_SEQUENCERSPEC = 0x7F, //size == len
|
||||
|
||||
/* Non-standard, internal ADLMIDI usage only */
|
||||
ST_LOOPSTART = 0xE1,//size == 0 <CUSTOM>
|
||||
ST_LOOPEND = 0xE2,//size == 0 <CUSTOM>
|
||||
ST_RAWOPL = 0xE3//size == 0 <CUSTOM>
|
||||
};
|
||||
//! Main type of event
|
||||
uint8_t type;
|
||||
//! Sub-type of the event
|
||||
uint8_t subtype;
|
||||
//! Targeted MIDI channel
|
||||
uint8_t channel;
|
||||
//! Is valid event
|
||||
uint8_t isValid;
|
||||
//! Reserved 5 bytes padding
|
||||
uint8_t __padding[4];
|
||||
//! Absolute tick position (Used for the tempo calculation only)
|
||||
uint64_t absPosition;
|
||||
//! Raw data of this event
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A track position event contains a chain of MIDI events until next delay value
|
||||
*
|
||||
* Created with purpose to sort events by type in the same position
|
||||
* (for example, to keep controllers always first than note on events or lower than note-off events)
|
||||
*/
|
||||
class MidiTrackRow
|
||||
{
|
||||
public:
|
||||
MidiTrackRow();
|
||||
void reset();
|
||||
//! Absolute time position in seconds
|
||||
double time;
|
||||
//! Delay to next event in ticks
|
||||
uint64_t delay;
|
||||
//! Absolute position in ticks
|
||||
uint64_t absPos;
|
||||
//! Delay to next event in seconds
|
||||
double timeDelay;
|
||||
std::vector<MidiEvent> events;
|
||||
/**
|
||||
* @brief Sort events in this position
|
||||
*/
|
||||
void sortEvents(bool *noteStates = NULL);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Tempo change point entry. Used in the MIDI data building function only.
|
||||
*/
|
||||
struct TempoChangePoint
|
||||
{
|
||||
uint64_t absPos;
|
||||
fraction<uint64_t> tempo;
|
||||
};
|
||||
//P.S. I declared it here instead of local in-function because C++99 can't process templates with locally-declared structures
|
||||
|
||||
typedef std::list<MidiTrackRow> MidiTrackQueue;
|
||||
|
||||
// Information about each track
|
||||
struct PositionNew
|
||||
{
|
||||
bool began;
|
||||
char padding[7];
|
||||
double wait;
|
||||
double absTimePosition;
|
||||
struct TrackInfo
|
||||
{
|
||||
size_t ptr;
|
||||
uint64_t delay;
|
||||
int status;
|
||||
char padding2[4];
|
||||
MidiTrackQueue::iterator pos;
|
||||
TrackInfo(): ptr(0), delay(0), status(0) {}
|
||||
};
|
||||
std::vector<TrackInfo> track;
|
||||
PositionNew(): began(false), wait(0.0), absTimePosition(0.0), track()
|
||||
{}
|
||||
};
|
||||
|
||||
struct Setup
|
||||
{
|
||||
unsigned int AdlBank;
|
||||
unsigned int NumFourOps;
|
||||
unsigned int NumCards;
|
||||
int HighTremoloMode;
|
||||
int HighVibratoMode;
|
||||
int AdlPercussionMode;
|
||||
bool LogarithmicVolumes;
|
||||
int VolumeModel;
|
||||
//unsigned int SkipForward;
|
||||
bool loopingIsEnabled;
|
||||
int ScaleModulators;
|
||||
|
||||
double delay;
|
||||
double carry;
|
||||
|
||||
/* The lag between visual content and audio content equals */
|
||||
/* the sum of these two buffers. */
|
||||
double mindelay;
|
||||
double maxdelay;
|
||||
|
||||
/* For internal usage */
|
||||
ssize_t tick_skip_samples_delay; /* Skip tick processing after samples count. */
|
||||
/* For internal usage */
|
||||
|
||||
unsigned long PCM_RATE;
|
||||
};
|
||||
|
||||
struct MIDI_MarkerEntry
|
||||
{
|
||||
std::string label;
|
||||
double pos_time;
|
||||
uint64_t pos_ticks;
|
||||
};
|
||||
|
||||
std::vector<MIDIchannel> Ch;
|
||||
bool cmf_percussion_mode;
|
||||
|
||||
MIDIEventHooks hooks;
|
||||
|
||||
private:
|
||||
std::map<std::string, uint64_t> devices;
|
||||
std::map<uint64_t /*track*/, uint64_t /*channel begin index*/> current_device;
|
||||
|
||||
//Padding to fix CLanc code model's warning
|
||||
char ____padding[7];
|
||||
|
||||
std::vector<AdlChannel> ch;
|
||||
std::vector<std::vector<uint8_t> > TrackData;
|
||||
|
||||
PositionNew CurrentPositionNew, LoopBeginPositionNew, trackBeginPositionNew;
|
||||
|
||||
//! Full song length in seconds
|
||||
double fullSongTimeLength;
|
||||
//! Delay after song playd before rejecting the output stream requests
|
||||
double postSongWaitDelay;
|
||||
|
||||
//! Loop start time
|
||||
double loopStartTime;
|
||||
//! Loop end time
|
||||
double loopEndTime;
|
||||
//! Local error string
|
||||
std::string errorString;
|
||||
//! Local error string
|
||||
std::string errorStringOut;
|
||||
|
||||
//! Pre-processed track data storage
|
||||
std::vector<MidiTrackQueue > trackDataNew;
|
||||
|
||||
//! Missing instruments catches
|
||||
std::set<uint8_t> caugh_missing_instruments;
|
||||
//! Missing melodic banks catches
|
||||
std::set<uint16_t> caugh_missing_banks_melodic;
|
||||
//! Missing percussion banks catches
|
||||
std::set<uint16_t> caugh_missing_banks_percussion;
|
||||
|
||||
/**
|
||||
* @brief Build MIDI track data from the raw track data storage
|
||||
* @return true if everything successfully processed, or false on any error
|
||||
*/
|
||||
bool buildTrackData();
|
||||
|
||||
/**
|
||||
* @brief Parse one event from raw MIDI track stream
|
||||
* @param [_inout] ptr pointer to pointer to current position on the raw data track
|
||||
* @param [_in] end address to end of raw track data, needed to validate position and size
|
||||
* @param [_inout] status status of the track processing
|
||||
* @return Parsed MIDI event entry
|
||||
*/
|
||||
MidiEvent parseEvent(uint8_t **ptr, uint8_t *end, int &status);
|
||||
|
||||
public:
|
||||
|
||||
const std::string &getErrorString();
|
||||
void setErrorString(const std::string &err);
|
||||
|
||||
std::string musTitle;
|
||||
std::string musCopyright;
|
||||
std::vector<std::string> musTrackTitles;
|
||||
std::vector<MIDI_MarkerEntry> musMarkers;
|
||||
|
||||
fraction<uint64_t> InvDeltaTicks, Tempo;
|
||||
//! Tempo multiplier
|
||||
double tempoMultiplier;
|
||||
bool atEnd,
|
||||
loopStart,
|
||||
loopEnd,
|
||||
invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/
|
||||
char ____padding2[2];
|
||||
OPL3 opl;
|
||||
|
||||
int16_t outBuf[1024];
|
||||
|
||||
Setup m_setup;
|
||||
|
||||
static uint64_t ReadBEint(const void *buffer, size_t nbytes);
|
||||
static uint64_t ReadLEint(const void *buffer, size_t nbytes);
|
||||
|
||||
/**
|
||||
* @brief Standard MIDI Variable-Length numeric value parser without of validation
|
||||
* @param [_inout] ptr Pointer to memory block that contains begin of variable-length value
|
||||
* @return Unsigned integer that conains parsed variable-length value
|
||||
*/
|
||||
uint64_t ReadVarLen(uint8_t **ptr);
|
||||
/**
|
||||
* @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection
|
||||
* @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward
|
||||
* @param [_in end Pointer to end of memory block where variable-length value is stored (after end of track)
|
||||
* @param [_out] ok Reference to boolean which takes result of variable-length value parsing
|
||||
* @return Unsigned integer that conains parsed variable-length value
|
||||
*/
|
||||
uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok);
|
||||
|
||||
bool LoadBank(const std::string &filename);
|
||||
bool LoadBank(const void *data, size_t size);
|
||||
bool LoadBank(fileReader &fr);
|
||||
|
||||
bool LoadMIDI(const std::string &filename);
|
||||
bool LoadMIDI(const void *data, size_t size);
|
||||
bool LoadMIDI(fileReader &fr);
|
||||
|
||||
/**
|
||||
* @brief Periodic tick handler.
|
||||
* @param s seconds since last call
|
||||
* @param granularity don't expect intervals smaller than this, in seconds
|
||||
* @return desired number of seconds until next call
|
||||
*/
|
||||
double Tick(double s, double granularity);
|
||||
|
||||
/**
|
||||
* @brief Process extra iterators like vibrato or arpeggio
|
||||
* @param s seconds since last call
|
||||
*/
|
||||
void TickIteratos(double s);
|
||||
|
||||
/**
|
||||
* @brief Change current position to specified time position in seconds
|
||||
* @param seconds Absolute time position in seconds
|
||||
*/
|
||||
void seek(double seconds);
|
||||
|
||||
/**
|
||||
* @brief Gives current time position in seconds
|
||||
* @return Current time position in seconds
|
||||
*/
|
||||
double tell();
|
||||
|
||||
/**
|
||||
* @brief Gives time length of current song in seconds
|
||||
* @return Time length of current song in seconds
|
||||
*/
|
||||
double timeLength();
|
||||
|
||||
/**
|
||||
* @brief Gives loop start time position in seconds
|
||||
* @return Loop start time position in seconds or -1 if song has no loop points
|
||||
*/
|
||||
double getLoopStart();
|
||||
|
||||
/**
|
||||
* @brief Gives loop end time position in seconds
|
||||
* @return Loop end time position in seconds or -1 if song has no loop points
|
||||
*/
|
||||
double getLoopEnd();
|
||||
|
||||
/**
|
||||
* @brief Return to begin of current song
|
||||
*/
|
||||
void rewind();
|
||||
|
||||
/**
|
||||
* @brief Set tempo multiplier
|
||||
* @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower
|
||||
*/
|
||||
void setTempo(double tempo);
|
||||
|
||||
/* RealTime event triggers */
|
||||
void realTime_ResetState();
|
||||
|
||||
bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||
void realTime_NoteOff(uint8_t channel, uint8_t note);
|
||||
|
||||
void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal);
|
||||
void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal);
|
||||
|
||||
void realTime_Controller(uint8_t channel, uint8_t type, uint8_t value);
|
||||
|
||||
void realTime_PatchChange(uint8_t channel, uint8_t patch);
|
||||
|
||||
void realTime_PitchBend(uint8_t channel, uint16_t pitch);
|
||||
void realTime_PitchBend(uint8_t channel, uint8_t msb, uint8_t lsb);
|
||||
|
||||
void realTime_BankChangeLSB(uint8_t channel, uint8_t lsb);
|
||||
void realTime_BankChangeMSB(uint8_t channel, uint8_t msb);
|
||||
void realTime_BankChange(uint8_t channel, uint16_t bank);
|
||||
|
||||
void realTime_panic();
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
Upd_Patch = 0x1,
|
||||
Upd_Pan = 0x2,
|
||||
Upd_Volume = 0x4,
|
||||
Upd_Pitch = 0x8,
|
||||
Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch,
|
||||
Upd_Off = 0x20
|
||||
};
|
||||
|
||||
void NoteUpdate(uint16_t MidCh,
|
||||
MIDIchannel::activenoteiterator i,
|
||||
unsigned props_mask,
|
||||
int32_t select_adlchn = -1);
|
||||
bool ProcessEventsNew(bool isSeek = false);
|
||||
void HandleEvent(size_t tk, const MidiEvent &evt, int &status);
|
||||
|
||||
// Determine how good a candidate this adlchannel
|
||||
// would be for playing a note from this instrument.
|
||||
long CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t /*MidCh*/) const;
|
||||
|
||||
// A new note will be played on this channel using this instrument.
|
||||
// Kill existing notes on this channel (or don't, if we do arpeggio)
|
||||
void PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins);
|
||||
|
||||
void KillOrEvacuate(
|
||||
size_t from_channel,
|
||||
AdlChannel::users_t::iterator j,
|
||||
MIDIchannel::activenoteiterator i);
|
||||
void Panic();
|
||||
void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1);
|
||||
void SetRPN(unsigned MidCh, unsigned value, bool MSB);
|
||||
//void UpdatePortamento(unsigned MidCh)
|
||||
void NoteUpdate_All(uint16_t MidCh, unsigned props_mask);
|
||||
void NoteOff(uint16_t MidCh, uint8_t note);
|
||||
|
||||
void UpdateVibrato(double amount);
|
||||
void UpdateArpeggio(double /*amount*/);
|
||||
|
||||
public:
|
||||
uint64_t ChooseDevice(const std::string &name);
|
||||
};
|
||||
|
||||
// I think, this is useless inside of Library
|
||||
/*
|
||||
struct FourChars
|
||||
{
|
||||
char ret[4];
|
||||
|
||||
FourChars(const char *s)
|
||||
{
|
||||
for(unsigned c = 0; c < 4; ++c)
|
||||
ret[c] = s[c];
|
||||
}
|
||||
FourChars(unsigned w) // Little-endian
|
||||
{
|
||||
for(unsigned c = 0; c < 4; ++c)
|
||||
ret[c] = static_cast<int8_t>((w >>(c * 8)) & 0xFF);
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
extern int adlRefreshNumCards(ADL_MIDIPlayer *device);
|
||||
|
||||
|
||||
#endif // ADLMIDI_PRIVATE_HPP
|
60
src/sound/adlmidi/adlmidi_xmi2mid.h
Normal file
60
src/sound/adlmidi/adlmidi_xmi2mid.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* XMIDI: Miles XMIDI to MID Library Header
|
||||
*
|
||||
* Copyright (C) 2001 Ryan Nunn
|
||||
* Copyright (C) 2014-2016 Bret Curtis
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library 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 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/* XMIDI Converter */
|
||||
|
||||
#ifndef XMIDILIB_H
|
||||
#define XMIDILIB_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __DJGPP__
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef signed long int32_t;
|
||||
typedef unsigned long uint32_t;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* Conversion types for Midi files */
|
||||
#define XMIDI_CONVERT_NOCONVERSION 0x00
|
||||
#define XMIDI_CONVERT_MT32_TO_GM 0x01
|
||||
#define XMIDI_CONVERT_MT32_TO_GS 0x02
|
||||
#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */
|
||||
#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */
|
||||
#define XMIDI_CONVERT_GS127_TO_GS 0x05
|
||||
|
||||
int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize,
|
||||
uint8_t **out, uint32_t *outsize,
|
||||
uint32_t convert_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* XMIDILIB_H */
|
2045
src/sound/adlmidi/dbopl.cpp
Normal file
2045
src/sound/adlmidi/dbopl.cpp
Normal file
File diff suppressed because it is too large
Load diff
314
src/sound/adlmidi/dbopl.h
Normal file
314
src/sound/adlmidi/dbopl.h
Normal file
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* Copyright (C) 2002-2010 The DOSBox Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
|
||||
/* BEGIN MIDIPLAY GLUE */
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
typedef unsigned long Bitu;
|
||||
typedef signed long Bits;
|
||||
typedef unsigned Bit32u;
|
||||
typedef int Bit32s;
|
||||
typedef unsigned short Bit16u;
|
||||
typedef signed short Bit16s;
|
||||
typedef unsigned char Bit8u;
|
||||
typedef signed char Bit8s;
|
||||
#define INLINE inline
|
||||
#ifdef _MSC_VER
|
||||
#define GCC_UNLIKELY(x) (!!(x) == 0)
|
||||
#define GCC_LIKELY(x) (!!(x) == 1)
|
||||
#else
|
||||
#define GCC_UNLIKELY(x) __builtin_expect((x),0)
|
||||
#define GCC_LIKELY(x) __builtin_expect((x),1)
|
||||
#endif
|
||||
/* END MIDIPLAY GLUE */
|
||||
|
||||
//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume
|
||||
#define WAVE_HANDLER 10
|
||||
//Use a logarithmic wavetable with an exponential table for volume
|
||||
#define WAVE_TABLELOG 11
|
||||
//Use a linear wavetable with a multiply table for volume
|
||||
#define WAVE_TABLEMUL 12
|
||||
|
||||
//Select the type of wave generator routine
|
||||
#define DBOPL_WAVE WAVE_TABLEMUL
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifdef _MSC_VER
|
||||
# ifdef _WIN64
|
||||
typedef __int64 ssize_t;
|
||||
# else
|
||||
typedef __int32 ssize_t;
|
||||
# endif
|
||||
# else
|
||||
# ifdef _WIN64
|
||||
typedef int64_t ssize_t;
|
||||
# else
|
||||
typedef int32_t ssize_t;
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace DBOPL
|
||||
{
|
||||
|
||||
struct Chip;
|
||||
struct Operator;
|
||||
struct Channel;
|
||||
|
||||
#if (DBOPL_WAVE == WAVE_HANDLER)
|
||||
typedef Bits(DB_FASTCALL *WaveHandler)(Bitu i, Bitu volume);
|
||||
#endif
|
||||
|
||||
typedef Bits(DBOPL::Operator::*VolumeHandler)();
|
||||
typedef Channel *(DBOPL::Channel::*SynthHandler)(Chip *chip, Bit32u samples, Bit32s *output);
|
||||
|
||||
//Different synth modes that can generate blocks of data
|
||||
typedef enum
|
||||
{
|
||||
sm2AM,
|
||||
sm2FM,
|
||||
sm3AM,
|
||||
sm3FM,
|
||||
sm4Start,
|
||||
sm3FMFM,
|
||||
sm3AMFM,
|
||||
sm3FMAM,
|
||||
sm3AMAM,
|
||||
sm6Start,
|
||||
sm2Percussion,
|
||||
sm3Percussion
|
||||
} SynthMode;
|
||||
|
||||
//Shifts for the values contained in chandata variable
|
||||
enum
|
||||
{
|
||||
SHIFT_KSLBASE = 16,
|
||||
SHIFT_KEYCODE = 24
|
||||
};
|
||||
|
||||
struct Operator
|
||||
{
|
||||
public:
|
||||
//Masks for operator 20 values
|
||||
enum
|
||||
{
|
||||
MASK_KSR = 0x10,
|
||||
MASK_SUSTAIN = 0x20,
|
||||
MASK_VIBRATO = 0x40,
|
||||
MASK_TREMOLO = 0x80
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OFF,
|
||||
RELEASE,
|
||||
SUSTAIN,
|
||||
DECAY,
|
||||
ATTACK
|
||||
} State;
|
||||
|
||||
VolumeHandler volHandler;
|
||||
|
||||
#if (DBOPL_WAVE == WAVE_HANDLER)
|
||||
WaveHandler waveHandler; //Routine that generate a wave
|
||||
#else
|
||||
Bit16s *waveBase;
|
||||
Bit32u waveMask;
|
||||
Bit32u waveStart;
|
||||
#endif
|
||||
Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index
|
||||
Bit32u waveAdd; //The base frequency without vibrato
|
||||
Bit32u waveCurrent; //waveAdd + vibratao
|
||||
|
||||
Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this
|
||||
Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove?
|
||||
Bit32u vibrato; //Scaled up vibrato strength
|
||||
Bit32s sustainLevel; //When stopping at sustain level stop here
|
||||
Bit32s totalLevel; //totalLevel is added to every generated volume
|
||||
Bit32u currentLevel; //totalLevel + tremolo
|
||||
Bit32s volume; //The currently active volume
|
||||
|
||||
Bit32u attackAdd; //Timers for the different states of the envelope
|
||||
Bit32u decayAdd;
|
||||
Bit32u releaseAdd;
|
||||
Bit32u rateIndex; //Current position of the evenlope
|
||||
|
||||
Bit8u rateZero; //Bits for the different states of the envelope having no changes
|
||||
Bit8u keyOn; //Bitmask of different values that can generate keyon
|
||||
//Registers, also used to check for changes
|
||||
Bit8u reg20, reg40, reg60, reg80, regE0;
|
||||
//Active part of the envelope we're in
|
||||
Bit8u state;
|
||||
//0xff when tremolo is enabled
|
||||
Bit8u tremoloMask;
|
||||
//Strength of the vibrato
|
||||
Bit8u vibStrength;
|
||||
//Keep track of the calculated KSR so we can check for changes
|
||||
Bit8u ksr;
|
||||
private:
|
||||
void SetState(Bit8u s);
|
||||
void UpdateAttack(const Chip *chip);
|
||||
void UpdateRelease(const Chip *chip);
|
||||
void UpdateDecay(const Chip *chip);
|
||||
public:
|
||||
void UpdateAttenuation();
|
||||
void UpdateRates(const Chip *chip);
|
||||
void UpdateFrequency();
|
||||
|
||||
void Write20(const Chip *chip, Bit8u val);
|
||||
void Write40(const Chip *chip, Bit8u val);
|
||||
void Write60(const Chip *chip, Bit8u val);
|
||||
void Write80(const Chip *chip, Bit8u val);
|
||||
void WriteE0(const Chip *chip, Bit8u val);
|
||||
|
||||
bool Silent() const;
|
||||
void Prepare(const Chip *chip);
|
||||
|
||||
void KeyOn(Bit8u mask);
|
||||
void KeyOff(Bit8u mask);
|
||||
|
||||
template< State state>
|
||||
Bits TemplateVolume();
|
||||
|
||||
Bit32s RateForward(Bit32u add);
|
||||
Bitu ForwardWave();
|
||||
Bitu ForwardVolume();
|
||||
|
||||
Bits GetSample(Bits modulation);
|
||||
Bits GetWave(Bitu index, Bitu vol);
|
||||
public:
|
||||
Operator();
|
||||
char ____padding[5];
|
||||
};
|
||||
|
||||
struct Channel
|
||||
{
|
||||
Operator op[2];
|
||||
inline Operator *Op(Bitu index)
|
||||
{
|
||||
return &((this + (index >> 1))->op[ index & 1 ]);
|
||||
}
|
||||
SynthHandler synthHandler;
|
||||
Bit32u chanData; //Frequency/octave and derived values
|
||||
Bit32s old[2]; //Old data for feedback
|
||||
|
||||
Bit8u feedback; //Feedback shift
|
||||
Bit8u regB0; //Register values to check for changes
|
||||
Bit8u regC0;
|
||||
//This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel
|
||||
Bit8u fourMask;
|
||||
Bit8s maskLeft; //Sign extended values for both channel's panning
|
||||
Bit8s maskRight;
|
||||
|
||||
//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
|
||||
void UpdateFrequency(const Chip *chip, Bit8u fourOp);
|
||||
void WriteA0(const Chip *chip, Bit8u val);
|
||||
void WriteB0(const Chip *chip, Bit8u val);
|
||||
void WriteC0(const Chip *chip, Bit8u val);
|
||||
void ResetC0(const Chip *chip);
|
||||
|
||||
//call this for the first channel
|
||||
template< bool opl3Mode >
|
||||
void GeneratePercussion(Chip *chip, Bit32s *output);
|
||||
|
||||
//Generate blocks of data in specific modes
|
||||
template<SynthMode mode>
|
||||
Channel *BlockTemplate(Chip *chip, Bit32u samples, Bit32s *output);
|
||||
Channel();
|
||||
char ____padding[6];
|
||||
};
|
||||
|
||||
struct Chip
|
||||
{
|
||||
//This is used as the base counter for vibrato and tremolo
|
||||
Bit32u lfoCounter;
|
||||
Bit32u lfoAdd;
|
||||
|
||||
|
||||
Bit32u noiseCounter;
|
||||
Bit32u noiseAdd;
|
||||
Bit32u noiseValue;
|
||||
|
||||
//Frequency scales for the different multiplications
|
||||
Bit32u freqMul[16];
|
||||
//Rates for decay and release for rate of this chip
|
||||
Bit32u linearRates[76];
|
||||
//Best match attack rates for the rate of this chip
|
||||
Bit32u attackRates[76];
|
||||
|
||||
//18 channels with 2 operators each
|
||||
Channel chan[18];
|
||||
|
||||
Bit8u reg104;
|
||||
Bit8u reg08;
|
||||
Bit8u reg04;
|
||||
Bit8u regBD;
|
||||
Bit8u vibratoIndex;
|
||||
Bit8u tremoloIndex;
|
||||
Bit8s vibratoSign;
|
||||
Bit8u vibratoShift;
|
||||
Bit8u tremoloValue;
|
||||
Bit8u vibratoStrength;
|
||||
Bit8u tremoloStrength;
|
||||
//Mask for allowed wave forms
|
||||
Bit8u waveFormMask;
|
||||
//0 or -1 when enabled
|
||||
Bit8s opl3Active;
|
||||
|
||||
//Return the maximum amount of samples before and LFO change
|
||||
Bit32u ForwardLFO(Bit32u samples);
|
||||
Bit32u ForwardNoise();
|
||||
|
||||
void WriteBD(Bit8u val);
|
||||
void WriteReg(Bit32u reg, Bit8u val);
|
||||
|
||||
Bit32u WriteAddr(Bit32u port, Bit8u val);
|
||||
|
||||
void GenerateBlock2(Bitu samples, Bit32s *output);
|
||||
void GenerateBlock3(Bitu samples, Bit32s *output);
|
||||
|
||||
void GenerateBlock2_Mix(Bitu samples, Bit32s *output);
|
||||
void GenerateBlock3_Mix(Bitu samples, Bit32s *output);
|
||||
|
||||
void Generate(Bit32u samples);
|
||||
void Setup(Bit32u r);
|
||||
|
||||
Chip();
|
||||
};
|
||||
|
||||
struct Handler
|
||||
{
|
||||
DBOPL::Chip chip;
|
||||
Bit32u WriteAddr(Bit32u port, Bit8u val);
|
||||
void WriteReg(Bit32u addr, Bit8u val);
|
||||
void Generate(void(*AddSamples_m32)(Bitu, Bit32s *),
|
||||
void(*AddSamples_s32)(Bitu, Bit32s *),
|
||||
Bitu samples);
|
||||
void GenerateArr(Bit32s *out, Bitu *samples);
|
||||
void GenerateArr(Bit32s *out, ssize_t *samples);
|
||||
void GenerateArr(Bit16s *out, ssize_t *samples);
|
||||
void GenerateArrMix(Bit32s *out, ssize_t *samples);
|
||||
void GenerateArrMix(Bit16s *out, ssize_t *samples);
|
||||
void Init(Bitu rate);
|
||||
};
|
||||
|
||||
|
||||
} //Namespace
|
206
src/sound/adlmidi/fraction.hpp
Normal file
206
src/sound/adlmidi/fraction.hpp
Normal file
|
@ -0,0 +1,206 @@
|
|||
#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);
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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
|
1391
src/sound/adlmidi/nukedopl3.c
Normal file
1391
src/sound/adlmidi/nukedopl3.c
Normal file
File diff suppressed because it is too large
Load diff
154
src/sound/adlmidi/nukedopl3.h
Normal file
154
src/sound/adlmidi/nukedopl3.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT)
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* Nuked OPL3 emulator.
|
||||
* Thanks:
|
||||
* MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
|
||||
* Feedback and Rhythm part calculation information.
|
||||
* forums.submarine.org.uk(carbon14, opl3):
|
||||
* Tremolo and phase generator calculation information.
|
||||
* OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
|
||||
* OPL2 ROMs.
|
||||
*
|
||||
* version: 1.7.4
|
||||
*/
|
||||
|
||||
#ifndef OPL_OPL3_H
|
||||
#define OPL_OPL3_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#define OPL_WRITEBUF_SIZE 1024
|
||||
#define OPL_WRITEBUF_DELAY 2
|
||||
|
||||
typedef uintptr_t Bitu;
|
||||
typedef intptr_t Bits;
|
||||
typedef uint64_t Bit64u;
|
||||
typedef int64_t Bit64s;
|
||||
typedef uint32_t Bit32u;
|
||||
typedef int32_t Bit32s;
|
||||
typedef uint16_t Bit16u;
|
||||
typedef int16_t Bit16s;
|
||||
typedef uint8_t Bit8u;
|
||||
typedef int8_t Bit8s;
|
||||
|
||||
typedef struct _opl3_slot opl3_slot;
|
||||
typedef struct _opl3_channel opl3_channel;
|
||||
typedef struct _opl3_chip opl3_chip;
|
||||
|
||||
struct _opl3_slot {
|
||||
opl3_channel *channel;
|
||||
opl3_chip *chip;
|
||||
Bit16s out;
|
||||
Bit16s fbmod;
|
||||
Bit16s *mod;
|
||||
Bit16s prout;
|
||||
Bit16s eg_rout;
|
||||
Bit16s eg_out;
|
||||
Bit8u eg_inc;
|
||||
Bit8u eg_gen;
|
||||
Bit8u eg_rate;
|
||||
Bit8u eg_ksl;
|
||||
Bit8u *trem;
|
||||
Bit8u reg_vib;
|
||||
Bit8u reg_type;
|
||||
Bit8u reg_ksr;
|
||||
Bit8u reg_mult;
|
||||
Bit8u reg_ksl;
|
||||
Bit8u reg_tl;
|
||||
Bit8u reg_ar;
|
||||
Bit8u reg_dr;
|
||||
Bit8u reg_sl;
|
||||
Bit8u reg_rr;
|
||||
Bit8u reg_wf;
|
||||
Bit8u key;
|
||||
Bit32u pg_phase;
|
||||
Bit32u timer;
|
||||
|
||||
Bit16u maskzero;
|
||||
Bit8u signpos;
|
||||
Bit8u phaseshift;
|
||||
};
|
||||
|
||||
struct _opl3_channel {
|
||||
opl3_slot *slots[2];
|
||||
opl3_channel *pair;
|
||||
opl3_chip *chip;
|
||||
Bit16s *out[4];
|
||||
Bit8u chtype;
|
||||
Bit16u f_num;
|
||||
Bit8u block;
|
||||
Bit8u fb;
|
||||
Bit8u con;
|
||||
Bit8u alg;
|
||||
Bit8u ksv;
|
||||
Bit16u cha, chb;
|
||||
};
|
||||
|
||||
typedef struct _opl3_writebuf {
|
||||
Bit64u time;
|
||||
Bit16u reg;
|
||||
Bit8u data;
|
||||
} opl3_writebuf;
|
||||
|
||||
struct _opl3_chip {
|
||||
opl3_channel channel[18];
|
||||
opl3_slot chipslot[36];
|
||||
Bit16u timer;
|
||||
Bit8u newm;
|
||||
Bit8u nts;
|
||||
Bit8u rhy;
|
||||
Bit8u vibpos;
|
||||
Bit8u vibshift;
|
||||
Bit8u tremolo;
|
||||
Bit8u tremolopos;
|
||||
Bit8u tremoloshift;
|
||||
Bit32u noise;
|
||||
Bit16s zeromod;
|
||||
Bit32s mixbuff[2];
|
||||
/* OPL3L */
|
||||
Bit32s rateratio;
|
||||
Bit32s samplecnt;
|
||||
Bit16s oldsamples[2];
|
||||
Bit16s samples[2];
|
||||
|
||||
Bit64u writebuf_samplecnt;
|
||||
Bit32u writebuf_cur;
|
||||
Bit32u writebuf_last;
|
||||
Bit64u writebuf_lasttime;
|
||||
opl3_writebuf writebuf[OPL_WRITEBUF_SIZE];
|
||||
};
|
||||
|
||||
void OPL3_Generate(opl3_chip *chip, Bit16s *buf);
|
||||
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_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
|
||||
void OPL3_GenerateStreamMix(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -315,6 +315,26 @@ protected:
|
|||
#endif
|
||||
};
|
||||
|
||||
|
||||
class ADLMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
struct ADL_MIDIPlayer *Renderer;
|
||||
TArray<int16_t> shortbuffer;
|
||||
public:
|
||||
ADLMIDIDevice(const char *args);
|
||||
~ADLMIDIDevice();
|
||||
|
||||
int Open(MidiCallback, void *userdata);
|
||||
int GetDeviceType() const override { return MDEV_OPL; }
|
||||
|
||||
protected:
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2);
|
||||
void HandleLongEvent(const uint8_t *data, int len);
|
||||
void ComputeOutput(float *buffer, int len);
|
||||
};
|
||||
|
||||
|
||||
// Base class for streaming MUS and MIDI files ------------------------------
|
||||
|
||||
enum
|
||||
|
|
180
src/sound/mididevices/music_adlmidi_mididevice.cpp
Normal file
180
src/sound/mididevices/music_adlmidi_mididevice.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
** music_timidity_mididevice.cpp
|
||||
** Provides access to TiMidity as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "i_musicinterns.h"
|
||||
#include "templates.h"
|
||||
#include "doomdef.h"
|
||||
#include "m_swap.h"
|
||||
#include "w_wad.h"
|
||||
#include "v_text.h"
|
||||
#include "adlmidi/adlmidi.h"
|
||||
#include <errno.h>
|
||||
|
||||
enum
|
||||
{
|
||||
ME_NOTEOFF = 0x80,
|
||||
ME_NOTEON = 0x90,
|
||||
ME_KEYPRESSURE = 0xA0,
|
||||
ME_CONTROLCHANGE = 0xB0,
|
||||
ME_PROGRAM = 0xC0,
|
||||
ME_CHANNELPRESSURE = 0xD0,
|
||||
ME_PITCHWHEEL = 0xE0
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ADLMIDIDevice::ADLMIDIDevice(const char *args)
|
||||
{
|
||||
Renderer = adl_init(44100); // todo: make it configurable
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
adl_setBank(Renderer, 14);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ADLMIDIDevice::~ADLMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
adl_close(Renderer);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int ADLMIDIDevice::Open(MidiCallback callback, void *userdata)
|
||||
{
|
||||
int ret = OpenStream(2, 0, callback, userdata);
|
||||
if (ret == 0)
|
||||
{
|
||||
adl_rt_resetState(Renderer);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int chan = status & 0x0F;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case ME_NOTEON:
|
||||
adl_rt_noteOn(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_NOTEOFF:
|
||||
adl_rt_noteOff(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_KEYPRESSURE:
|
||||
adl_rt_noteAfterTouch(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_CONTROLCHANGE:
|
||||
adl_rt_controllerChange(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_PROGRAM:
|
||||
adl_rt_bankChange(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_CHANNELPRESSURE:
|
||||
adl_rt_channelAfterTouch(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_PITCHWHEEL:
|
||||
adl_rt_pitchBend(Renderer, chan, parm1 | (parm2 << 7));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
if (shortbuffer.Size() < len*2) shortbuffer.Resize(len*2);
|
||||
auto result = adl_generate(Renderer, len, &shortbuffer[0]);
|
||||
for(int i=0; i<result*2; i++)
|
||||
{
|
||||
buffer[i] = shortbuffer[i] * (1.f/32768.f);
|
||||
}
|
||||
/*
|
||||
for (int i = result; i < len; i++) // The backend cannot deal with gaps.
|
||||
{
|
||||
buffer[i*2] = shortbuffer[(result-1)*2] * (1.f / 32768.f);
|
||||
buffer[i * 2+1] = shortbuffer[(result - 1) * 2+1] * (1.f / 32768.f);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
@ -194,38 +194,39 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
selectedDevice = devtype;
|
||||
try
|
||||
{
|
||||
switch (devtype)
|
||||
{
|
||||
case MDEV_GUS:
|
||||
switch (devtype)
|
||||
{
|
||||
case MDEV_GUS:
|
||||
dev = new TimidityMIDIDevice(Args, samplerate);
|
||||
break;
|
||||
|
||||
case MDEV_MMAPI:
|
||||
case MDEV_MMAPI:
|
||||
#ifdef _WIN32
|
||||
dev = CreateWinMIDIDevice(mididevice);
|
||||
break;
|
||||
#endif
|
||||
// Intentional fall-through for non-Windows systems.
|
||||
// Intentional fall-through for non-Windows systems.
|
||||
|
||||
case MDEV_FLUIDSYNTH:
|
||||
case MDEV_FLUIDSYNTH:
|
||||
dev = new FluidSynthMIDIDevice(Args, samplerate);
|
||||
break;
|
||||
|
||||
case MDEV_OPL:
|
||||
case MDEV_OPL:
|
||||
dev = new OPLMIDIDevice(Args);
|
||||
break;
|
||||
*/
|
||||
|
||||
case MDEV_TIMIDITY:
|
||||
case MDEV_TIMIDITY:
|
||||
dev = CreateTimidityPPMIDIDevice(Args, samplerate);
|
||||
break;
|
||||
|
||||
case MDEV_WILDMIDI:
|
||||
case MDEV_WILDMIDI:
|
||||
dev = new WildMIDIDevice(Args, samplerate);
|
||||
break;
|
||||
|
||||
default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CRecoverableError &err)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue