/* * libOPNMIDI is a free Software MIDI synthesizer library with OPN2 (YM2612) emulation * * MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma * OPNMIDI Library and YM2612 support: Copyright (c) 2017-2020 Vitaly Novichkov * * 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 . */ #include "opnmidi_midiplay.hpp" #include "opnmidi_opn2.hpp" #include "opnmidi_private.hpp" #include "chips/opn_chip_base.h" #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER #include "midi_sequencer.hpp" #endif /* Unify MIDI player casting and interface between ADLMIDI and OPNMIDI */ #define GET_MIDI_PLAYER(device) reinterpret_cast((device)->opn2_midiPlayer) typedef OPNMIDIplay MidiPlayer; static OPN2_Version opn2_version = { OPNMIDI_VERSION_MAJOR, OPNMIDI_VERSION_MINOR, OPNMIDI_VERSION_PATCHLEVEL }; static const OPNMIDI_AudioFormat opn2_DefaultAudioFormat = { OPNMIDI_SampleType_S16, sizeof(int16_t), 2 * sizeof(int16_t), }; /*---------------------------EXPORTS---------------------------*/ OPNMIDI_EXPORT struct OPN2_MIDIPlayer *opn2_init(long sample_rate) { OPN2_MIDIPlayer *midi_device; midi_device = (OPN2_MIDIPlayer *)malloc(sizeof(OPN2_MIDIPlayer)); if(!midi_device) { OPN2MIDI_ErrorString = "Can't initialize OPNMIDI: out of memory!"; return NULL; } OPNMIDIplay *player = new(std::nothrow) OPNMIDIplay(static_cast(sample_rate)); if(!player) { free(midi_device); OPN2MIDI_ErrorString = "Can't initialize OPNMIDI: out of memory!"; return NULL; } midi_device->opn2_midiPlayer = player; return midi_device; } OPNMIDI_EXPORT int opn2_setDeviceIdentifier(OPN2_MIDIPlayer *device, unsigned id) { if(!device || id > 0x0f) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->setDeviceId(static_cast(id)); return 0; } OPNMIDI_EXPORT int opn2_setNumChips(OPN2_MIDIPlayer *device, int numCards) { if(device == NULL) return -2; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_setup.numChips = static_cast(numCards); if(play->m_setup.numChips < 1 || play->m_setup.numChips > OPN_MAX_CHIPS) { play->setErrorString("number of chips may only be 1.." OPN_MAX_CHIPS_STR ".\n"); return -1; } Synth &synth = *play->m_synth; if(!synth.setupLocked()) { synth.m_numChips = play->m_setup.numChips; play->partialReset(); } return 0; } OPNMIDI_EXPORT int opn2_getNumChips(struct OPN2_MIDIPlayer *device) { if(device == NULL) return -2; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return (int)play->m_setup.numChips; } OPNMIDI_EXPORT int opn2_getNumChipsObtained(struct OPN2_MIDIPlayer *device) { if(device == NULL) return -2; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return (int)play->m_synth->m_numChips; } OPNMIDI_EXPORT int opn2_reserveBanks(OPN2_MIDIPlayer *device, unsigned banks) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth::BankMap &map = play->m_synth->m_insBanks; map.reserve(banks); return (int)map.capacity(); } OPNMIDI_EXPORT int opn2_getBank(OPN2_MIDIPlayer *device, const OPN2_BankId *idp, int flags, OPN2_Bank *bank) { if(!device || !idp || !bank) return -1; OPN2_BankId id = *idp; if(id.lsb > 127 || id.msb > 127 || id.percussive > 1) return -1; size_t idnumber = ((id.msb << 8) | id.lsb | (id.percussive ? size_t(Synth::PercussionTag) : 0)); MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth::BankMap &map = play->m_synth->m_insBanks; Synth::BankMap::iterator it; if(!(flags & OPNMIDI_Bank_Create)) { it = map.find(idnumber); if(it == map.end()) return -1; } else { std::pair value; value.first = idnumber; memset(&value.second, 0, sizeof(value.second)); for (unsigned i = 0; i < 128; ++i) value.second.ins[i].flags = OpnInstMeta::Flag_NoSound; std::pair ir; if((flags & OPNMIDI_Bank_CreateRt) == OPNMIDI_Bank_CreateRt) { ir = map.insert(value, Synth::BankMap::do_not_expand_t()); if(ir.first == map.end()) return -1; } else ir = map.insert(value); it = ir.first; } it.to_ptrs(bank->pointer); return 0; } OPNMIDI_EXPORT int opn2_getBankId(OPN2_MIDIPlayer *device, const OPN2_Bank *bank, OPN2_BankId *id) { if(!device || !bank) return -1; Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); Synth::BankMap::key_type idnumber = it->first; id->msb = (idnumber >> 8) & 127; id->lsb = idnumber & 127; id->percussive = (idnumber & Synth::PercussionTag) ? 1 : 0; return 0; } OPNMIDI_EXPORT int opn2_removeBank(OPN2_MIDIPlayer *device, OPN2_Bank *bank) { if(!device || !bank) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth::BankMap &map = play->m_synth->m_insBanks; Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); size_t size = map.size(); map.erase(it); return (map.size() != size) ? 0 : -1; } OPNMIDI_EXPORT int opn2_getFirstBank(OPN2_MIDIPlayer *device, OPN2_Bank *bank) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth::BankMap &map = play->m_synth->m_insBanks; Synth::BankMap::iterator it = map.begin(); if(it == map.end()) return -1; it.to_ptrs(bank->pointer); return 0; } OPNMIDI_EXPORT int opn2_getNextBank(OPN2_MIDIPlayer *device, OPN2_Bank *bank) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth::BankMap &map = play->m_synth->m_insBanks; Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); if(++it == map.end()) return -1; it.to_ptrs(bank->pointer); return 0; } OPNMIDI_EXPORT int opn2_getInstrument(OPN2_MIDIPlayer *device, const OPN2_Bank *bank, unsigned index, OPN2_Instrument *ins) { if(!device || !bank || index > 127 || !ins) return -1; Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); cvt_FMIns_to_OPNI(*ins, it->second.ins[index]); ins->version = 0; return 0; } OPNMIDI_EXPORT int opn2_setInstrument(OPN2_MIDIPlayer *device, OPN2_Bank *bank, unsigned index, const OPN2_Instrument *ins) { if(!device || !bank || index > 127 || !ins) return -1; if(ins->version != 0) return -1; Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); cvt_OPNI_to_FMIns(it->second.ins[index], *ins); return 0; } OPNMIDI_EXPORT int opn2_openBankFile(OPN2_MIDIPlayer *device, const char *filePath) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_setup.tick_skip_samples_delay = 0; if(!play->LoadBank(filePath)) { std::string err = play->getErrorString(); if(err.empty()) play->setErrorString("OPN2 MIDI: Can't load file"); return -1; } else return 0; } OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized"; return -1; } OPNMIDI_EXPORT int opn2_openBankData(OPN2_MIDIPlayer *device, const void *mem, long size) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_setup.tick_skip_samples_delay = 0; if(!play->LoadBank(mem, static_cast(size))) { std::string err = play->getErrorString(); if(err.empty()) play->setErrorString("OPN2 MIDI: Can't load data from memory"); return -1; } else return 0; } OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized"; return -1; } OPNMIDI_EXPORT void opn2_setLfoEnabled(struct OPN2_MIDIPlayer *device, int lfoEnable) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; play->m_setup.lfoEnable = lfoEnable; synth.m_lfoEnable = (lfoEnable < 0 ? synth.m_insBankSetup.lfoEnable : play->m_setup.lfoEnable) != 0; synth.commitLFOSetup(); } OPNMIDI_EXPORT int opn2_getLfoEnabled(struct OPN2_MIDIPlayer *device) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_synth->m_lfoEnable; } OPNMIDI_EXPORT void opn2_setLfoFrequency(struct OPN2_MIDIPlayer *device, int lfoFrequency) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; play->m_setup.lfoFrequency = lfoFrequency; synth.m_lfoFrequency = lfoFrequency < 0 ? synth.m_insBankSetup.lfoFrequency : (uint8_t)play->m_setup.lfoFrequency; synth.commitLFOSetup(); } OPNMIDI_EXPORT int opn2_getLfoFrequency(struct OPN2_MIDIPlayer *device) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_synth->m_lfoFrequency; } /*Override chip type. -1 - use bank default state*/ OPNMIDI_EXPORT void opn2_setChipType(struct OPN2_MIDIPlayer *device, int chipType) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_setup.chipType = chipType; play->applySetup(); } /*Get the chip type*/ OPNMIDI_EXPORT int opn2_getChipType(struct OPN2_MIDIPlayer *device) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_synth->chipFamily(); } OPNMIDI_EXPORT void opn2_setScaleModulators(OPN2_MIDIPlayer *device, int smod) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_setup.ScaleModulators = smod; play->m_synth->m_scaleModulators = (play->m_setup.ScaleModulators != 0); } OPNMIDI_EXPORT void opn2_setFullRangeBrightness(struct OPN2_MIDIPlayer *device, int fr_brightness) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_setup.fullRangeBrightnessCC74 = (fr_brightness != 0); } OPNMIDI_EXPORT void opn2_setLoopEnabled(OPN2_MIDIPlayer *device, int loopEn) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_sequencer->setLoopEnabled(loopEn != 0); #else ADL_UNUSED(device); ADL_UNUSED(loopEn); #endif } OPNMIDI_EXPORT void opn2_setSoftPanEnabled(OPN2_MIDIPlayer *device, int softPanEn) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_synth->m_softPanning = (softPanEn != 0); } /* !!!DEPRECATED!!! */ OPNMIDI_EXPORT void opn2_setLogarithmicVolumes(struct OPN2_MIDIPlayer *device, int logvol) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; play->m_setup.LogarithmicVolumes = static_cast(logvol); if(!synth.setupLocked()) { if(play->m_setup.LogarithmicVolumes != 0) synth.setVolumeScaleModel(OPNMIDI_VolumeModel_NativeOPN2); else synth.setVolumeScaleModel(static_cast(play->m_setup.VolumeModel)); } } OPNMIDI_EXPORT void opn2_setVolumeRangeModel(OPN2_MIDIPlayer *device, int volumeModel) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; play->m_setup.VolumeModel = volumeModel; if(!synth.setupLocked()) { if(play->m_setup.VolumeModel == OPNMIDI_VolumeModel_AUTO)//Use bank default volume model synth.m_volumeScale = (Synth::VolumesScale)synth.m_insBankSetup.volumeModel; else synth.setVolumeScaleModel(static_cast(volumeModel)); } } OPNMIDI_EXPORT int opn2_getVolumeRangeModel(struct OPN2_MIDIPlayer *device) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_synth->getVolumeScaleModel(); } OPNMIDI_EXPORT int opn2_openFile(OPN2_MIDIPlayer *device, const char *filePath) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER play->m_setup.tick_skip_samples_delay = 0; if(!play->LoadMIDI(filePath)) { std::string err = play->getErrorString(); if(err.empty()) play->setErrorString("OPN2 MIDI: Can't load file"); return -1; } else return 0; #else ADL_UNUSED(filePath); play->setErrorString("OPNMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif } OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized"; return -1; } OPNMIDI_EXPORT int opn2_openData(OPN2_MIDIPlayer *device, const void *mem, unsigned long size) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER play->m_setup.tick_skip_samples_delay = 0; if(!play->LoadMIDI(mem, static_cast(size))) { std::string err = play->getErrorString(); if(err.empty()) play->setErrorString("OPN2 MIDI: Can't load data from memory"); return -1; } else return 0; #else ADL_UNUSED(mem); ADL_UNUSED(size); play->setErrorString("OPNMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif } OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized"; return -1; } OPNMIDI_EXPORT const char *opn2_emulatorName() { return ""; } OPNMIDI_EXPORT const char *opn2_chipEmulatorName(struct OPN2_MIDIPlayer *device) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; if(!synth.m_chips.empty()) return synth.m_chips[0]->emulatorName(); } return "Unknown"; } OPNMIDI_EXPORT int opn2_switchEmulator(struct OPN2_MIDIPlayer *device, int emulator) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); if(opn2_isEmulatorAvailable(emulator)) { play->m_setup.emulator = emulator; play->partialReset(); return 0; } play->setErrorString("OPN2 MIDI: Unknown emulation core!"); } return -1; } OPNMIDI_EXPORT int opn2_setRunAtPcmRate(OPN2_MIDIPlayer *device, int enabled) { if(device) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); Synth &synth = *play->m_synth; play->m_setup.runAtPcmRate = (enabled != 0); if(!synth.setupLocked()) play->partialReset(); return 0; } return -1; } OPNMIDI_EXPORT const char *opn2_linkedLibraryVersion() { #if !defined(OPNMIDI_ENABLE_HQ_RESAMPLER) return OPNMIDI_VERSION; #else return OPNMIDI_VERSION " (HQ)"; #endif } OPNMIDI_EXPORT const OPN2_Version *opn2_linkedVersion() { return &opn2_version; } OPNMIDI_EXPORT const char *opn2_errorString() { return OPN2MIDI_ErrorString.c_str(); } OPNMIDI_EXPORT const char *opn2_errorInfo(struct OPN2_MIDIPlayer *device) { if(!device) return opn2_errorString(); MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return opn2_errorString(); return play->getErrorString().c_str(); } OPNMIDI_EXPORT void opn2_close(OPN2_MIDIPlayer *device) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); delete play; device->opn2_midiPlayer = NULL; free(device); device = NULL; } OPNMIDI_EXPORT void opn2_reset(OPN2_MIDIPlayer *device) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->partialReset(); play->resetMIDI(); } OPNMIDI_EXPORT double opn2_totalTimeLength(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->timeLength(); #else ADL_UNUSED(device); return -1.0; #endif } OPNMIDI_EXPORT double opn2_loopStartTime(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getLoopStart(); #else ADL_UNUSED(device); return -1.0; #endif } OPNMIDI_EXPORT double opn2_loopEndTime(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getLoopEnd(); #else ADL_UNUSED(device); return -1.0; #endif } OPNMIDI_EXPORT double opn2_positionTell(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->tell(); #else ADL_UNUSED(device); return -1.0; #endif } OPNMIDI_EXPORT void opn2_positionSeek(struct OPN2_MIDIPlayer *device, double seconds) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(seconds < 0.0) return;//Seeking negative position is forbidden! :-P if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_panic(); play->m_setup.delay = play->m_sequencer->seek(seconds, play->m_setup.mindelay); play->m_setup.carry = 0.0; #else ADL_UNUSED(device); ADL_UNUSED(seconds); #endif } OPNMIDI_EXPORT void opn2_positionRewind(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_panic(); play->m_sequencer->rewind(); #else ADL_UNUSED(device); #endif } OPNMIDI_EXPORT void opn2_setTempo(struct OPN2_MIDIPlayer *device, double tempo) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device || (tempo <= 0.0)) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_sequencer->setTempo(tempo); #else ADL_UNUSED(device); ADL_UNUSED(tempo); #endif } OPNMIDI_EXPORT int opn2_describeChannels(struct OPN2_MIDIPlayer *device, char *str, char *attr, size_t size) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->describeChannels(str, attr, size); return 0; } OPNMIDI_EXPORT const char *opn2_metaMusicTitle(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getMusicTitle().c_str(); #else ADL_UNUSED(device); return ""; #endif } OPNMIDI_EXPORT const char *opn2_metaMusicCopyright(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getMusicCopyright().c_str(); #else ADL_UNUSED(device); return 0; #endif } OPNMIDI_EXPORT size_t opn2_metaTrackTitleCount(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getTrackTitles().size(); #else ADL_UNUSED(device); return 0; #endif } OPNMIDI_EXPORT const char *opn2_metaTrackTitle(struct OPN2_MIDIPlayer *device, size_t index) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); const std::vector &titles = play->m_sequencer->getTrackTitles(); if(index >= titles.size()) return "INVALID"; return titles[index].c_str(); #else ADL_UNUSED(device); ADL_UNUSED(index); return "NOT SUPPORTED"; #endif } OPNMIDI_EXPORT size_t opn2_metaMarkerCount(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getMarkers().size(); #else ADL_UNUSED(device); return 0; #endif } OPNMIDI_EXPORT Opn2_MarkerEntry opn2_metaMarker(struct OPN2_MIDIPlayer *device, size_t index) { struct Opn2_MarkerEntry marker; #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) { marker.label = "INVALID"; marker.pos_time = 0.0; marker.pos_ticks = 0; return marker; } MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); const std::vector &markers = play->m_sequencer->getMarkers(); if(index >= markers.size()) { marker.label = "INVALID"; marker.pos_time = 0.0; marker.pos_ticks = 0; return marker; } const MidiSequencer::MIDI_MarkerEntry &mk = markers[index]; marker.label = mk.label.c_str(); marker.pos_time = mk.pos_time; marker.pos_ticks = (unsigned long)mk.pos_ticks; #else (void)device; (void)index; marker.label = "NOT SUPPORTED"; marker.pos_time = 0.0; marker.pos_ticks = 0; #endif return marker; } OPNMIDI_EXPORT void opn2_setRawEventHook(struct OPN2_MIDIPlayer *device, OPN2_RawEventHook rawEventHook, void *userData) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->m_sequencerInterface->onEvent = rawEventHook; play->m_sequencerInterface->onEvent_userData = userData; #else ADL_UNUSED(device); ADL_UNUSED(rawEventHook); ADL_UNUSED(userData); #endif } /* Set note hook */ OPNMIDI_EXPORT void opn2_setNoteHook(struct OPN2_MIDIPlayer *device, OPN2_NoteHook noteHook, void *userData) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->hooks.onNote = noteHook; play->hooks.onNote_userData = userData; } /* Set debug message hook */ OPNMIDI_EXPORT void opn2_setDebugMessageHook(struct OPN2_MIDIPlayer *device, OPN2_DebugMessageHook debugMessageHook, void *userData) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->hooks.onDebugMessage = debugMessageHook; play->hooks.onDebugMessage_userData = userData; #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER play->m_sequencerInterface->onDebugMessage = debugMessageHook; play->m_sequencerInterface->onDebugMessage_userData = userData; #endif } template static void CopySamplesRaw(OPN2_UInt8 *dstLeft, OPN2_UInt8 *dstRight, const int32_t *src, size_t frameCount, unsigned sampleOffset) { for(size_t i = 0; i < frameCount; ++i) { *(Dst *)(dstLeft + (i * sampleOffset)) = src[2 * i]; *(Dst *)(dstRight + (i * sampleOffset)) = src[(2 * i) + 1]; } } template static void CopySamplesTransformed(OPN2_UInt8 *dstLeft, OPN2_UInt8 *dstRight, const int32_t *src, size_t frameCount, unsigned sampleOffset, Ret(&transform)(int32_t)) { for(size_t i = 0; i < frameCount; ++i) { *(Dst *)(dstLeft + (i * sampleOffset)) = static_cast(transform(src[2 * i])); *(Dst *)(dstRight + (i * sampleOffset)) = static_cast(transform(src[(2 * i) + 1])); } } static int SendStereoAudio(int samples_requested, ssize_t in_size, int32_t *_in, ssize_t out_pos, OPN2_UInt8 *left, OPN2_UInt8 *right, const OPNMIDI_AudioFormat *format) { if(!in_size) return 0; size_t outputOffset = static_cast(out_pos); size_t inSamples = static_cast(in_size * 2); size_t maxSamples = static_cast(samples_requested) - outputOffset; size_t toCopy = std::min(maxSamples, inSamples); OPNMIDI_SampleType sampleType = format->type; const unsigned containerSize = format->containerSize; const unsigned sampleOffset = format->sampleOffset; left += (outputOffset / 2) * sampleOffset; right += (outputOffset / 2) * sampleOffset; typedef int32_t(&pfnConvert)(int32_t); switch(sampleType) { case OPNMIDI_SampleType_S8: case OPNMIDI_SampleType_U8: { pfnConvert cvt = (sampleType == OPNMIDI_SampleType_S8) ? opn2_cvtS8 : opn2_cvtU8; switch(containerSize) { case sizeof(int8_t): CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, cvt); break; case sizeof(int16_t): CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, cvt); break; case sizeof(int32_t): CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, cvt); break; default: return -1; } break; } case OPNMIDI_SampleType_S16: case OPNMIDI_SampleType_U16: { pfnConvert cvt = (sampleType == OPNMIDI_SampleType_S16) ? opn2_cvtS16 : opn2_cvtU16; switch(containerSize) { case sizeof(int16_t): CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, cvt); break; case sizeof(int32_t): CopySamplesRaw(left, right, _in, toCopy / 2, sampleOffset); break; default: return -1; } break; } case OPNMIDI_SampleType_S24: case OPNMIDI_SampleType_U24: { pfnConvert cvt = (sampleType == OPNMIDI_SampleType_S24) ? opn2_cvtS24 : opn2_cvtU24; switch(containerSize) { case sizeof(int32_t): CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, cvt); break; default: return -1; } break; } case OPNMIDI_SampleType_S32: case OPNMIDI_SampleType_U32: { pfnConvert cvt = (sampleType == OPNMIDI_SampleType_S32) ? opn2_cvtS32 : opn2_cvtU32; switch(containerSize) { case sizeof(int32_t): CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, cvt); break; default: return -1; } break; } case OPNMIDI_SampleType_F32: if(containerSize != sizeof(float)) return -1; CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, opn2_cvtReal); break; case OPNMIDI_SampleType_F64: if(containerSize != sizeof(double)) return -1; CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, opn2_cvtReal); break; default: return -1; } return 0; } OPNMIDI_EXPORT int opn2_play(struct OPN2_MIDIPlayer *device, int sampleCount, short *out) { return opn2_playFormat(device, sampleCount, (OPN2_UInt8 *)out, (OPN2_UInt8 *)(out + 1), &opn2_DefaultAudioFormat); } OPNMIDI_EXPORT int opn2_playFormat(OPN2_MIDIPlayer *device, int sampleCount, OPN2_UInt8 *out_left, OPN2_UInt8 *out_right, const OPNMIDI_AudioFormat *format) { #if defined(OPNMIDI_DISABLE_MIDI_SEQUENCER) ADL_UNUSED(device); ADL_UNUSED(sampleCount); ADL_UNUSED(out_left); ADL_UNUSED(out_right); ADL_UNUSED(format); return 0; #endif #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; if(!device) return 0; MidiPlayer *player = GET_MIDI_PLAYER(device); assert(player); MidiPlayer::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) { {// if(setup.delay <= 0.0) setup.delay = double(left / 2) / double(setup.PCM_RATE); 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 += double(setup.PCM_RATE) * eat_delay; n_periodCountStereo = static_cast(setup.carry); setup.carry -= double(n_periodCountStereo); } //if(setup.SkipForward > 0) // setup.SkipForward -= 1; //else { if((player->m_sequencer->positionAtEnd()) && (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 int32_t *out_buf = player->m_outBuf; std::memset(out_buf, 0, static_cast(in_generatedPhys) * sizeof(out_buf[0])); Synth &synth = *player->m_synth; unsigned int chips = synth.m_numChips; if(chips == 1) synth.m_chips[0]->generate32(out_buf, (size_t)in_generatedStereo); else/* if(n_periodCountStereo > 0)*/ { /* Generate data from every chip and mix result */ for(size_t card = 0; card < chips; ++card) synth.m_chips[card]->generateAndMix32(out_buf, (size_t)in_generatedStereo); } /* Process it */ if(SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out_left, out_right, format) == -1) return 0; 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(gotten_len); #endif //OPNMIDI_DISABLE_MIDI_SEQUENCER } OPNMIDI_EXPORT int opn2_generate(struct OPN2_MIDIPlayer *device, int sampleCount, short *out) { return opn2_generateFormat(device, sampleCount, (OPN2_UInt8 *)out, (OPN2_UInt8 *)(out + 1), &opn2_DefaultAudioFormat); } OPNMIDI_EXPORT int opn2_generateFormat(struct OPN2_MIDIPlayer *device, int sampleCount, OPN2_UInt8 *out_left, OPN2_UInt8 *out_right, const OPNMIDI_AudioFormat *format) { sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; if(!device) return 0; MidiPlayer *player = GET_MIDI_PLAYER(device); assert(player); MidiPlayer::Setup &setup = player->m_setup; ssize_t gotten_len = 0; ssize_t n_periodCountStereo = 512; int left = sampleCount; double delay = double(sampleCount / 2) / double(setup.PCM_RATE); while(left > 0) { {// if(delay <= 0.0) delay = double(left / 2) / double(setup.PCM_RATE); const double eat_delay = delay < setup.maxdelay ? delay : setup.maxdelay; delay -= eat_delay; setup.carry += double(setup.PCM_RATE) * eat_delay; n_periodCountStereo = static_cast(setup.carry); setup.carry -= double(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 int32_t *out_buf = player->m_outBuf; std::memset(out_buf, 0, static_cast(in_generatedPhys) * sizeof(out_buf[0])); Synth &synth = *player->m_synth; unsigned int chips = synth.m_numChips; if(chips == 1) synth.m_chips[0]->generate32(out_buf, (size_t)in_generatedStereo); else/* if(n_periodCountStereo > 0)*/ { /* Generate data from every chip and mix result */ for(size_t card = 0; card < chips; ++card) synth.m_chips[card]->generateAndMix32(out_buf, (size_t)in_generatedStereo); } /* Process it */ if(SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out_left, out_right, format) == -1) return 0; left -= (int)in_generatedPhys; gotten_len += (in_generatedPhys) /* - setup.stored_samples*/; } player->TickIterators(eat_delay); }//... } return static_cast(gotten_len); } OPNMIDI_EXPORT double opn2_tickEvents(struct OPN2_MIDIPlayer *device, double seconds, double granuality) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->Tick(seconds, granuality); #else ADL_UNUSED(device); ADL_UNUSED(seconds); ADL_UNUSED(granuality); return -1.0; #endif } OPNMIDI_EXPORT int opn2_atEnd(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return (int)play->m_sequencer->positionAtEnd(); #else ADL_UNUSED(device); return 1; #endif } OPNMIDI_EXPORT size_t opn2_trackCount(struct OPN2_MIDIPlayer *device) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->m_sequencer->getTrackCount(); #else ADL_UNUSED(device); return 0; #endif } OPNMIDI_EXPORT int opn2_setTrackOptions(struct OPN2_MIDIPlayer *device, size_t trackNumber, unsigned trackOptions) { #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); MidiSequencer &seq = *play->m_sequencer; unsigned enableFlag = trackOptions & 3; trackOptions &= ~3u; // handle on/off/solo switch(enableFlag) { default: break; case OPNMIDI_TrackOption_On: case OPNMIDI_TrackOption_Off: if(!seq.setTrackEnabled(trackNumber, enableFlag == OPNMIDI_TrackOption_On)) return -1; break; case OPNMIDI_TrackOption_Solo: seq.setSoloTrack(trackNumber); break; } // handle others... if(trackOptions != 0) return -1; return 0; #else ADL_UNUSED(device); ADL_UNUSED(trackNumber); ADL_UNUSED(trackOptions); return -1; #endif } OPNMIDI_EXPORT void opn2_panic(struct OPN2_MIDIPlayer *device) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_panic(); } OPNMIDI_EXPORT void opn2_rt_resetState(struct OPN2_MIDIPlayer *device) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_ResetState(); } OPNMIDI_EXPORT int opn2_rt_noteOn(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note, OPN2_UInt8 velocity) { if(!device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return (int)play->realTime_NoteOn(channel, note, velocity); } OPNMIDI_EXPORT void opn2_rt_noteOff(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_NoteOff(channel, note); } OPNMIDI_EXPORT void opn2_rt_noteAfterTouch(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note, OPN2_UInt8 atVal) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_NoteAfterTouch(channel, note, atVal); } OPNMIDI_EXPORT void opn2_rt_channelAfterTouch(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 atVal) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_ChannelAfterTouch(channel, atVal); } OPNMIDI_EXPORT void opn2_rt_controllerChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 type, OPN2_UInt8 value) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_Controller(channel, type, value); } OPNMIDI_EXPORT void opn2_rt_patchChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 patch) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_PatchChange(channel, patch); } OPNMIDI_EXPORT void opn2_rt_pitchBend(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt16 pitch) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_PitchBend(channel, pitch); } OPNMIDI_EXPORT void opn2_rt_pitchBendML(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 msb, OPN2_UInt8 lsb) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_PitchBend(channel, msb, lsb); } OPNMIDI_EXPORT void opn2_rt_bankChangeLSB(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 lsb) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_BankChangeLSB(channel, lsb); } OPNMIDI_EXPORT void opn2_rt_bankChangeMSB(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 msb) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_BankChangeMSB(channel, msb); } OPNMIDI_EXPORT void opn2_rt_bankChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_SInt16 bank) { if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_BankChange(channel, (uint16_t)bank); } OPNMIDI_EXPORT int opn2_rt_systemExclusive(struct OPN2_MIDIPlayer *device, const OPN2_UInt8 *msg, size_t size) { if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); return play->realTime_SysEx(msg, size); }