# HG changeset patch # User michi_cc # Date 2009-10-04 21:24:09 # Node ID 2fe2fe099c0ba3979d9cc16188ab3a9acdf635cd # Parent 3691ad62820d94ffa4c21fb0ce52380457a26e72 (svn r17710) -Feature [FS#3223]: [OSX] Add a MIDI driver using Cocoa/CoreAudio. diff --git a/config.lib b/config.lib --- a/config.lib +++ b/config.lib @@ -1320,7 +1320,7 @@ make_cflags_and_ldflags() { if [ "$with_cocoa" != "0" ]; then CFLAGS="$CFLAGS -DWITH_COCOA" - LIBS="$LIBS -F/System/Library/Frameworks -framework Cocoa -framework Carbon -framework AudioUnit" + LIBS="$LIBS -F/System/Library/Frameworks -framework Cocoa -framework Carbon -framework AudioUnit -framework AudioToolbox" if [ "$enable_cocoa_quartz" != "0" ]; then CFLAGS="$CFLAGS -DENABLE_COCOA_QUARTZ" diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -327,6 +327,7 @@ zoom_type.h #if WIN32 #else music/bemidi.h +music/cocoa_m.h music/extmidi.h music/libtimidity.h music/os2_m.h @@ -919,6 +920,7 @@ sound/null_s.cpp video/cocoa/fullscreen.mm video/cocoa/wnd_quartz.mm video/cocoa/wnd_quickdraw.mm + music/cocoa_m.cpp sound/cocoa_s.cpp os/macosx/splash.cpp #end diff --git a/src/music/cocoa_m.cpp b/src/music/cocoa_m.cpp new file mode 100644 --- /dev/null +++ b/src/music/cocoa_m.cpp @@ -0,0 +1,241 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** + * @file cocoa_m.cpp + * @brief MIDI music player for MacOS X using CoreAudio. + */ + + +#ifdef WITH_COCOA + +#include "../stdafx.h" +#include "../os/macosx/macos.h" +#include "cocoa_m.h" +#include "../debug.h" + +#define Rect OTTDRect +#define Point OTTDPoint +#include +#include +#include +#undef Rect +#undef Point + +static FMusicDriver_Cocoa iFMusicDriver_Cocoa; + + +static MusicPlayer _player = NULL; +static MusicSequence _sequence = NULL; +static MusicTimeStamp _seq_length = 0; +static bool _playing = false; +static byte _volume = 127; + + +/** Set the volume of the current sequence. */ +static void DoSetVolume() +{ + if (_sequence == NULL) return; + + AUGraph graph; + MusicSequenceGetAUGraph(_sequence, &graph); + + AudioUnit output_unit = NULL; + + /* Get output audio unit */ + UInt32 node_count = 0; + AUGraphGetNodeCount(graph, &node_count); + for (UInt32 i = 0; i < node_count; i++) { + AUNode node; + AUGraphGetIndNode(graph, i, &node); + + AudioUnit unit; + OSType comp_type = 0; + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (MacOSVersionIsAtLeast(10, 5, 0)) { + /* The 10.6 SDK has changed the function prototype of + * AUGraphNodeInfo. This is a binary compatible change, + * but we need to get the type declaration right or + * risk compilation errors. The header AudioComponent.h + * was introduced in 10.6 so use it to decide which + * type definition to use. */ +#ifdef __AUDIOCOMPONENT_H__ + AudioComponentDescription desc; +#else + ComponentDescription desc; +#endif + AUGraphNodeInfo(graph, node, &desc, &unit); + comp_type = desc.componentType; + } else +#endif + { +#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) + ComponentDescription desc; + AUGraphGetNodeInfo(graph, node, &desc, NULL, NULL, &unit); + comp_type = desc.componentType; +#endif + } + + if (comp_type == kAudioUnitType_Output) { + output_unit = unit; + break; + } + } + if (output_unit == NULL) { + DEBUG(driver, 1, "cocoa_m: Failed to get output node to set volume"); + return; + } + + Float32 vol = _volume / 127.0f; // 0 - +127 -> 0.0 - 1.0 + AudioUnitSetParameter(output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, vol, 0); +} + + +/** + * Initialized the MIDI player, including QuickTime initialization. + */ +const char *MusicDriver_Cocoa::Start(const char * const *parm) +{ + if (NewMusicPlayer(&_player) != noErr) return "failed to create music player"; + + return NULL; +} + + +/** + * Checks wether the player is active. + */ +bool MusicDriver_Cocoa::IsSongPlaying() +{ + if (!_playing) return false; + + MusicTimeStamp time = 0; + MusicPlayerGetTime(_player, &time); + return time < _seq_length; +} + + +/** + * Stops the MIDI player. + */ +void MusicDriver_Cocoa::Stop() +{ + if (_player != NULL) DisposeMusicPlayer(_player); + if (_sequence != NULL) DisposeMusicSequence(_sequence); +} + + +/** + * Starts playing a new song. + * + * @param filename Path to a MIDI file. + */ +void MusicDriver_Cocoa::PlaySong(const char *filename) +{ + DEBUG(driver, 2, "cocoa_m: trying to play '%s'", filename); + + this->StopSong(); + if (_sequence != NULL) { + DisposeMusicSequence(_sequence); + _sequence = NULL; + } + + if (NewMusicSequence(&_sequence) != noErr) { + DEBUG(driver, 0, "cocoa_m: Failed to create music sequence"); + return; + } + + const char *os_file = OTTD2FS(filename); + CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)os_file, strlen(os_file), false); + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (MacOSVersionIsAtLeast(10, 5, 0)) { + if (MusicSequenceFileLoad(_sequence, url, 0, 0) != noErr) { + DEBUG(driver, 0, "cocoa_m: Failed to load MIDI file"); + CFRelease(url); + return; + } + } else +#endif + { +#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) + FSRef ref_file; + if (!CFURLGetFSRef(url, &ref_file)) { + DEBUG(driver, 0, "cocoa_m: Failed to make FSRef"); + CFRelease(url); + return; + } + if (MusicSequenceLoadSMFWithFlags(_sequence, &ref_file, 0) != noErr) { + DEBUG(driver, 0, "cocoa_m: Failed to load MIDI file old style"); + CFRelease(url); + return; + } +#endif + } + CFRelease(url); + + /* Construct audio graph */ + AUGraph graph = NULL; + + MusicSequenceGetAUGraph(_sequence, &graph); + AUGraphOpen(graph); + if (AUGraphInitialize(graph) != noErr) { + DEBUG(driver, 0, "cocoa_m: Failed to initialize AU graph"); + return; + } + + /* Figure out sequence length */ + UInt32 num_tracks; + MusicSequenceGetTrackCount(_sequence, &num_tracks); + _seq_length = 0; + for (UInt32 i = 0; i < num_tracks; i++) { + MusicTrack track = NULL; + MusicTimeStamp track_length = 0; + UInt32 prop_size = sizeof(MusicTimeStamp); + MusicSequenceGetIndTrack(_sequence, i, &track); + MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &track_length, &prop_size); + if (track_length > _seq_length) _seq_length = track_length; + } + /* Add 8 beats for reverb/long note release */ + _seq_length += 8; + + DoSetVolume(); + MusicPlayerSetSequence(_player, _sequence); + MusicPlayerPreroll(_player); + if (MusicPlayerStart(_player) != noErr) return; + _playing = true; + + DEBUG(driver, 3, "cocoa_m: playing '%s'", filename); +} + + +/** + * Stops playing the current song, if the player is active. + */ +void MusicDriver_Cocoa::StopSong() +{ + MusicPlayerStop(_player); + MusicPlayerSetSequence(_player, NULL); + _playing = false; +} + + +/** + * Changes the playing volume of the MIDI player. + * + * @param vol The desired volume, range of the value is @c 0-127 + */ +void MusicDriver_Cocoa::SetVolume(byte vol) +{ + _volume = vol; + DoSetVolume(); +} + +#endif /* WITH_COCOA */ diff --git a/src/music/cocoa_m.h b/src/music/cocoa_m.h new file mode 100644 --- /dev/null +++ b/src/music/cocoa_m.h @@ -0,0 +1,41 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file cocoa_m.h Base of music playback via CoreAudio. */ + +#ifndef MUSIC_MACOSX_COCOA_H +#define MUSIC_MACOSX_COCOA_H + +#include "music_driver.hpp" + +class MusicDriver_Cocoa: public MusicDriver { +public: + /* virtual */ const char *Start(const char * const *param); + + /* virtual */ void Stop(); + + /* virtual */ void PlaySong(const char *filename); + + /* virtual */ void StopSong(); + + /* virtual */ bool IsSongPlaying(); + + /* virtual */ void SetVolume(byte vol); + /* virtual */ const char *GetName() const { return "cocoa"; } +}; + +class FMusicDriver_Cocoa: public MusicDriverFactory { +public: + static const int priority = 10; + /* virtual */ const char *GetName() { return "cocoa"; } + /* virtual */ const char *GetDescription() { return "Cocoa MIDI Driver"; } + /* virtual */ Driver *CreateInstance() { return new MusicDriver_Cocoa(); } +}; + +#endif /* MUSIC_MACOSX_COCOA_H */ diff --git a/src/music/qtmidi.h b/src/music/qtmidi.h --- a/src/music/qtmidi.h +++ b/src/music/qtmidi.h @@ -32,7 +32,7 @@ public: class FMusicDriver_QtMidi: public MusicDriverFactory { public: - static const int priority = 10; + static const int priority = 5; /* virtual */ const char *GetName() { return "qt"; } /* virtual */ const char *GetDescription() { return "QuickTime MIDI Driver"; } /* virtual */ Driver *CreateInstance() { return new MusicDriver_QtMidi(); }