unify native MIDI modules (#1635)

* remove midiStream functions, adapt for realtime output

* implement I_SleepUS

* add midiout.c, implement WINMM, ALSA, CoreMIDI and DLS Synth output

* simplify I_DeviceList functions

* display ALSA items last

* remove `midi_player` variable
This commit is contained in:
Roman Fomin 2024-04-18 07:29:27 +00:00 committed by GitHub
parent 572cd3c3b1
commit 6f62dc5829
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 2487 additions and 2727 deletions

View File

@ -67,6 +67,13 @@ find_package(SDL2 2.0.18 REQUIRED)
find_package(SDL2_net REQUIRED)
find_package(OpenAL REQUIRED)
find_package(SndFile 1.0.29 REQUIRED)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_package(ALSA REQUIRED)
endif()
if(ALSA_FOUND)
set(HAVE_ALSA TRUE)
endif()
if(OPENAL_VERSION_STRING VERSION_GREATER_EQUAL "1.22.0")
set(HAVE_AL_BUFFER_CALLBACK TRUE)

View File

@ -6,6 +6,7 @@
#cmakedefine HAVE_DIRENT_H
#cmakedefine01 HAVE_DECL_STRCASECMP
#cmakedefine01 HAVE_DECL_STRNCASECMP
#cmakedefine HAVE_ALSA
#cmakedefine HAVE_FLUIDSYNTH
#cmakedefine HAVE_LIBXMP
#cmakedefine HAVE_SNDFILE_MPEG

View File

@ -38,6 +38,7 @@ set(WOOF_SOURCES
i_input.c i_input.h
i_main.c
i_mbfsound.c
i_midimusic.c
i_oalmusic.c
i_oalsound.c i_oalsound.h
i_oalstream.h
@ -67,6 +68,7 @@ set(WOOF_SOURCES
memio.c memio.h
midifallback.c midifallback.h
midifile.c midifile.h
midiout.c midiout.h
mus2mid.c mus2mid.h
nano_bsp.c nano_bsp.h
net_client.c net_client.h
@ -186,7 +188,6 @@ if(HAVE_AL_BUFFER_CALLBACK)
endif()
if(WIN32)
target_sources(woof PRIVATE i_winmusic.c)
target_include_directories(woof PRIVATE "../win32")
target_link_libraries(woof PRIVATE winmm)
if(MSVC)
@ -196,9 +197,15 @@ if(WIN32)
endif()
if(APPLE)
target_sources(woof PRIVATE i_macmusic.c)
target_link_libraries(woof PRIVATE
-Wl,-framework,AudioToolbox -Wl,-framework,AudioUnit -Wl,-framework,CoreServices)
-Wl,-framework,AudioToolbox -Wl,-framework,AudioUnit
-Wl,-framework,CoreServices -Wl,-framework,CoreAudio
-Wl,-framework,CoreMIDI)
endif()
if(ALSA_FOUND)
target_include_directories(woof PRIVATE ${ALSA_INCLUDE_DIR})
target_link_libraries(woof PRIVATE ALSA::ALSA)
endif()
if(FluidSynth_FOUND)

View File

@ -2915,8 +2915,6 @@ void D_DoomMain(void)
// Update display, next frame, with current state.
if (screenvisible)
D_Display();
S_UpdateMusic();
}
}

View File

@ -26,7 +26,11 @@
#include "config.h"
#if __bool_true_false_are_defined
typedef int boolean;
#else
typedef enum {false, true} boolean;
#endif
typedef uint8_t byte;

View File

@ -43,7 +43,6 @@ typedef fluid_long_long_t fluid_int_t;
#include "w_wad.h"
#include "z_zone.h"
const char *soundfont_path = "";
char *soundfont_dir = "";
boolean mus_chorus;
boolean mus_reverb;
@ -137,10 +136,7 @@ static void GetSoundFonts(void)
{
char *left, *p, *dup_path;
if (array_size(soundfonts))
{
return;
}
array_clear(soundfonts);
// Split into individual dirs within the list.
dup_path = M_StringDuplicate(soundfont_dir);
@ -254,40 +250,23 @@ static boolean I_FL_InitStream(int device)
}
else
{
if (device == DEFAULT_MIDI_DEVICE)
GetSoundFonts();
if (device >= array_size(soundfonts))
{
GetSoundFonts();
device = 0;
for (int i = 0; i < array_size(soundfonts); ++i)
{
if (!strcasecmp(soundfonts[i], soundfont_path))
{
device = i;
break;
}
}
FreeSynthAndSettings();
return false;
}
if (array_size(soundfonts))
{
if (device >= array_size(soundfonts))
{
device = 0;
}
soundfont_path = soundfonts[device];
}
sf_id = fluid_synth_sfload(synth, soundfont_path, true);
sf_id = fluid_synth_sfload(synth, soundfonts[device], true);
}
if (sf_id == FLUID_FAILED)
{
char *errmsg;
errmsg =
M_StringJoin("Error loading FluidSynth soundfont: ",
lumpnum >= 0 ? "SNDFONT lump" : soundfont_path, NULL);
errmsg = M_StringJoin(
"Error loading FluidSynth soundfont: ",
lumpnum >= 0 ? "SNDFONT lump" : soundfonts[device], NULL);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, PROJECT_STRING, errmsg,
NULL);
free(errmsg);
@ -296,7 +275,7 @@ static boolean I_FL_InitStream(int device)
}
I_Printf(VB_INFO, "FluidSynth Init: Using '%s'.",
lumpnum >= 0 ? "SNDFONT lump" : soundfont_path);
lumpnum >= 0 ? "SNDFONT lump" : soundfonts[device]);
return true;
}
@ -304,6 +283,11 @@ static boolean I_FL_InitStream(int device)
static boolean I_FL_OpenStream(void *data, ALsizei size, ALenum *format,
ALsizei *freq, ALsizei *frame_size)
{
if (!IsMid(data, size) && !IsMus(data, size))
{
return false;
}
if (!synth)
{
return false;
@ -409,20 +393,15 @@ static void I_FL_ShutdownStream(void)
#define NAME_MAX_LENGTH 25
static const char **I_FL_DeviceList(int *current_device)
static const char **I_FL_DeviceList(void)
{
static const char **devices = NULL;
if (devices)
if (array_size(devices))
{
return devices;
}
if (current_device)
{
*current_device = 0;
}
if (W_CheckNumForName("SNDFONT") >= 0)
{
array_push(devices, "FluidSynth (SNDFONT)");
@ -438,15 +417,10 @@ static const char **I_FL_DeviceList(int *current_device)
{
name[NAME_MAX_LENGTH] = '\0';
}
array_push(devices, M_StringJoin("FluidSynth (", name, ")", NULL));
if (current_device && !strcasecmp(soundfonts[i], soundfont_path))
{
*current_device = i;
}
free(name);
}
return devices;
}

View File

@ -1,405 +0,0 @@
//
// 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_printf.h"
#include "i_sound.h"
#include "m_array.h"
#include "memio.h"
#include "mus2mid.h"
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
#include <AvailabilityMacros.h>
static MusicPlayer player;
static MusicSequence sequence;
static AudioUnit unit;
static AUGraph graph;
static AUNode synth;
static AUNode output;
static MusicTimeStamp endtime;
static boolean music_initialized;
static boolean is_playing, is_looping;
static boolean I_MAC_InitMusic(int device)
{
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
ComponentDescription d;
#else
AudioComponentDescription d;
#endif
NewAUGraph(&graph);
d.componentType = kAudioUnitType_MusicDevice;
d.componentSubType = kAudioUnitSubType_DLSSynth;
d.componentManufacturer = kAudioUnitManufacturer_Apple;
d.componentFlags = 0;
d.componentFlagsMask = 0;
#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)
{
I_Printf(VB_ERROR, "I_MAC_InitMusic: AUGraphConnectNodeInput failed.");
return false;
}
if (AUGraphOpen(graph) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_InitMusic: AUGraphOpen failed.");
return false;
}
if (AUGraphInitialize(graph) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_InitMusic: AUGraphInitialize failed.");
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
{
I_Printf(VB_ERROR, "I_MAC_InitMusic: AUGraphGetNodeInfo failed.");
return false;
}
if (NewMusicPlayer(&player) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_InitMusic: Music player creation failed "
"using AudioToolbox.");
return false;
}
I_Printf(VB_INFO,
"I_MAC_InitMusic: Music playback enabled using AudioToolbox.");
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)
{
I_Printf(VB_ERROR,
"I_MAC_SetMusicVolume: AudioUnitSetParameter failed.");
}
}
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;
if (!music_initialized)
{
return;
}
if (MusicSequenceSetAUGraph(sequence, graph) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_PlaySong: MusicSequenceSetAUGraph failed.");
return;
}
if (MusicPlayerSetSequence(player, sequence) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_PlaySong: MusicPlayerSetSequence failed.");
return;
}
if (MusicPlayerPreroll(player) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_PlaySong: MusicPlayerPreroll failed.");
return;
}
if (MusicSequenceGetTrackCount(sequence, &ntracks) != noErr)
{
I_Printf(VB_ERROR,
"I_MAC_PlaySong: MusicSequenceGetTrackCount failed.");
return;
}
endtime = 0;
for (i = 0; i < ntracks; i++)
{
MusicTimeStamp time;
MusicTrack track;
UInt32 size = sizeof(time);
if (MusicSequenceGetIndTrack(sequence, i, &track) != noErr)
{
I_Printf(VB_ERROR,
"I_MAC_PlaySong: MusicSequenceGetIndTrack failed.");
return;
}
if (MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength,
&time, &size)
!= noErr)
{
I_Printf(VB_ERROR, "I_MAC_PlaySong: MusicTrackGetProperty failed.");
return;
}
if (time > endtime)
{
endtime = time;
}
}
if (MusicPlayerSetTime(player, 0) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_PlaySong: MusicPlayerSetTime failed.");
return;
}
if (MusicPlayerStart(player) != noErr)
{
I_Printf(VB_ERROR, "I_MAC_PlaySong: MusicPlayerStart failed.");
return;
}
is_playing = true;
is_looping = looping;
}
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);
is_playing = false;
}
static void *I_MAC_RegisterSong(void *data, int len)
{
CFDataRef data_ref = NULL;
if (!music_initialized)
{
return NULL;
}
if (NewMusicSequence(&sequence) != noErr)
{
I_Printf(VB_ERROR,
"I_MAC_RegisterSong: Unable to create AudioUnit sequence.");
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)
{
I_Printf(VB_ERROR, "I_MAC_RegisterSong: Failed to load MID.");
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 const char **I_MAC_DeviceList(int *current_device)
{
const char **devices = NULL;
*current_device = 0;
array_push(devices, "Native");
return devices;
}
static void I_MAC_UpdateMusic(void)
{
MusicTimeStamp time;
if (!music_initialized || !is_playing)
{
return;
}
MusicPlayerGetTime(player, &time);
if (time < endtime)
{
return;
}
else if (is_looping)
{
MusicPlayerSetTime(player, 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_UpdateMusic,
I_MAC_StopSong,
I_MAC_UnRegisterSong,
I_MAC_DeviceList,
};

1502
src/i_midimusic.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
#include "i_oalstream.h"
#include "i_printf.h"
#include "i_sound.h"
#include "m_array.h"
// Define the number of buffers and buffer size (in milliseconds) to use. 4
// buffers with 4096 samples each gives a nice per-chunk size, and lets the
@ -32,6 +33,18 @@
#define NUM_BUFFERS 4
#define BUFFER_SAMPLES 4096
static stream_module_t *all_modules[] =
{
#if defined (HAVE_FLUIDSYNTH)
&stream_fl_module,
#endif
&stream_opl_module,
&stream_snd_module,
#if defined(HAVE_LIBXMP)
&stream_xmp_module,
#endif
};
static stream_module_t *stream_modules[] =
{
&stream_snd_module,
@ -40,6 +53,14 @@ static stream_module_t *stream_modules[] =
#endif
};
static stream_module_t *midi_modules[] =
{
#if defined (HAVE_FLUIDSYNTH)
&stream_fl_module,
#endif
&stream_opl_module,
};
static stream_module_t *active_module;
typedef struct
@ -60,12 +81,10 @@ typedef struct
static stream_player_t player;
static SDL_Thread *player_thread_handle;
static int player_thread_running;
static SDL_atomic_t player_thread_running;
static boolean music_initialized;
static SDL_mutex *music_lock;
static boolean UpdatePlayer(void)
{
ALint processed, state;
@ -177,22 +196,11 @@ static int PlayerThread(void *unused)
StartPlayer();
while (true)
while (SDL_AtomicGet(&player_thread_running))
{
boolean keep_going;
if (!UpdatePlayer())
{
break;
}
SDL_LockMutex(music_lock);
keep_going = player_thread_running;
SDL_UnlockMutex(music_lock);
if (!keep_going)
{
break;
SDL_AtomicSet(&player_thread_running, 0);
}
SDL_Delay(1);
@ -201,15 +209,13 @@ static int PlayerThread(void *unused)
return 0;
}
static boolean I_OAL_InitMusic(int device)
boolean I_OAL_InitStream(void)
{
if (alcGetCurrentContext() == NULL)
if (alcGetCurrentContext() == NULL || music_initialized)
{
return false;
}
active_module = &stream_snd_module;
alGenBuffers(NUM_BUFFERS, player.buffers);
alGenSources(1, &player.source);
@ -243,6 +249,50 @@ static boolean I_OAL_InitMusic(int device)
return true;
}
void I_OAL_ShutdownStream(void)
{
if (!music_initialized)
{
return;
}
for (int i = 0; i < arrlen(stream_modules); ++i)
{
stream_modules[i]->I_ShutdownStream();
}
alDeleteSources(1, &player.source);
alDeleteBuffers(NUM_BUFFERS, player.buffers);
if (alGetError() != AL_NO_ERROR)
{
I_Printf(VB_ERROR, "I_OAL_ShutdownMusic: Failed to delete object IDs.");
}
memset(&player, 0, sizeof(stream_player_t));
music_initialized = false;
}
static boolean I_OAL_InitMusic(int device)
{
int count_devices = 0;
for (int i = 0; i < arrlen(midi_modules); ++i)
{
const char **strings = midi_modules[i]->I_DeviceList();
if (device >= count_devices
&& device < count_devices + array_size(strings))
{
return midi_modules[i]->I_InitStream(device - count_devices);
}
count_devices += array_size(strings);
}
return false;
}
int mus_gain = 100;
int opl_gain = 200;
@ -305,38 +355,26 @@ static void I_OAL_PlaySong(void *handle, boolean looping)
return;
}
music_lock = SDL_CreateMutex();
player_thread_running = true;
SDL_AtomicSet(&player_thread_running, 1);
player_thread_handle = SDL_CreateThread(PlayerThread, NULL, NULL);
if (player_thread_handle == NULL)
{
I_Printf(VB_ERROR, "Error creating thread: %s", SDL_GetError());
player_thread_running = false;
}
}
static void I_OAL_StopSong(void *handle)
{
if (!music_initialized || !player_thread_running)
if (!music_initialized || !SDL_AtomicGet(&player_thread_running))
{
return;
}
alSourceStop(player.source);
SDL_LockMutex(music_lock);
player_thread_running = false;
SDL_UnlockMutex(music_lock);
SDL_AtomicSet(&player_thread_running, 0);
SDL_WaitThread(player_thread_handle, NULL);
if (alGetError() != AL_NO_ERROR)
{
I_Printf(VB_ERROR, "I_OAL_StopSong: Error stopping playback.");
}
SDL_DestroyMutex(music_lock);
}
static void I_OAL_UnRegisterSong(void *handle)
@ -346,7 +384,11 @@ static void I_OAL_UnRegisterSong(void *handle)
return;
}
active_module->I_CloseStream();
if (active_module)
{
active_module->I_CloseStream();
active_module = NULL;
}
if (player.data)
{
@ -365,53 +407,27 @@ static void I_OAL_ShutdownMusic(void)
I_OAL_StopSong(NULL);
I_OAL_UnRegisterSong(NULL);
if (midi_stream_module)
for (int i = 0; i < arrlen(midi_modules); ++i)
{
midi_stream_module->I_ShutdownStream();
midi_modules[i]->I_ShutdownStream();
}
active_module->I_ShutdownStream();
alDeleteSources(1, &player.source);
alDeleteBuffers(NUM_BUFFERS, player.buffers);
if (alGetError() != AL_NO_ERROR)
{
I_Printf(VB_ERROR, "I_OAL_ShutdownMusic: Failed to delete object IDs.");
}
memset(&player, 0, sizeof(stream_player_t));
music_initialized = false;
}
// Prebuffers some audio from the file, and starts playing the source.
static void *I_OAL_RegisterSong(void *data, int len)
{
int i;
if (!music_initialized)
{
return NULL;
}
if (IsMid(data, len) || IsMus(data, len))
for (int i = 0; i < arrlen(all_modules); ++i)
{
if (midi_stream_module
&& midi_stream_module->I_OpenStream(data, len, &player.format,
&player.freq, &player.frame_size))
{
active_module = midi_stream_module;
return (void *)1;
}
return NULL;
}
for (i = 0; i < arrlen(stream_modules); ++i)
{
if (stream_modules[i]->I_OpenStream(data, len, &player.format,
if (all_modules[i]->I_OpenStream(data, len, &player.format,
&player.freq, &player.frame_size))
{
active_module = stream_modules[i];
active_module = all_modules[i];
return (void *)1;
}
}
@ -419,14 +435,26 @@ static void *I_OAL_RegisterSong(void *data, int len)
return NULL;
}
static const char **I_OAL_DeviceList(int *current_device)
static const char **I_OAL_DeviceList(void)
{
return NULL;
}
static const char **devices = NULL;
static void I_OAL_UpdateMusic(void)
{
;
if (array_size(devices))
{
return devices;
}
for (int i = 0; i < arrlen(midi_modules); ++i)
{
const char **strings = midi_modules[i]->I_DeviceList();
for (int k = 0; k < array_size(strings); ++k)
{
array_push(devices, strings[k]);
}
}
return devices;
}
music_module_t music_oal_module =
@ -438,7 +466,6 @@ music_module_t music_oal_module =
I_OAL_ResumeSong,
I_OAL_RegisterSong,
I_OAL_PlaySong,
I_OAL_UpdateMusic,
I_OAL_StopSong,
I_OAL_UnRegisterSong,
I_OAL_DeviceList,

View File

@ -21,7 +21,7 @@
#include "doomtype.h"
typedef struct stream_module_s
typedef struct
{
boolean (*I_InitStream)(int device);
boolean (*I_OpenStream)(void *data, ALsizei size, ALenum *format,
@ -30,7 +30,7 @@ typedef struct stream_module_s
void (*I_PlayStream)(boolean looping);
void (*I_CloseStream)(void);
void (*I_ShutdownStream)(void);
const char **(*I_DeviceList)(int *current_device);
const char **(*I_DeviceList)(void);
} stream_module_t;
extern stream_module_t stream_opl_module;
@ -38,4 +38,8 @@ extern stream_module_t stream_fl_module;
extern stream_module_t stream_snd_module;
extern stream_module_t stream_xmp_module;
boolean I_OAL_InitStream(void);
void I_OAL_ShutdownStream(void);
#endif

View File

@ -1503,6 +1503,11 @@ static midi_file_t *midifile;
static boolean I_OPL_OpenStream(void *data, ALsizei size, ALenum *format,
ALsizei *freq, ALsizei *frame_size)
{
if (!IsMid(data, size) && !IsMus(data, size))
{
return false;
}
if (!music_initialized)
{
return false;
@ -1652,19 +1657,13 @@ static void I_OPL_ShutdownStream(void)
}
}
static const char **I_OPL_DeviceList(int *current_device)
static const char **I_OPL_DeviceList(void)
{
static const char **devices = NULL;
if (devices)
{
return devices;
}
if (current_device)
{
*current_device = 0;
}
array_push(devices, "OPL3 Emulation");
return devices;
}

View File

@ -704,7 +704,7 @@ static void I_SND_ShutdownStream(void)
;
}
static const char **I_SND_DeviceList(int *current_device)
static const char **I_SND_DeviceList(void)
{
return NULL;
}

View File

@ -29,6 +29,7 @@
#include "i_printf.h"
#include "i_system.h"
#include "m_array.h"
#include "mn_setup.h"
#include "p_mobj.h"
#include "sounds.h"
#include "w_wad.h"
@ -46,28 +47,19 @@ static const sound_module_t *sound_modules[] =
static const sound_module_t *sound_module;
static music_module_t *native_midi_module =
#if defined(_WIN32)
&music_win_module;
#elif defined(__APPLE__)
&music_mac_module;
#else
NULL;
#endif
static boolean native_midi;
static stream_module_t *stream_modules[] =
static music_module_t *music_modules[] =
{
#if defined(HAVE_FLUIDSYNTH)
&stream_fl_module,
#if defined(HAVE_ALSA)
&music_oal_module,
&music_mid_module,
#else
&music_mid_module,
&music_oal_module,
#endif
&stream_opl_module,
};
stream_module_t *midi_stream_module = NULL;
static music_module_t *active_module = NULL;
static music_module_t *midi_module = NULL;
// haleyjd: safety variables to keep changes to *_card from making
// these routines think that sound has been initialized when it hasn't
@ -519,39 +511,6 @@ void I_SetSoundModule(int device)
}
}
int midi_player; // current music module
static void MidiPlayerFallback(void)
{
// Fall back the the first module that initializes, device 0.
midi_player = 0;
if (native_midi_module)
{
if (native_midi_module->I_InitMusic(0))
{
native_midi = true;
return;
}
midi_player = 1;
}
native_midi = false;
for (int i = 0; i < arrlen(stream_modules); ++i)
{
if (stream_modules[i]->I_InitStream(0))
{
midi_player += i;
midi_stream_module = stream_modules[i];
return;
}
}
I_Error("MidiPlayerFallback: No music module could be initialized");
}
void I_SetMidiPlayer(int device)
{
if (nomusicparm)
@ -559,49 +518,42 @@ void I_SetMidiPlayer(int device)
return;
}
int num_devices = 0;
midi_player = 0;
if (native_midi_module)
if (midi_module)
{
const char **strings = native_midi_module->I_DeviceList(NULL);
num_devices = array_size(strings);
native_midi_module->I_ShutdownMusic();
if (device < num_devices)
{
if (native_midi_module->I_InitMusic(device))
{
native_midi = true;
return;
}
}
midi_player = 1;
midi_module->I_ShutdownMusic();
}
native_midi = false;
int count_devices = 0;
for (int i = 0, accum = num_devices; i < arrlen(stream_modules); ++i)
for (int i = 0; i < arrlen(music_modules); ++i)
{
const char **strings = stream_modules[i]->I_DeviceList(NULL);
num_devices = array_size(strings);
const char **strings = music_modules[i]->I_DeviceList();
if (device >= accum && device < accum + num_devices)
if (device >= count_devices
&& device < count_devices + array_size(strings))
{
midi_player += i;
if (stream_modules[i]->I_InitStream(device - accum))
if (music_modules[i]->I_InitMusic(device - count_devices))
{
midi_stream_module = stream_modules[i];
midi_module = music_modules[i];
return;
}
}
accum += num_devices;
count_devices += array_size(strings);
}
MidiPlayerFallback();
// Fall back the the first module that initializes, device 0.
for (int i = 0; i < arrlen(music_modules); ++i)
{
if (music_modules[i]->I_InitMusic(0))
{
midi_module = music_modules[i];
return;
}
}
I_Error("I_SetMidiPlayer: No music module could be initialized");
}
boolean I_InitMusic(void)
@ -614,55 +566,31 @@ boolean I_InitMusic(void)
// Always initialize the OpenAL module, it is used for software synth and
// non-MIDI music streaming.
music_oal_module.I_InitMusic(0);
active_module = &music_oal_module;
I_OAL_InitStream();
I_AtExit(I_ShutdownMusic, true);
int module_index = 0;
if (native_midi_module)
{
if (midi_player == 0
&& native_midi_module->I_InitMusic(DEFAULT_MIDI_DEVICE))
{
native_midi = true;
return true;
}
module_index = 1;
}
native_midi = false;
module_index = midi_player - module_index;
if (module_index < arrlen(stream_modules))
{
if (stream_modules[module_index]->I_InitStream(DEFAULT_MIDI_DEVICE))
{
midi_stream_module = stream_modules[module_index];
return true;
}
}
MidiPlayerFallback();
I_SetMidiPlayer(midi_player_menu);
return true;
}
void I_ShutdownMusic(void)
{
music_oal_module.I_ShutdownMusic();
if (native_midi && native_midi_module)
if (active_module != midi_module)
{
native_midi_module->I_ShutdownMusic();
midi_module->I_ShutdownMusic();
}
active_module->I_ShutdownMusic();
I_OAL_ShutdownStream();
}
void I_SetMusicVolume(int volume)
{
active_module->I_SetMusicVolume(volume);
if (active_module)
{
active_module->I_SetMusicVolume(volume);
}
}
void I_PauseSong(void *handle)
@ -687,16 +615,17 @@ boolean IsMus(byte *mem, int len)
void *I_RegisterSong(void *data, int size)
{
active_module = &music_oal_module;
if (native_midi && (IsMid(data, size) || IsMus(data, size)))
for (int i = 0; i < arrlen(music_modules); ++i)
{
active_module = native_midi_module;
void *result = music_modules[i]->I_RegisterSong(data, size);
if (result)
{
active_module = music_modules[i];
active_module->I_SetMusicVolume(snd_MusicVolume);
return result;
}
}
void *result = active_module->I_RegisterSong(data, size);
active_module->I_SetMusicVolume(snd_MusicVolume);
return result;
return NULL;
}
void I_PlaySong(void *handle, boolean looping)
@ -704,11 +633,6 @@ void I_PlaySong(void *handle, boolean looping)
active_module->I_PlaySong(handle, looping);
}
void I_UpdateMusic(void)
{
active_module->I_UpdateMusic();
}
void I_StopSong(void *handle)
{
active_module->I_StopSong(handle);
@ -719,50 +643,20 @@ void I_UnRegisterSong(void *handle)
active_module->I_UnRegisterSong(handle);
}
// Get a list of devices for all music modules. Retrieve the selected device, as
// each module manages and stores its own devices independently.
const char **I_DeviceList(int *current_device)
const char **I_DeviceList(void)
{
const char **devices = NULL;
static const char **devices = NULL;
*current_device = 0;
array_clear(devices);
int module_index = 0;
if (native_midi_module)
for (int i = 0; i < arrlen(music_modules); ++i)
{
int device;
const char **strings = native_midi_module->I_DeviceList(&device);
if (midi_player == module_index)
{
*current_device = device;
}
for (int i = 0; i < array_size(strings); ++i)
{
array_push(devices, strings[i]);
}
module_index = 1;
}
for (int i = 0; i < arrlen(stream_modules); ++i)
{
int device;
const char **strings = stream_modules[i]->I_DeviceList(&device);
if (midi_player == module_index)
{
*current_device = array_size(devices) + device;
}
const char **strings = music_modules[i]->I_DeviceList();
for (int k = 0; k < array_size(strings); ++k)
{
array_push(devices, strings[k]);
}
module_index++;
}
return devices;

View File

@ -167,26 +167,18 @@ typedef struct
void (*I_ResumeSong)(void *handle);
void *(*I_RegisterSong)(void *data, int size);
void (*I_PlaySong)(void *handle, boolean looping);
void (*I_UpdateMusic)(void);
void (*I_StopSong)(void *handle);
void (*I_UnRegisterSong)(void *handle);
const char **(*I_DeviceList)(int *current_device);
const char **(*I_DeviceList)(void);
} music_module_t;
// Music modules
extern music_module_t music_oal_module;
extern music_module_t music_win_module;
extern music_module_t music_mac_module;
extern int midi_player;
extern struct stream_module_s *midi_stream_module;
extern music_module_t music_mid_module;
boolean I_InitMusic(void);
void I_ShutdownMusic(void);
#define DEFAULT_MIDI_DEVICE -1 // use saved music module device
void I_SetMidiPlayer(int device);
// Volume.
@ -205,15 +197,13 @@ void *I_RegisterSong(void *data, int size);
// Horrible thing to do, considering.
void I_PlaySong(void *handle, boolean looping);
void I_UpdateMusic(void);
// Stops a song over 3 seconds.
void I_StopSong(void *handle);
// See above (register), then think backwards
void I_UnRegisterSong(void *handle);
const char **I_DeviceList(int *current_device);
const char **I_DeviceList(void);
// Determine whether memory block is a .mid file
boolean IsMid(byte *mem, int len);

View File

@ -24,6 +24,15 @@
#include "i_system.h"
#include "m_fixed.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
HANDLE hTimer = NULL;
#else
#include <unistd.h>
#endif
static uint64_t basecounter = 0;
static uint64_t basefreq = 0;
@ -136,6 +145,15 @@ void I_InitTimer(void)
I_Error("I_InitTimer: Failed to initialize timer: %s", SDL_GetError());
}
#ifdef _WIN32
// Create an unnamed waitable timer.
hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
if (hTimer == NULL)
{
I_Error("I_InitTimer: CreateWaitableTimer failed");
}
#endif
I_AtExit(I_ShutdownTimer, true);
basefreq = SDL_GetPerformanceFrequency();
@ -184,6 +202,20 @@ void I_Sleep(int ms)
SDL_Delay(ms);
}
void I_SleepUS(uint64_t us)
{
#if defined(_WIN32)
LARGE_INTEGER liDueTime;
liDueTime.QuadPart = -(LONGLONG)(us * 1000 / 100);
if (SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0))
{
WaitForSingleObject(hTimer, INFINITE);
}
#else
usleep(us);
#endif
}
void I_WaitVBL(int count)
{
// haleyjd

View File

@ -44,6 +44,8 @@ void I_EnableWarp(boolean warp);
// Pause for a specified number of ms
void I_Sleep(int ms);
void I_SleepUS(uint64_t us);
// Initialize timer
void I_InitTimer(void);

File diff suppressed because it is too large Load Diff

View File

@ -60,6 +60,11 @@ static void PrintError(int e)
static boolean I_XMP_InitStream(int device)
{
if (context)
{
return true;
}
context = xmp_create_context();
if (!context)
@ -127,6 +132,7 @@ static void I_XMP_CloseStream(void)
xmp_stop_module(context);
xmp_end_player(context);
xmp_release_module(context);
context = NULL;
}
static void I_XMP_ShutdownStream(void)
@ -140,7 +146,7 @@ static void I_XMP_ShutdownStream(void)
context = NULL;
}
static const char **I_XMP_DeviceList(int *current_device)
static const char **I_XMP_DeviceList(void)
{
return NULL;
}

View File

@ -85,18 +85,14 @@ extern int mouse_acceleration;
extern int mouse_acceleration_threshold;
extern int show_endoom;
#if defined(HAVE_FLUIDSYNTH)
extern char *soundfont_path;
extern char *soundfont_dir;
extern boolean mus_chorus;
extern boolean mus_reverb;
extern int mus_gain;
#endif
#if defined(_WIN32)
extern char *winmm_device;
extern int winmm_complevel;
extern int winmm_reset_type;
extern int winmm_reset_delay;
#endif
extern int midi_complevel;
extern int midi_reset_type;
extern int midi_reset_delay;
extern int opl_gain;
extern boolean demobar;
extern boolean smoothlight;
@ -458,13 +454,6 @@ default_t defaults[] = {
"[OpenAL 3D] Doppler effect (0 = Off, 10 = Max)"
},
{
"midi_player",
(config_t *) &midi_player, NULL,
{0}, {0, 2}, number, ss_none, wad_no,
"MIDI Player backend (Native if available, FluidSynth if available, OPL Emulation)"
},
{
"midi_player_menu",
(config_t *) &midi_player_menu, NULL,
@ -476,9 +465,9 @@ default_t defaults[] = {
{
"soundfont_dir",
(config_t *) &soundfont_dir, NULL,
#if defined(_WIN32)
# if defined(_WIN32)
{.s = "soundfonts"},
#else
# else
/* RedHat/Fedora/Arch */
{.s = "/usr/share/soundfonts:"
/* Debian/Ubuntu/OpenSUSE */
@ -486,18 +475,11 @@ default_t defaults[] = {
"/usr/share/sounds/sf3:"
/* AppImage */
"../share/" PROJECT_SHORTNAME "/soundfonts"},
#endif
# endif
{0}, string, ss_none, wad_no,
"FluidSynth soundfont directories"
},
{
"soundfont_path",
(config_t *) &soundfont_path, NULL,
{.s = ""}, {0}, string, ss_none, wad_no,
"FluidSynth current soundfont path"
},
{
"mus_chorus",
(config_t *) &mus_chorus, NULL,
@ -527,35 +509,26 @@ default_t defaults[] = {
"fine tune OPL emulation output level (default 200%)"
},
#if defined(_WIN32)
{
"winmm_device",
(config_t *) &winmm_device, NULL,
{.s = ""}, {0}, string, ss_none, wad_no,
"Native MIDI device"
},
{
"winmm_complevel",
(config_t *) &winmm_complevel, NULL,
"midi_complevel",
(config_t *) &midi_complevel, NULL,
{1}, {0, 2}, number, ss_none, wad_no,
"Native MIDI compatibility level (0 = Vanilla, 1 = Standard, 2 = Full)"
},
{
"winmm_reset_type",
(config_t *) &winmm_reset_type, NULL,
"midi_reset_type",
(config_t *) &midi_reset_type, NULL,
{1}, {0, 3}, number, ss_none, wad_no,
"SysEx reset for native MIDI (0 = None, 1 = GM, 2 = GS, 3 = XG)"
},
{
"winmm_reset_delay",
(config_t *) &winmm_reset_delay, NULL,
"midi_reset_delay",
(config_t *) &midi_reset_delay, NULL,
{0}, {0, 2000}, number, ss_none, wad_no,
"Delay after reset for native MIDI (milliseconds)"
},
#endif
//
// QOL features

View File

@ -18,8 +18,8 @@
//
//-----------------------------------------------------------------------------
#ifndef __M_MISC__
#define __M_MISC__
#ifndef __M_CONFIG__
#define __M_CONFIG__
#include "doomtype.h"
#include "doomdef.h"

View File

@ -16,8 +16,8 @@
// [FG] miscellaneous helper functions from Chocolate Doom.
//
#ifndef __M_MISC2__
#define __M_MISC2__
#ifndef __M_MISC__
#define __M_MISC__
#include <stdarg.h>

676
src/midiout.c Normal file
View File

@ -0,0 +1,676 @@
//
// Copyright(C) 2024 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.
#include "midiout.h"
#include <stdlib.h>
#include "config.h"
#include "doomtype.h"
#include "i_printf.h"
#define MIDI_DEFAULT_BUFFER_SIZE 32
//---------------------------------------------------------
// WINMM
//---------------------------------------------------------
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <mmreg.h>
#include "m_io.h"
static HMIDIOUT hMidiOut;
static HANDLE hCallbackEvent;
static void MidiError(const char *prefix, MMRESULT result)
{
wchar_t werror[MAXERRORLENGTH];
if (midiOutGetErrorTextW(result, (LPWSTR)werror, MAXERRORLENGTH)
== MMSYSERR_NOERROR)
{
char *error = M_ConvertWideToUtf8(werror);
I_Printf(VB_ERROR, "%s: %s", prefix, error);
free(error);
}
else
{
I_Printf(VB_ERROR, "%s: Unknown error", prefix);
}
}
static void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
if (wMsg == MOM_DONE)
{
SetEvent(hCallbackEvent);
}
}
void MIDI_SendShortMsg(const byte *message, unsigned int length)
{
MMRESULT result;
// Pack MIDI bytes into double word.
DWORD packet = 0;
byte *ptr = (byte *)&packet;
for (int i = 0; i < length; ++i)
{
*ptr = message[i];
++ptr;
}
result = midiOutShortMsg(hMidiOut, packet);
if (result != MMSYSERR_NOERROR)
{
MidiError("MIDI_SendShortMsg", result);
}
}
void MIDI_SendLongMsg(const byte *message, unsigned int length)
{
MMRESULT result;
MIDIHDR hdr = {0};
hdr.lpData = (LPSTR)message;
hdr.dwBufferLength = length;
hdr.dwFlags = 0;
result = midiOutPrepareHeader(hMidiOut, &hdr, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
MidiError("MIDI_SendLongMsg", result);
return;
}
result = midiOutLongMsg(hMidiOut, &hdr, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
MidiError("MIDI_SendLongMsg", result);
return;
}
if (WaitForSingleObject(hCallbackEvent, INFINITE) == WAIT_OBJECT_0)
{
result = midiOutUnprepareHeader(hMidiOut, &hdr, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR)
{
MidiError("MIDI_SendLongMsg", result);
}
}
}
int MIDI_CountDevices(void)
{
return midiOutGetNumDevs();
}
const char *MIDI_GetDeviceName(int device)
{
MMRESULT result;
MIDIOUTCAPSW caps;
result = midiOutGetDevCapsW(device, &caps, sizeof(caps));
if (result == MMSYSERR_NOERROR)
{
return M_ConvertWideToUtf8(caps.szPname);
}
else
{
MidiError("MIDI_GetDeviceName", result);
}
return NULL;
}
boolean MIDI_OpenDevice(int device)
{
MMRESULT result = midiOutOpen(&hMidiOut, device, (DWORD_PTR)&MidiOutProc,
(DWORD_PTR)NULL, CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR)
{
MidiError("MIDI_OpenDevice", result);
return false;
}
hCallbackEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
return true;
}
void MIDI_CloseDevice(void)
{
MMRESULT result = midiOutClose(hMidiOut);
if (result != MMSYSERR_NOERROR)
{
MidiError("MIDI_CloseDevice", result);
}
CloseHandle(hCallbackEvent);
}
//---------------------------------------------------------
// ALSA
//---------------------------------------------------------
#elif defined(HAVE_ALSA)
#include <alsa/asoundlib.h>
#include "m_array.h"
#include "m_misc.h"
static snd_seq_t *seq;
static snd_seq_port_subscribe_t *subscription;
static snd_midi_event_t *coder;
static size_t buffer_size;
static int vport = -1;
typedef struct
{
const char *name;
int port_id;
int client_id;
} port_info_t;
static port_info_t *ports;
static boolean Init(void)
{
if (seq)
{
return true;
}
int err;
snd_seq_client_info_t *cinfo;
snd_seq_port_info_t *pinfo;
err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
if (err < 0)
{
I_Printf(VB_ERROR, "Init: %s", snd_strerror(err));
seq = NULL;
return false;
}
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);
snd_seq_client_info_set_client(cinfo, -1);
while (snd_seq_query_next_client(seq, cinfo) == 0)
{
snd_seq_port_info_set_client(pinfo,
snd_seq_client_info_get_client(cinfo));
snd_seq_port_info_set_port(pinfo, -1);
while (snd_seq_query_next_port(seq, pinfo) == 0)
{
if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
{
continue; // ignore Timer and Announce ports on client 0
}
unsigned int caps = snd_seq_port_info_get_capability(pinfo);
if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE)
{
port_info_t port;
port.name =
M_StringDuplicate(snd_seq_port_info_get_name(pinfo));
port.port_id = snd_seq_port_info_get_port(pinfo);
port.client_id = snd_seq_port_info_get_client(pinfo);
array_push(ports, port);
}
}
}
buffer_size = MIDI_DEFAULT_BUFFER_SIZE;
snd_midi_event_new(buffer_size, &coder);
snd_midi_event_init(coder);
return true;
}
static void Cleanup(void)
{
if (!seq)
{
return;
}
if (subscription)
{
snd_seq_unsubscribe_port(seq, subscription);
snd_seq_port_subscribe_free(subscription);
subscription = NULL;
}
if (vport >= 0)
{
snd_seq_delete_port(seq, vport);
vport = -1;
}
if (coder)
{
snd_midi_event_free(coder);
coder = NULL;
}
snd_seq_close(seq);
seq = NULL;
}
static void SendMessage(const byte *message, unsigned int length)
{
if (length > buffer_size)
{
buffer_size = length;
snd_midi_event_resize_buffer(coder, buffer_size);
}
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_source(&ev, vport);
snd_seq_ev_set_subs(&ev);
snd_seq_ev_set_direct(&ev);
for (int i = 0; i < length; ++i)
{
if (snd_midi_event_encode_byte(coder, message[i], &ev) == 1)
{
// Send the event
int err = snd_seq_event_output(seq, &ev);
if (err < 0)
{
I_Printf(VB_ERROR, "SendMessage: %s", snd_strerror(err));
return;
}
break;
}
}
snd_seq_drain_output(seq);
}
void MIDI_SendShortMsg(const byte *message, unsigned int length)
{
SendMessage(message, length);
}
void MIDI_SendLongMsg(const byte *message, unsigned int length)
{
SendMessage(message, length);
}
int MIDI_CountDevices(void)
{
Init();
return array_size(ports);
}
const char *MIDI_GetDeviceName(int device)
{
if (device >= array_size(ports))
{
return NULL;
}
return ports[device].name;
}
boolean MIDI_OpenDevice(int device)
{
if (!Init() || device >= array_size(ports))
{
return false;
}
int err;
snd_seq_addr_t sender, receiver;
receiver.client = ports[device].client_id;
receiver.port = ports[device].port_id;
sender.client = snd_seq_client_id(seq);
vport = snd_seq_create_simple_port(
seq, NULL,
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
if (vport < 0)
{
I_Printf(VB_ERROR, "MIDI_OpenDevice: %s", snd_strerror(vport));
Cleanup();
return false;
}
sender.port = vport;
// Make subscription
snd_seq_port_subscribe_malloc(&subscription);
snd_seq_port_subscribe_set_sender(subscription, &sender);
snd_seq_port_subscribe_set_dest(subscription, &receiver);
snd_seq_port_subscribe_set_time_update(subscription, 1);
snd_seq_port_subscribe_set_time_real(subscription, 1);
err = snd_seq_subscribe_port(seq, subscription);
if (err < 0)
{
I_Printf(VB_ERROR, "MIDI_OpenDevice: %s", snd_strerror(err));
Cleanup();
return false;
}
return true;
}
void MIDI_CloseDevice(void)
{
Cleanup();
}
//---------------------------------------------------------
// CoreMIDI and DLS Synth
//---------------------------------------------------------
#elif defined(__APPLE__)
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/HostTime.h>
#include <CoreMIDI/MIDIServices.h>
#include <CoreServices/CoreServices.h>
#include <AvailabilityMacros.h>
#include "m_array.h"
#include "m_misc.h"
static AUGraph graph;
static AudioUnit unit;
static MIDIPortRef port;
static MIDIClientRef client;
static MIDIEndpointRef endpoint;
// Maximum buffer size that CoreMIDI can handle for MIDIPacketList
#define PACKET_BUFFER_SIZE 65536
static Byte *packet_buffer = NULL;
typedef struct
{
char *name;
int id;
} device_t;
static device_t *devices = NULL;
static int current_device;
static void Init(void)
{
if (array_size(devices))
{
return;
}
device_t device;
device.name = "DLS Synth";
device.id = -1;
array_push(devices, device);
int num_dest = MIDIGetNumberOfDestinations();
for (int i = 0; i < num_dest; ++i)
{
MIDIEndpointRef dest = MIDIGetDestination(i);
if (!dest)
{
continue;
}
CFStringRef name;
if (MIDIObjectGetStringProperty(dest, kMIDIPropertyName, &name)
== noErr)
{
CFIndex length;
CFRange range = {0, CFStringGetLength(name)};
CFStringGetBytes(name, range, kCFStringEncodingASCII, '?', false,
NULL, INT_MAX, &length);
char *buffer = malloc(length + 1);
CFStringGetBytes(name, range, kCFStringEncodingASCII, '?', false,
(UInt8 *)buffer, length, NULL);
buffer[length] = '\0';
device.name = buffer;
device.id = i;
array_push(devices, device);
}
}
}
static void Cleanup(void)
{
if (graph)
{
AUGraphStop(graph);
DisposeAUGraph(graph);
graph = NULL;
}
if (port)
{
MIDIPortDispose(port);
port = 0;
}
if (client)
{
MIDIClientDispose(client);
client = 0;
}
if (packet_buffer)
{
free(packet_buffer);
packet_buffer = NULL;
}
}
static void SendMessage(const byte *message, unsigned int length)
{
MIDITimeStamp time_stamp = AudioGetCurrentHostTime();
MIDIPacketList *packet_list = (MIDIPacketList *)packet_buffer;
MIDIPacket *packet = MIDIPacketListInit(packet_list);
// MIDIPacketList and MIDIPacket consume extra buffer areas for meta
// information, and available size is smaller than buffer size. Here, we
// simply assume that at least half size is available for data payload.
ByteCount send_size = MIN(length, PACKET_BUFFER_SIZE / 2);
// Add message to the MIDIPacketList
MIDIPacketListAdd(packet_list, PACKET_BUFFER_SIZE, packet, time_stamp,
send_size, message);
if (MIDISend(port, endpoint, packet_list) != noErr)
{
I_Printf(VB_ERROR, "SendMessage: MIDISend failed");
}
}
void MIDI_SendShortMsg(const byte *message, unsigned int length)
{
if (current_device > 0)
{
SendMessage(message, length);
return;
}
UInt32 data[3] = {0};
for (int i = 0; i < length; ++i)
{
data[i] = message[i];
}
if (MusicDeviceMIDIEvent(unit, data[0], data[1], data[2], 0)
!= noErr)
{
I_Printf(VB_ERROR, "MIDI_SendShortMsg: MusicDeviceMIDIEvent failed");
}
}
void MIDI_SendLongMsg(const byte *message, unsigned int length)
{
if (current_device > 0)
{
SendMessage(message, length);
return;
}
if (MusicDeviceSysEx(unit, message, length) != noErr)
{
I_Printf(VB_ERROR, "MIDI_SendLongMsg: MusicDeviceSysEx failed");
}
}
int MIDI_CountDevices(void)
{
Init();
return array_size(devices);
}
const char *MIDI_GetDeviceName(int device)
{
if (device >= array_size(devices))
{
return NULL;
}
return devices[device].name;
}
#define CHECK_ERR(stmt) \
do \
{ \
if ((stmt) != noErr) \
{ \
I_Printf(VB_ERROR, "%s: " #stmt " failed", __func__); \
Cleanup(); \
return false; \
} \
} while (0)
static boolean OpenDLSSynth(void)
{
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
ComponentDescription d;
#else
AudioComponentDescription d;
#endif
AUNode synth, output;
CHECK_ERR(NewAUGraph(&graph));
// The default output device
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
CHECK_ERR(AUGraphNewNode(graph, &d, 0, NULL, &output));
#else
CHECK_ERR(AUGraphAddNode(graph, &d, &output));
#endif
// The built-in default (softsynth) music device
d.componentType = kAudioUnitType_MusicDevice;
d.componentSubType = kAudioUnitSubType_DLSSynth;
d.componentManufacturer = kAudioUnitManufacturer_Apple;
d.componentFlags = 0;
d.componentFlagsMask = 0;
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
CHECK_ERR(AUGraphNewNode(graph, &d, 0, NULL, &synth));
#else
CHECK_ERR(AUGraphAddNode(graph, &d, &synth));
#endif
CHECK_ERR(AUGraphConnectNodeInput(graph, synth, 0, output, 0));
CHECK_ERR(AUGraphOpen(graph));
CHECK_ERR(AUGraphInitialize(graph));
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
CHECK_ERR(AUGraphGetNodeInfo(graph, synth, NULL, NULL, NULL, &unit));
#else
CHECK_ERR(AUGraphNodeInfo(graph, synth, NULL, &unit));
#endif
CHECK_ERR(AUGraphStart(graph));
return true;
}
boolean MIDI_OpenDevice(int device)
{
Init();
if (device >= array_size(devices))
{
return false;
}
current_device = device;
if (current_device == 0)
{
return OpenDLSSynth();
}
endpoint = MIDIGetDestination(devices[current_device].id);
// Create a MIDI client and port
CHECK_ERR(MIDIClientCreate(CFSTR(PROJECT_NAME), NULL, NULL, &client));
CHECK_ERR(MIDIOutputPortCreate(client, CFSTR(PROJECT_NAME"Port"), &port));
packet_buffer = malloc(PACKET_BUFFER_SIZE);
return true;
}
void MIDI_CloseDevice(void)
{
Cleanup();
}
//---------------------------------------------------------
// DUMMY
//---------------------------------------------------------
#else
void MIDI_SendShortMsg(const byte *message, unsigned int length)
{
;
}
void MIDI_SendLongMsg(const byte *message, unsigned int length)
{
;
}
int MIDI_CountDevices(void)
{
return 0;
}
const char *MIDI_GetDeviceName(int device)
{
return NULL;
}
boolean MIDI_OpenDevice(int device)
{
return false;
}
void MIDI_CloseDevice(void)
{
;
}
#endif

31
src/midiout.h Normal file
View File

@ -0,0 +1,31 @@
//
// Copyright(C) 2024 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.
#ifndef MIDIOUT_H
#define MIDIOUT_H
#include "doomtype.h"
void MIDI_SendShortMsg(const byte *message, unsigned int length);
void MIDI_SendLongMsg(const byte *message, unsigned int length);
int MIDI_CountDevices(void);
const char *MIDI_GetDeviceName(int device);
boolean MIDI_OpenDevice(int device);
void MIDI_CloseDevice(void);
#endif

View File

@ -1373,7 +1373,7 @@ static const char *screensize_strings[] = {
static const char *hudtype_strings[] = {"Crispy", "Boom No Bars", "Boom"};
static const char **M_GetHUDModeStrings(void)
static const char **GetHUDModeStrings(void)
{
static const char *crispy_strings[] = {"Off", "Original", "Widescreen"};
static const char *boom_strings[] = {"Minimal", "Compact", "Distributed"};
@ -2059,7 +2059,12 @@ int midi_player_menu;
static const char **GetMidiDevicesStrings(void)
{
return I_DeviceList(&midi_player_menu);
const char **devices = I_DeviceList();
if (midi_player_menu >= array_size(devices))
{
midi_player_menu = 0;
}
return devices;
}
static void SetMidiPlayer(void)
@ -3791,7 +3796,7 @@ static const char **GetStrings(int id)
static void UpdateHUDModeStrings(void)
{
selectstrings[str_hudmode] = M_GetHUDModeStrings();
selectstrings[str_hudmode] = GetHUDModeStrings();
}
void MN_InitMenuStrings(void)

View File

@ -486,16 +486,6 @@ void S_UpdateSounds(const mobj_t *listener)
I_ProcessSoundUpdates();
}
void S_UpdateMusic(void)
{
if (nomusicparm)
{
return;
}
I_UpdateMusic();
}
void S_SetMusicVolume(int volume)
{
// jff 1/22/98 return if music is not enabled

View File

@ -68,8 +68,6 @@ void S_ResumeSound(void);
void S_RestartMusic(void);
void S_UpdateMusic(void);
//
// Updates music & sounds
//