initial implementation of macOS native MIDI module (#1048)

* set CMAKE_FIND_FRAMEWORK to NEVER

* use pkg-config

* simplify FindOpenAL.cmake

* try to fix build

* integrate code from Odamex
This commit is contained in:
Roman Fomin 2023-05-11 07:29:38 +07:00 committed by GitHub
parent 69b4b5a81a
commit b86a45f2a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 432 additions and 87 deletions

View File

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

View File

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

View File

@ -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 <AL/al.h>``. 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 ``<OpenAL/al.h>``.
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()

View File

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

367
src/i_macmusic.c Normal file
View File

@ -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 <icculus@icculus.org>
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 <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AvailabilityMacros.h>
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,
};

View File

@ -22,7 +22,6 @@
#include "SDL.h"
#include "al.h"
#include "alext.h"
#include "doomtype.h"
#include "i_sndfile.h"

View File

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