From de9c9221fe13cc8e8846c459f21a99585f80ea9e Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 4 Mar 2017 14:53:31 +0200 Subject: [PATCH] Added AudioToolbox MIDI device for macOS This device is the default one for OpenAL backend on Apple's platform --- src/CMakeLists.txt | 1 + src/sound/i_musicinterns.h | 41 +++ src/sound/music_audiotoolbox_mididevice.cpp | 292 ++++++++++++++++++++ src/sound/oalsound.cpp | 2 + 4 files changed, 336 insertions(+) create mode 100644 src/sound/music_audiotoolbox_mididevice.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8913ef6ea..f2b160fd1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -873,6 +873,7 @@ set( FASTMATH_SOURCES sound/music_timidity_mididevice.cpp sound/music_wildmidi_mididevice.cpp sound/music_win_mididevice.cpp + sound/music_audiotoolbox_mididevice.cpp sound/oalsound.cpp sound/sndfile_decoder.cpp sound/music_pseudo_mididevice.cpp diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index a86429e97..d073625b4 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -16,6 +16,9 @@ #define FALSE 0 #define TRUE 1 #endif +#ifdef __APPLE__ +#include +#endif // __APPLE__ #include "tempfiles.h" #include "oplsynth/opl_mus_player.h" #include "c_cvars.h" @@ -145,6 +148,44 @@ protected: }; #endif +// AudioToolbox implementation of a MIDI output device ---------------------- + +#ifdef __APPLE__ + +class AudioToolboxMIDIDevice : public MIDIDevice +{ +public: + virtual int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userData) override; + virtual void Close() override; + virtual bool IsOpen() const override; + virtual int GetTechnology() const override; + virtual int SetTempo(int tempo) override; + virtual int SetTimeDiv(int timediv) override; + virtual int StreamOut(MIDIHDR *data) override; + virtual int StreamOutSync(MIDIHDR *data) override; + virtual int Resume() override; + virtual void Stop() override; + virtual int PrepareHeader(MIDIHDR* data) override; + virtual bool FakeVolume() override { return true; } + virtual bool Pause(bool paused) override; + virtual bool Preprocess(MIDIStreamer *song, bool looping) override; + +private: + MusicPlayer m_player = nullptr; + MusicSequence m_sequence = nullptr; + AudioUnit m_audioUnit = nullptr; + CFRunLoopTimerRef m_timer = nullptr; + MusicTimeStamp m_length = 0; + + typedef void (*Callback)(unsigned int, void *, DWORD, DWORD); + Callback m_callback = nullptr; + void* m_userData = nullptr; + + static void TimerCallback(CFRunLoopTimerRef timer, void* info); +}; + +#endif // __APPLE__ + // Base class for pseudo-MIDI devices --------------------------------------- class PseudoMIDIDevice : public MIDIDevice diff --git a/src/sound/music_audiotoolbox_mididevice.cpp b/src/sound/music_audiotoolbox_mididevice.cpp new file mode 100644 index 000000000..3dc57fdb0 --- /dev/null +++ b/src/sound/music_audiotoolbox_mididevice.cpp @@ -0,0 +1,292 @@ +// +//--------------------------------------------------------------------------- +// +// MIDI device for Apple's macOS using AudioToolbox framework +// Copyright(C) 2017 Alexey Lysiuk +// All rights reserved. +// +// This program 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 3 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +// Implementation is loosely based on macOS native MIDI support from SDL_mixer + +#ifdef __APPLE__ + +#include "i_musicinterns.h" +#include "templates.h" + +#define AT_MIDI_CHECK_ERROR(CALL,...) \ +{ \ + const OSStatus result = CALL; \ + if (noErr != result) \ + { \ + DPrintf(DMSG_ERROR, \ + "Failed with error 0x%08X at " __FILE__ ":%d:\n> %s", \ + result, __LINE__, #CALL); \ + return __VA_ARGS__; \ + } \ +} + +int AudioToolboxMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userData) +{ + AT_MIDI_CHECK_ERROR(NewMusicPlayer(&m_player), false); + AT_MIDI_CHECK_ERROR(NewMusicSequence(&m_sequence), false); + AT_MIDI_CHECK_ERROR(MusicPlayerSetSequence(m_player, m_sequence), false); + + CFRunLoopTimerContext context = { 0, this, nullptr, nullptr, nullptr }; + m_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0.1, 0, 0, TimerCallback, &context); + + if (nullptr == m_timer) + { + DPrintf(DMSG_ERROR, "Failed with create timer for MIDI playback"); + return 1; + } + + CFRunLoopAddTimer(CFRunLoopGetMain(), m_timer, kCFRunLoopDefaultMode); + + m_callback = callback; + m_userData = userData; + + return 0; +} + +void AudioToolboxMIDIDevice::Close() +{ + m_length = 0; + m_audioUnit = nullptr; + + m_callback = nullptr; + m_userData = nullptr; + + if (nullptr != m_timer) + { + CFRunLoopRemoveTimer(CFRunLoopGetMain(), m_timer, kCFRunLoopDefaultMode); + + CFRelease(m_timer); + m_timer = nullptr; + } + + if (nullptr != m_sequence) + { + DisposeMusicSequence(m_sequence); + m_sequence = nullptr; + } + + if (nullptr != m_player) + { + DisposeMusicPlayer(m_player); + m_player = nullptr; + } +} + +bool AudioToolboxMIDIDevice::IsOpen() const +{ + return nullptr != m_player + && nullptr != m_sequence + && nullptr != m_timer; +} + +int AudioToolboxMIDIDevice::GetTechnology() const +{ + return MOD_SWSYNTH; +} + +int AudioToolboxMIDIDevice::SetTempo(int tempo) +{ + return 0; +} + +int AudioToolboxMIDIDevice::SetTimeDiv(int timediv) +{ + return 0; +} + +int AudioToolboxMIDIDevice::StreamOut(MIDIHDR* data) +{ + return 0; +} + +int AudioToolboxMIDIDevice::StreamOutSync(MIDIHDR* data) +{ + return 0; +} + +int AudioToolboxMIDIDevice::Resume() +{ + AT_MIDI_CHECK_ERROR(MusicPlayerSetTime(m_player, 0), false); + AT_MIDI_CHECK_ERROR(MusicPlayerPreroll(m_player), false); + + if (nullptr == m_audioUnit) + { + AUGraph graph; + AT_MIDI_CHECK_ERROR(MusicSequenceGetAUGraph(m_sequence, &graph), false); + + UInt32 nodecount; + AT_MIDI_CHECK_ERROR(AUGraphGetNodeCount(graph, &nodecount), false); + + for (UInt32 i = 0; i < nodecount; ++i) + { + AUNode node; + AT_MIDI_CHECK_ERROR(AUGraphGetIndNode(graph, i, &node), false); + + AudioComponentDescription desc = {}; + AudioUnit audioUnit = nullptr; + AT_MIDI_CHECK_ERROR(AUGraphNodeInfo(graph, node, &desc, &audioUnit), false); + + if ( kAudioUnitType_Output != desc.componentType + || kAudioUnitSubType_DefaultOutput != desc.componentSubType) + { + continue; + } + + const float volume = clamp(snd_musicvolume * relative_volume, 0.f, 1.f); + AT_MIDI_CHECK_ERROR(AudioUnitSetParameter(audioUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0), false); + + m_audioUnit = audioUnit; + break; + } + } + + AT_MIDI_CHECK_ERROR(MusicPlayerStart(m_player), false); + + return 0; +} + +void AudioToolboxMIDIDevice::Stop() +{ + AT_MIDI_CHECK_ERROR(MusicPlayerStop(m_player)); +} + +int AudioToolboxMIDIDevice::PrepareHeader(MIDIHDR* data) +{ + MIDIHDR* events = data; + DWORD position = 0; + + while (nullptr != events) + { + DWORD* const event = reinterpret_cast(events->lpData + position); + const DWORD message = event[2]; + + if (0 == MEVT_EVENTTYPE(message)) + { + static const DWORD VOLUME_CHANGE_EVENT = 7; + + const DWORD status = message & 0xFF; + const DWORD param1 = (message >> 8) & 0x7F; + const DWORD param2 = (message >> 16) & 0x7F; + + if (nullptr != m_audioUnit && MIDI_CTRLCHANGE == status && VOLUME_CHANGE_EVENT == param1) + { + AT_MIDI_CHECK_ERROR(AudioUnitSetParameter(m_audioUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, param2 / 100.f, 0), false); + } + } + + // Advance to next event + position += 12 + ( (message < 0x80000000) + ? 0 + : ((MEVT_EVENTPARM(message) + 3) & ~3) ); + + // Did we use up this buffer? + if (position >= events->dwBytesRecorded) + { + events = events->lpNext; + position = 0; + } + + if (nullptr == events) + { + break; + } + } + + return 0; +} + +bool AudioToolboxMIDIDevice::Pause(bool paused) +{ + return false; +} + +static MusicTimeStamp GetSequenceLength(MusicSequence sequence) +{ + UInt32 trackCount; + AT_MIDI_CHECK_ERROR(MusicSequenceGetTrackCount(sequence, &trackCount), 0); + + MusicTimeStamp result = 0; + + for (UInt32 i = 0; i < trackCount; ++i) + { + MusicTrack track; + AT_MIDI_CHECK_ERROR(MusicSequenceGetIndTrack(sequence, i, &track), 0); + + MusicTimeStamp trackLength = 0; + UInt32 trackLengthSize = sizeof trackLength; + + AT_MIDI_CHECK_ERROR(MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &trackLength, &trackLengthSize), 0); + + if (result < trackLength) + { + result = trackLength; + } + } + + return result; +} + +bool AudioToolboxMIDIDevice::Preprocess(MIDIStreamer* song, bool looping) +{ + assert(nullptr != song); + + TArray midi; + song->CreateSMF(midi, looping ? 0 : 1); + + CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, &midi[0], midi.Size(), kCFAllocatorNull); + if (nullptr == data) + { + DPrintf(DMSG_ERROR, "Failed with create CFDataRef for MIDI song"); + return false; + } + + AT_MIDI_CHECK_ERROR(MusicSequenceFileLoadData(m_sequence, data, kMusicSequenceFile_MIDIType, 0), CFRelease(data), false); + + CFRelease(data); + + m_length = GetSequenceLength(m_sequence); + + return true; +} + +void AudioToolboxMIDIDevice::TimerCallback(CFRunLoopTimerRef timer, void* info) +{ + AudioToolboxMIDIDevice* const self = static_cast(info); + + if (nullptr != self->m_callback) + { + self->m_callback(MOM_DONE, self->m_userData, 0, 0); + } + + MusicTimeStamp currentTime = 0; + AT_MIDI_CHECK_ERROR(MusicPlayerGetTime(self->m_player, ¤tTime)); + + if (currentTime > self->m_length) + { + MusicPlayerSetTime(self->m_player, 0); + } +} + +#undef AT_MIDI_CHECK_ERROR + +#endif // __APPLE__ diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp index e274c7bbf..6bd2dc091 100644 --- a/src/sound/oalsound.cpp +++ b/src/sound/oalsound.cpp @@ -2057,6 +2057,8 @@ MIDIDevice* OpenALSoundRenderer::CreateMIDIDevice() const #ifdef _WIN32 extern UINT mididevice; return new WinMIDIDevice(mididevice); +#elif defined __APPLE__ + return new AudioToolboxMIDIDevice; #else return new OPLMIDIDevice(nullptr); #endif