diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d3a0fdf8..c4fca568 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,8 +26,8 @@ jobs: shell: bash, } - { - name: Linux Clang, - os: ubuntu-latest, + name: macOS Clang, + os: macos-latest, compiler: clang, shell: bash, } @@ -42,7 +42,7 @@ jobs: steps: - name: Install dependencies (Linux) - if: matrix.config.os == 'ubuntu-latest' + if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install \ @@ -54,6 +54,21 @@ jobs: libxmp-dev \ ninja-build + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install \ + cmake \ + ninja \ + pkg-config \ + sdl2 \ + sdl2_net \ + openal-soft \ + libsndfile \ + fluid-synth \ + libxmp + - name: Install dependencies (MSYS2) if: matrix.config.shell == 'msys2 {0}' uses: msys2/setup-msys2@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 68dd36cc..319334c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,8 @@ check_symbol_exists(strncasecmp "strings.h" HAVE_DECL_STRNCASECMP) option(CMAKE_FIND_PACKAGE_PREFER_CONFIG "Lookup package config files before using find modules" ON) +set(CMAKE_FIND_FRAMEWORK NEVER) + # Library requirements. find_package(SDL2 2.0.18 REQUIRED) find_package(SDL2_net REQUIRED) diff --git a/cmake/FindOpenAL.cmake b/cmake/FindOpenAL.cmake index 0f30ad02..86c2090e 100644 --- a/cmake/FindOpenAL.cmake +++ b/cmake/FindOpenAL.cmake @@ -1,6 +1,3 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - #[=======================================================================[.rst: FindOpenAL ---------- @@ -12,23 +9,6 @@ header file, **not** ``#include ``. The reason for this is that the latter is not entirely portable. Windows/Creative Labs does not by default put their headers in ``AL/`` and macOS uses the convention ````. -Hints -^^^^^ - -Environment variable ``$OPENALDIR`` can be used to set the prefix of OpenAL -installation to be found. - -By default on macOS, system framework is search first. In other words, -OpenAL is searched in the following order: - -1. System framework: ``/System/Library/Frameworks``, whose priority can be - changed via setting the :variable:`CMAKE_FIND_FRAMEWORK` variable. -2. Environment variable ``$OPENALDIR``. -3. System paths. -4. User-compiled framework: ``~/Library/Frameworks``. -5. Manually compiled framework: ``/Library/Frameworks``. -6. Add-on package: ``/opt``. - IMPORTED Targets ^^^^^^^^^^^^^^^^ @@ -50,27 +30,16 @@ This module defines the following variables: Human-readable string containing the version of OpenAL #]=======================================================================] -# For Windows, Creative Labs seems to have added a registry key for their -# OpenAL 1.1 installer. I have added that key to the list of search paths, -# however, the key looks like it could be a little fragile depending on -# if they decide to change the 1.00.0000 number for bug fix releases. -# Also, they seem to have laid down groundwork for multiple library platforms -# which puts the library in an extra subdirectory. Currently there is only -# Win32 and I have hardcoded that here. This may need to be adjusted as -# platforms are introduced. -# The OpenAL 1.0 installer doesn't seem to have a useful key I can use. -# I do not know if the Nvidia OpenAL SDK has a registry key. +if(APPLE) + set(ENV{PKG_CONFIG_PATH} "/usr/local/opt/openal-soft/lib/pkgconfig") +endif() + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_OpenAL QUIET OpenAL) find_path(OPENAL_INCLUDE_DIR al.h - HINTS - ENV OPENALDIR - PATHS - ~/Library/Frameworks - /Library/Frameworks - /opt - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Creative\ Labs\\OpenAL\ 1.1\ Software\ Development\ Kit\\1.00.0000;InstallDir] PATH_SUFFIXES include/AL include/OpenAL include AL OpenAL - ) + HINTS "${PC_OpenAL_INCLUDEDIR}") if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_OpenAL_ARCH_DIR libs/Win64) @@ -80,37 +49,21 @@ endif() find_library(OPENAL_LIBRARY NAMES OpenAL al openal OpenAL32 - HINTS - ENV OPENALDIR - PATHS - ~/Library/Frameworks - /Library/Frameworks - /opt - [HKEY_LOCAL_MACHINE\\SOFTWARE\\Creative\ Labs\\OpenAL\ 1.1\ Software\ Development\ Kit\\1.00.0000;InstallDir] PATH_SUFFIXES libx32 lib64 lib libs64 libs ${_OpenAL_ARCH_DIR} - ) + HINTS "${PC_OpenAL_LIBDIR}") unset(_OpenAL_ARCH_DIR) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - OpenAL +find_package_handle_standard_args(OpenAL REQUIRED_VARS OPENAL_LIBRARY OPENAL_INCLUDE_DIR - VERSION_VAR OPENAL_VERSION_STRING - ) + VERSION_VAR OPENAL_VERSION_STRING) mark_as_advanced(OPENAL_LIBRARY OPENAL_INCLUDE_DIR) if(OPENAL_FOUND AND NOT TARGET OpenAL::OpenAL) - if(OPENAL_LIBRARY MATCHES "/([^/]+)\\.framework$") - add_library(OpenAL::OpenAL INTERFACE IMPORTED) - set_target_properties(OpenAL::OpenAL PROPERTIES - INTERFACE_LINK_LIBRARIES "${OPENAL_LIBRARY}") - else() add_library(OpenAL::OpenAL UNKNOWN IMPORTED) set_target_properties(OpenAL::OpenAL PROPERTIES - IMPORTED_LOCATION "${OPENAL_LIBRARY}") - endif() - set_target_properties(OpenAL::OpenAL PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENAL_INCLUDE_DIR}") + IMPORTED_LOCATION "${OPENAL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPENAL_INCLUDE_DIR}") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a5cad62..bc99d427 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -124,30 +124,6 @@ set(WOOF_SOURCES z_zone.c z_zone.h ../miniz/miniz.c ../miniz/miniz.h) -# Some platforms require standard libraries to be linked against. -if(HAVE_LIBM) - list(APPEND WOOF_LIBRARIES m) -endif() - -if(WIN32) - list(APPEND WOOF_SOURCES i_winmusic.c) - list(APPEND WOOF_LIBRARIES winmm) - if(MSVC) - list(APPEND WOOF_SOURCES - ../win32/win_opendir.c ../win32/win_opendir.h) - endif() -endif() - -if(FluidSynth_FOUND) - list(APPEND WOOF_SOURCES i_flmusic.c) - list(APPEND WOOF_LIBRARIES FluidSynth::libfluidsynth) -endif() - -if(libxmp_FOUND) - list(APPEND WOOF_SOURCES i_xmp.c) - list(APPEND WOOF_LIBRARIES libxmp::xmp) -endif() - # Standard target definition if(WIN32) add_library(woof SHARED EXCLUDE_FROM_ALL ${WOOF_SOURCES}) @@ -194,6 +170,36 @@ target_link_libraries(woof PRIVATE ${WOOF_LIBRARIES} opl textscreen) +# Some platforms require standard libraries to be linked against. +if(HAVE_LIBM) + target_link_libraries(woof PRIVATE m) +endif() + +if(WIN32) + target_sources(woof PRIVATE i_winmusic.c) + target_link_libraries(woof PRIVATE winmm) + if(MSVC) + target_sources(woof PRIVATE + ../win32/win_opendir.c ../win32/win_opendir.h) + endif() +endif() + +if(APPLE) + target_sources(woof PRIVATE i_macmusic.c) + target_link_libraries(woof PRIVATE + -Wl,-framework,AudioToolbox -Wl,-framework,AudioUnit -Wl,-framework,CoreServices) +endif() + +if(FluidSynth_FOUND) + target_sources(woof PRIVATE i_flmusic.c) + target_link_libraries(woof PRIVATE FluidSynth::libfluidsynth) +endif() + +if(libxmp_FOUND) + target_sources(woof PRIVATE i_xmp.c) + target_link_libraries(woof PRIVATE libxmp::xmp) +endif() + # Optional features. # # Our defines are not namespaced, so we pass them at compile-time instead of diff --git a/src/i_macmusic.c b/src/i_macmusic.c new file mode 100644 index 00000000..30a08465 --- /dev/null +++ b/src/i_macmusic.c @@ -0,0 +1,367 @@ +// +// Copyright (C) 2006-2020 by The Odamex Team. +// Copyright(C) 2023 Roman Fomin +// +// 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. +// + +/* +native_midi_macosx: Native Midi support on Mac OS X for the SDL_mixer library +Copyright (C) 2009 Ryan C. Gordon + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "doomtype.h" +#include "i_sound.h" +#include "memio.h" +#include "mus2mid.h" + +#include +#include +#include + +static MusicPlayer player; +static MusicSequence sequence; +static AudioUnit unit; +static AUGraph graph; +static AUNode synth; +static AUNode output; + +static boolean music_initialized; + +static boolean I_MAC_InitMusic(int device) +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + ComponentDescription d; +#else + AudioComponentDescription d; +#endif + + d.componentType = kAudioUnitType_MusicDevice; + d.componentSubType = kAudioUnitSubType_DLSSynth; + d.componentManufacturer = kAudioUnitManufacturer_Apple; + d.componentFlags = 0; + d.componentFlagsMask = 0; + + NewAUGraph(&graph); +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + AUGraphNewNode(graph, &d, 0, NULL, &synth); +#else + AUGraphAddNode(graph, &d, &synth); +#endif + + d.componentType = kAudioUnitType_Output; + d.componentSubType = kAudioUnitSubType_DefaultOutput; + d.componentManufacturer = kAudioUnitManufacturer_Apple; + d.componentFlags = 0; + d.componentFlagsMask = 0; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + AUGraphNewNode(graph, &d, 0, NULL, &output); +#else + AUGraphAddNode(graph, &d, &output); +#endif + + if (AUGraphConnectNodeInput(graph, synth, 0, output, 0) != noErr) + { + fprintf(stderr, "I_MAC_InitMusic: AUGraphConnectNodeInput failed.\n"); + return false; + } + + if (AUGraphOpen(graph) != noErr) + { + fprintf(stderr, "I_MAC_InitMusic: AUGraphOpen failed.\n"); + return false; + } + + if (AUGraphInitialize(graph) != noErr) + { + fprintf(stderr, "I_MAC_InitMusic: AUGraphInitialize failed.\n"); + return false; + } + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 // this is deprecated, but works back to 10.0 + if (AUGraphGetNodeInfo(graph, output, NULL, NULL, NULL, &unit) != noErr) +#else // not deprecated, but requires 10.5 or later + if (AUGraphNodeInfo(graph, output, NULL, &unit) != noErr) +#endif + { + fprintf(stderr, "I_MAC_InitMusic: AUGraphGetNodeInfo failed.\n"); + return false; + } + + if (NewMusicPlayer(&player) != noErr) + { + fprintf(stderr, "I_MAC_InitMusic: Music player creation failed using AudioToolbox.\n"); + return false; + } + + printf("I_MAC_InitMusic: Music playback enabled using AudioToolbox.\n"); + music_initialized = true; + + return true; +} + +static void I_MAC_SetMusicVolume(int volume) +{ + if (!music_initialized) + return; + + if (AudioUnitSetParameter(unit, + kAudioUnitParameterUnit_LinearGain, + kAudioUnitScope_Output, + 0, (float) volume / 15, 0) != noErr) + { + fprintf(stderr, "I_MAC_SetMusicVolume: AudioUnitSetParameter failed.\n"); + } +} + +static void I_MAC_PauseSong(void *handle) +{ + if (!music_initialized) + return; + + MusicPlayerStop(player); +} + +static void I_MAC_ResumeSong(void *handle) +{ + if (!music_initialized) + return; + + MusicPlayerStart(player); +} + +static void I_MAC_PlaySong(void *handle, boolean looping) +{ + UInt32 i, ntracks; + MusicTimeStamp maxtime = 0; + + if (!music_initialized) + return; + + if (MusicSequenceSetAUGraph(sequence, graph) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicSequenceSetAUGraph failed.\n"); + return; + } + + if (MusicPlayerSetSequence(player, sequence) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicPlayerSetSequence failed.\n"); + return; + } + + if (MusicPlayerPreroll(player) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicPlayerPreroll failed.\n"); + return; + } + + if (MusicSequenceGetTrackCount(sequence, &ntracks) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicSequenceGetTrackCount failed.\n"); + return; + } + + for (i = 0; i < ntracks; i++) + { + MusicTrack track; + MusicTimeStamp time; + UInt32 size = sizeof(time); + + if (MusicSequenceGetIndTrack(sequence, i, &track) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicSequenceGetIndTrack failed.\n"); + return; + } + + if (MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, + &time, &size) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicTrackGetProperty failed.\n"); + return; + } + + if (time > maxtime) + { + maxtime = time; + } + } + + for (i = 0; i < ntracks; i++) + { + MusicTrack track; + MusicTrackLoopInfo info; + + if (MusicSequenceGetIndTrack(sequence, i, &track) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicSequenceGetIndTrack failed.\n"); + return; + } + + info.loopDuration = maxtime; + info.numberOfLoops = (looping ? 0 : 1); + + if (MusicTrackSetProperty(track, kSequenceTrackProperty_LoopInfo, + &info, sizeof(info)) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicTrackSetProperty failed.\n"); + return; + } + } + + if (MusicPlayerStart(player) != noErr) + { + fprintf(stderr, "I_MAC_PlaySong: MusicPlayerStart failed.\n"); + } +} + +static void I_MAC_StopSong(void *handle) +{ + if (!music_initialized) + return; + + MusicPlayerStop(player); + + // needed to prevent error and memory leak when disposing sequence + MusicPlayerSetSequence(player, NULL); +} + +static void *I_MAC_RegisterSong(void *data, int len) +{ + CFDataRef data_ref = NULL; + + if (!music_initialized) + return NULL; + + if (NewMusicSequence(&sequence) != noErr) + { + fprintf(stderr, "I_MAC_RegisterSong: Unable to create AudioUnit sequence.\n"); + return NULL; + } + + if (IsMid(data, len)) + { + data_ref = CFDataCreate(NULL, (const UInt8 *)data, len); + } + else + { + // Assume a MUS file and try to convert + MEMFILE *instream; + MEMFILE *outstream; + void *outbuf; + size_t outbuf_len; + + instream = mem_fopen_read(data, len); + outstream = mem_fopen_write(); + + if (mus2mid(instream, outstream) == 0) + { + mem_get_buf(outstream, &outbuf, &outbuf_len); + data_ref = CFDataCreate(NULL, (const UInt8 *)outbuf, outbuf_len); + } + + mem_fclose(instream); + mem_fclose(outstream); + } + + if (data_ref == NULL) + { + fprintf(stderr, "I_MAC_RegisterSong: Failed to load MID.\n"); + DisposeMusicSequence(sequence); + return NULL; + } + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + // MusicSequenceLoadSMFData() (avail. in 10.2, no 64 bit) is equivalent to + // calling MusicSequenceLoadSMFDataWithFlags() with a flags value of 0 + // (avail. in 10.3, avail. 64 bit). So, we use MusicSequenceLoadSMFData() for + // powerpc versions but the *WithFlags() on intel which require 10.4 anyway. + #if defined(__ppc__) || defined(__POWERPC__) + if (MusicSequenceLoadSMFData(sequence, data_ref) != noErr) + #else + if (MusicSequenceLoadSMFDataWithFlags(sequence, data_ref, 0) != noErr) + #endif +#else // MusicSequenceFileLoadData() requires 10.5 or later. + if (MusicSequenceFileLoadData(sequence, data_ref, 0, 0) != noErr) +#endif + { + DisposeMusicSequence(sequence); + CFRelease(data_ref); + return NULL; + } + + CFRelease(data_ref); + + return (void *)1; +} + +static void I_MAC_UnRegisterSong(void *handle) +{ + if (!music_initialized) + return; + + DisposeMusicSequence(sequence); +} + +static void I_MAC_ShutdownMusic(void) +{ + if (!music_initialized) + return; + + I_MAC_StopSong(NULL); + I_MAC_UnRegisterSong(NULL); + + DisposeMusicPlayer(player); + DisposeAUGraph(graph); + + music_initialized = false; +} + +static int I_MAC_DeviceList(const char* devices[], int size, int *current_device) +{ + *current_device = 0; + if (size > 0) + { + devices[0] = "Native"; + return 1; + } + return 0; +} + +music_module_t music_mac_module = +{ + I_MAC_InitMusic, + I_MAC_ShutdownMusic, + I_MAC_SetMusicVolume, + I_MAC_PauseSong, + I_MAC_ResumeSong, + I_MAC_RegisterSong, + I_MAC_PlaySong, + I_MAC_StopSong, + I_MAC_UnRegisterSong, + I_MAC_DeviceList, +}; diff --git a/src/i_oalmusic.c b/src/i_oalmusic.c index 897b8599..3cf02967 100644 --- a/src/i_oalmusic.c +++ b/src/i_oalmusic.c @@ -22,7 +22,6 @@ #include "SDL.h" #include "al.h" -#include "alext.h" #include "doomtype.h" #include "i_sndfile.h" diff --git a/src/i_sound.c b/src/i_sound.c index 59050ad5..66085bdf 100644 --- a/src/i_sound.c +++ b/src/i_sound.c @@ -31,6 +31,7 @@ // Music modules extern music_module_t music_win_module; +extern music_module_t music_mac_module; extern music_module_t music_fl_module; extern music_module_t music_oal_module; extern music_module_t music_opl_module; @@ -45,6 +46,8 @@ static music_modules_t music_modules[] = { #if defined(_WIN32) { &music_win_module, 1 }, +#elif defined(__APPLE__) + { &music_mac_module, 1 }, #endif #if defined(HAVE_FLUIDSYNTH) { &music_fl_module, 1 },