Merge branch 'adlMIDI'

# Conflicts:
#	src/sound/musicformats/music_midistream.cpp
This commit is contained in:
Christoph Oelckers 2018-03-07 20:31:32 +01:00
commit fd801b8b94
21 changed files with 21287 additions and 11 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

View 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

View 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
View 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 */

View 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

View 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;
}

File diff suppressed because it is too large Load diff

View 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 */

View 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();
}

View 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;
}

View 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

View 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

File diff suppressed because it is too large Load diff

314
src/sound/adlmidi/dbopl.h Normal file
View 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

View 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

File diff suppressed because it is too large Load diff

View 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

View file

@ -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

View 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);
}
*/
}

View file

@ -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)
{