diff --git a/.gitignore b/.gitignore index 90ce5685..8f1ba8e4 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,8 @@ tyrian.sav # Doxygen output /doc/doxygen/ + +tyrian2000 +.vscode +vcpkg_installed/ +.DS_Store diff --git a/Makefile b/Makefile index a6ad3ce9..97379c29 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,48 @@ ifneq ($(filter Msys Cygwin, $(shell uname -o)), ) PLATFORM := WIN32 + OS := win TYRIAN_DIR = C:\\TYRIAN +else ifeq (Darwin, $(shell uname)) # macOS + PLATFORM := UNIX + OS := osx + TYRIAN_DIR = $(gamesdir)/opentyrian2000 else PLATFORM := UNIX + OS := linux TYRIAN_DIR = $(gamesdir)/opentyrian2000 endif +# detect the architecture +HOST_TRIPLET := $(shell $(CC) -dumpmachine) +_ARCH := $(word 1, $(subst -, ,$(HOST_TRIPLET))) + +ifeq ($(_ARCH), aarch64) + ARCH := arm64 +else + ARCH := $(_ARCH) +endif + +# check if VCPKG_TRIPLET, VCPKG_TARGET_TRIPLET, or VCPKG_DEFAULT_TRIPLET is set +# if not, set it to the OS and ARCH +ifneq ($(VCPKG_TRIPLET), ) + $(info VCPKG_TRIPLET: $(VCPKG_TRIPLET)) +else ifneq ($(VCPKG_TARGET_TRIPLET), ) + VCPKG_TRIPLET := $(VCPKG_TARGET_TRIPLET) +else ifneq ($(VCPKG_DEFAULT_TRIPLET), ) + VCPKG_TRIPLET := $(VCPKG_DEFAULT_TRIPLET) +else + _VCPKG_TRIPLET := $(ARCH)-$(OS) + # if using windows, use the static-md triplet by default + ifeq ($(OS), win) + VCPKG_TRIPLET := $(_VCPKG_TRIPLET)-static-md + else + VCPKG_TRIPLET := $(_VCPKG_TRIPLET) + endif +endif + WITH_NETWORK := true +WITH_MIDI := true ################################################################################ @@ -56,6 +91,14 @@ ifeq ($(WITH_NETWORK), true) EXTRA_CPPFLAGS += -DWITH_NETWORK endif +ifeq ($(WITH_MIDI), true) + EXTRA_CPPFLAGS += -DWITH_MIDI +endif + +ifeq ($(OS), linux) + EXTRA_CPPFLAGS += -DNO_NATIVE_MIDI +endif + OPENTYRIAN_VERSION := $(shell $(VCS_IDREV) 2>/dev/null && \ touch src/opentyrian_version.h) ifneq ($(OPENTYRIAN_VERSION), ) @@ -67,20 +110,57 @@ CPPFLAGS += -DNDEBUG CFLAGS ?= -pedantic \ -Wall \ -Wextra \ - -Wno-format-truncation \ + -Wno-strict-prototypes \ -Wno-missing-field-initializers \ - -O2 + -O3 + LDFLAGS ?= LDLIBS ?= -ifeq ($(WITH_NETWORK), true) - SDL_CPPFLAGS := $(shell $(PKG_CONFIG) sdl2 SDL2_net --cflags) - SDL_LDFLAGS := $(shell $(PKG_CONFIG) sdl2 SDL2_net --libs-only-L --libs-only-other) - SDL_LDLIBS := $(shell $(PKG_CONFIG) sdl2 SDL2_net --libs-only-l) +libmidiconv_PC_PATH := + +# check if the "vcpkg_installed" directory exists in the directory that this makefile is in +# if it does, add it to the pkg-config search path= +VCPKG_CHLDDIR := $(VCPKG_TRIPLET)/lib/pkgconfig +ifeq ($(findstring debug, $(MAKECMDGOALS)), debug) + VCPKG_CHLDDIR := $(VCPKG_TRIPLET)/debug/lib/pkgconfig +endif + +# local vcpkg +ifneq ($(wildcard vcpkg_installed), ) + VCPKG_PC_PATH := $(CURDIR)/vcpkg_installed/$(VCPKG_CHLDDIR) +# global vcpkg +else ifneq ($(VCPKG_ROOT), ) + VCPKG_PC_PATH := $(VCPKG_ROOT)/installed/$(VCPKG_CHLDDIR) +endif + +ifeq ($(PKG_CONFIG_PATH), ) + PKG_CONFIG_PATH := $(VCPKG_PC_PATH):$(libmidiconv_PC_PATH):$(shell $(PKG_CONFIG) --variable pc_path pkg-config) else - SDL_CPPFLAGS := $(shell $(PKG_CONFIG) sdl2 --cflags) - SDL_LDFLAGS := $(shell $(PKG_CONFIG) sdl2 --libs-only-L --libs-only-other) - SDL_LDLIBS := $(shell $(PKG_CONFIG) sdl2 --libs-only-l) + PKG_CONFIG_PATH := $(VCPKG_PC_PATH):$(libmidiconv_PC_PATH):$(PKG_CONFIG_PATH) +endif + + +SDL_PACKAGES := sdl2 +MIDIPROC_LIBS := +PC_CMD := PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(PKG_CONFIG) + +ifeq ($(WITH_NETWORK), true) + SDL_PACKAGES += SDL2_net +endif + +ifeq ($(WITH_MIDI), true) + SDL_PACKAGES += SDL2_mixer_ext + MIDIPROC_LIBS := midiproc fluidsynth +endif + +SDL_CPPFLAGS := $(shell $(PC_CMD) $(SDL_PACKAGES) $(MIDIPROC_LIBS) --cflags) +SDL_LDFLAGS := $(shell $(PC_CMD) $(SDL_PACKAGES) $(MIDIPROC_LIBS) --libs-only-L --libs-only-other) +SDL_LDLIBS := $(shell $(PC_CMD) $(SDL_PACKAGES) $(MIDIPROC_LIBS) --libs-only-l) + +# add stdc++ to the ldlibs if using midiproc +ifneq ($(MIDIPROC_LIBS), ) + SDL_LDLIBS += -lstdc++ endif ALL_CPPFLAGS = -DTARGET_$(PLATFORM) \ diff --git a/ports/midiproc/portfile.cmake b/ports/midiproc/portfile.cmake new file mode 100644 index 00000000..dcd1130e --- /dev/null +++ b/ports/midiproc/portfile.cmake @@ -0,0 +1,25 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO nikitalita/midiproc + REF "${VERSION}" + SHA512 aa885281d92197f68cc12d1b61d2b37f6860f6d54b8e3ed9a6eb6b1d805ccdb0d497f790be76e0482c348961e20550d8cc1979cc574f3a3863dfa7228265c333 + HEAD_REF master +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup( + PACKAGE_NAME midiproc + CONFIG_PATH lib/cmake/midiproc) + +vcpkg_fixup_pkgconfig() + +vcpkg_copy_pdbs() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/debug/include" +) diff --git a/ports/midiproc/vcpkg.json b/ports/midiproc/vcpkg.json new file mode 100644 index 00000000..1f267b3a --- /dev/null +++ b/ports/midiproc/vcpkg.json @@ -0,0 +1,32 @@ +{ + "name": "midiproc", + "version": "0.1.0", + "description": "MIDI processing library", + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "features": { + "test": { + "description": "Enable tests", + "dependencies": [ + { + "name": "sdl2", + "host": true + }, + { + "name": "sdl2-mixer-ext", + "features": ["fluidsynth"] + } + ] + } + }, + "default-features": [] +} diff --git a/src/config.c b/src/config.c index 9e7b00a4..982f66b6 100644 --- a/src/config.c +++ b/src/config.c @@ -279,6 +279,29 @@ bool load_opentyrian_config(void) } } } + section = config_find_section(config, "music", NULL); + if (section != NULL) + { + const char *music_device_name; + const char *soundfont_name; + + if (config_get_string_option(section, "music_device", &music_device_name)) + { + for (size_t i = 0; i < COUNTOF(music_device_names); ++i) + { + if (strcmp(music_device_name, music_device_names[i]) == 0) + { + music_device = i; + break; + } + } + } + + if (config_get_string_option(section, "soundfont", &soundfont_name)) + strncpy(soundfont, soundfont_name, MAX(strlen(soundfont_name) + 1, 4096)); + + + } fclose(file); @@ -327,6 +350,13 @@ bool save_opentyrian_config(void) for (size_t i = 0; i < COUNTOF(mouseSettings); ++i) config_set_string_option(section, mouseSettingNames[i], mouseSettingValues[mouseSettings[i] - 1]); + section = config_find_or_add_section(config, "music", NULL); + if (section == NULL) + exit(EXIT_FAILURE); // out of memory + + config_set_string_option(section, "music_device", music_device_names[music_device]); + config_set_string_option(section, "soundfont", soundfont); + FILE *file = dir_fopen(get_user_directory(), "opentyrian.cfg", "w"); if (file == NULL) return false; diff --git a/src/helptext.c b/src/helptext.c index 8e55a2cf..791bba0e 100644 --- a/src/helptext.c +++ b/src/helptext.c @@ -109,7 +109,7 @@ void read_encrypted_pascal_string(char *s, size_t size, FILE *f) decrypt_string(buffer, len); - assert(len < size); + //assert(len < size); len = MIN(len, size - 1); memcpy(s, buffer, len); diff --git a/src/loudness.c b/src/loudness.c index 4831f5d9..b51f1a91 100644 --- a/src/loudness.c +++ b/src/loudness.c @@ -24,6 +24,11 @@ #include "opentyr.h" #include "params.h" +#include +#ifdef WITH_MIDI +#include +#include +#endif #include #include #include @@ -36,7 +41,33 @@ bool music_stopped = true; unsigned int song_playing = 0; bool audio_disabled = false, music_disabled = false, samples_disabled = false; - +bool fading_out = false; +bool unwated_loop = false; + +MusicDevice music_device = OPL; +char soundfont[4096] = {0}; +const char *const music_device_names[MUSIC_DEVICE_MAX] = { + "OPL3", + "FluidSynth", + "Native MIDI" +}; + +const uint8_t IS_MIDI_DEVICE = FLUIDSYNTH | NATIVE_MIDI; + +#ifdef WITH_MIDI +static Mix_CommonMixer_t music_mixer = NULL; +typedef struct _MidiData { + Uint8 *data; + Uint32 size; + Uint32 duration; + Uint32 loop_start; + Uint32 loop_end; + Uint32 track_count; + Uint32 subsong_count; +} MidiData; +static MidiData * midi_data; +static Mix_Music ** midi_tracks = NULL; +#endif static SDL_AudioDeviceID audioDevice = 0; static Uint8 musicVolume = 255; @@ -66,6 +97,8 @@ static FILE *music_file = NULL; static Uint32 *song_offset; static Uint16 song_count = 0; +#define NO_SONG_PLAYING 0xFFFFFFFF +static double time_playing = 0; #define CHANNEL_COUNT 8 static const Sint16 *channelSamples[CHANNEL_COUNT]; static size_t channelSampleCount[CHANNEL_COUNT] = { 0 }; @@ -76,8 +109,161 @@ static void audioCallback(void *userdata, Uint8 *stream, int size); static void load_song(unsigned int song_num); +#ifdef WITH_MIDI +bool init_midi(SDL_AudioSpec * got){ + if (!Mix_Init(MIX_INIT_MID)){ + fprintf(stderr, "error: SDL2_mixer_ext failed to init: %s\n", Mix_GetError()); + return false; + } else if (Mix_InitMixer(got, SDL_FALSE) != 0) { + fprintf(stderr, "error: SDL2_mixer_ext failed to open audio device: %s\n", Mix_GetError()); + return false; + } else if (strlen(soundfont) != 0 && Mix_SetSoundFonts(soundfont) == 0) { + Mix_FreeMixer(); + fprintf(stderr, "error: SDL2_mixer_ext failed to set soundfont: %s\n", Mix_GetError()); + return false; + } else { + music_mixer = Mix_GetGeneralMixer(); + if (music_mixer == NULL){ + Mix_FreeMixer(); + fprintf(stderr, "error: SDL2_mixer_ext: failed to get music_mixer: %s\n", Mix_GetError()); + return false; + } + } + return true; +} + + +// only use this within a lock +void _stop_midi(void){ + if (playing) + { + Mix_HaltMusic(); + } + playing = false; + songlooped = false; +} + +void deinit_midi(void){ + _stop_midi(); + if (midi_tracks != NULL) { + for (unsigned int i = 0; i < song_count; ++i) + { + if (midi_tracks[i] != NULL) + { + Mix_FreeMusic(midi_tracks[i]); + midi_tracks[i] = NULL; + } + } + } + if (music_mixer){ + Mix_FreeMixer(); + music_mixer = NULL; + } +} + +void convert_midi_data(void){ + // initialize the midi_data array + midi_data = malloc(song_count * sizeof(*midi_data)); + midi_tracks = malloc(song_count * sizeof(*midi_tracks)); + memset(midi_tracks, 0, song_count * sizeof(*midi_tracks)); + for (unsigned int i = 0; i < song_count; ++i) + { + memset(&midi_data[i], 0, sizeof(MidiData)); + Uint32 start = song_offset[i]; + Uint32 end = song_offset[i + 1]; + Uint32 size = end - start; + Uint8 *buf = malloc(size); + fread_die(buf, size, 1, music_file); + HMIDIContainer midi_container = MIDPROC_Container_Create(); + if (!MIDPROC_Process(buf, size, "lds", midi_container)) + { + fprintf(stderr, "warning: failed to process song %d\n", i + 1); + MIDPROC_Container_Delete(midi_container); + free(buf); + continue; + } + size_t midi_data_size = 0; + MIDPROC_Container_SerializeAsSMF(midi_container, &(midi_data[i].data), &midi_data_size); + midi_data[i].size = (Uint32) midi_data_size; + if (midi_data[i].size == 0) + { + fprintf(stderr, "warning: failed to process song %d\n", i + 1); + continue; + } + + midi_data[i].duration = MIDPROC_Container_GetDuration(midi_container, 0, false); + midi_data[i].duration_ms = MIDPROC_Container_GetDuration(midi_container, 0, true); + MIDPROC_Container_DetectLoops(midi_container, false, true, false, false); + midi_data[i].loop_start = MIDPROC_Container_GetLoopBeginTimestamp(midi_container, 0, false); + midi_data[i].loop_end = MIDPROC_Container_GetLoopEndTimestamp(midi_container, 0, false); + midi_data[i].track_count = MIDPROC_Container_GetTrackCount(midi_container); + midi_data[i].subsong_count = MIDPROC_Container_GetSubSongCount(midi_container); + MIDPROC_Container_Delete(midi_container); + + free(buf); + } +} + + +// only use this within a lock +bool _play_midi(Uint32 songnum){ + if (fading_out) + { + _stop_midi(); + } + assert((midi_tracks[songnum] != NULL)); + Sint32 loops = -1; // loop forever + // Not setting loops to 0 for no loop because it will either loop anyway + // or continue playing for like 4+ seconds after the end; we stop it manually below + Mix_RewindMusicStream(midi_tracks[songnum]); + if (Mix_PlayMusic(midi_tracks[songnum], loops) != 0) + { + fprintf(stderr, "error: failed to play music: %s\n", Mix_GetError()); + return false; + } + song_playing = songnum; + playing = true; + songlooped = false; + return true; +} + +const char * get_midi_params(void){ + if (music_device == FLUIDSYNTH){ + return "s4;p512;"; + } else if (music_device == NATIVE_MIDI){ + return "s1;"; + } else{ + return ""; + } +} + +bool load_midi(unsigned int song_num){ + // This is outside of the audio lock because it can take a while + if (midi_tracks[song_num] == NULL){ + const char * params = get_midi_params(); + midi_tracks[song_num] = Mix_LoadMUSType_RW_ARG(SDL_RWFromConstMem(midi_data[song_num].data, midi_data[song_num].size), MUS_MID, 1, params); + if (midi_tracks[song_num] == NULL) + { + fprintf(stderr, "error: failed to load music: %s\n", Mix_GetError()); + return false; + } + } + return true; +} +#endif + bool init_audio(void) { +#ifndef WITH_MIDI + // Force OPL if compiled without MIDI support + music_device = OPL; +#else + #ifdef NO_NATIVE_MIDI + if (music_device == NATIVE_MIDI){ + music_device = FLUIDSYNTH; + } + #endif +#endif if (audio_disabled) return false; @@ -108,6 +294,14 @@ bool init_audio(void) audio_disabled = true; return false; } +#ifdef WITH_MIDI + if (music_device & IS_MIDI_DEVICE){ + if (!init_midi(&got)) { + fprintf(stderr, "error: failed to initialize midi, falling back to OPL...\n"); + music_device = OPL; + } + } +#endif audioSampleRate = got.freq; @@ -125,14 +319,84 @@ bool init_audio(void) return true; } +bool restart_audio(void){ + if (audio_disabled) + return false; + SDL_LockAudioDevice(audioDevice); + unsigned int prev_song = song_playing; + SDL_UnlockAudioDevice(audioDevice); + deinit_audio(); + if (!init_audio()){ + return false; + } + if (prev_song != NO_SONG_PLAYING){ + play_song(prev_song); + } + return true; +} + static void audioCallback(void *userdata, Uint8 *stream, int size) { (void)userdata; Sint16 *const samples = (Sint16 *)stream; const int samplesCount = size / sizeof (Sint16); - - if (!music_disabled && !music_stopped) +#ifdef WITH_MIDI + if ((music_device & IS_MIDI_DEVICE) && !music_disabled && !music_stopped){ + if (Mix_PlayingMusic() == 0){ + fading_out = false; + time_playing = 0; + playing = false; + songlooped = false; + } else { + if (playing){ + // get samples from the mixer + double factor = 1000.0; + music_mixer(NULL, stream, size); + double cur_position = midi_tracks[song_playing] ? Mix_GetMusicPosition(midi_tracks[song_playing]) : 0; + cur_position *= factor; + // check the duration of the song and see if it looped + bool has_loop = midi_data[song_playing].loop_end <= midi_data[song_playing].duration; + #ifdef _DEBUG + fprintf(stderr, "cur_position: %f, time_playing: %f, duration: %d, loop_end: %d\n", cur_position, time_playing, midi_data[song_playing].duration, midi_data[song_playing].loop_end); + #endif + if (unwated_loop && !has_loop) { + // this is to get around a bug in fluidsynth where it plays songs twice even if no loops are set + _stop_midi(); + for (int i = 0; i < samplesCount; ++i) + samples[i] = 0; + time_playing = 0; + unwated_loop = false; + } else if (!has_loop && + (cur_position < time_playing || + cur_position >= midi_data[song_playing].duration + 100)) { + unwated_loop = true; // stop it the next time + } else { // has loop and did loop + // The reason for this is that fluidsynth doesn't recognize any form of SMF loops, + // and consequentially does not loop where the original songs looped; + // e.g. they start at the very beginning rather than a few positions up like most of the songs. + // So, we have to do it manually. + // We have to call music_mixer above first to get SDL_mixer to drive the synth and update the position, + // then clear the samples and call it again. + if (has_loop && + (cur_position < time_playing || cur_position >= midi_data[song_playing].loop_end)) { + double loop_start = ((double)midi_data[song_playing].loop_start) / factor; + Mix_SetMusicPosition(loop_start); + for (int i = 0; i < samplesCount; ++i) + samples[i] = 0; + music_mixer(NULL, stream, size); + songlooped = true; + } + } + time_playing = cur_position; + } + if (!playing) { + } + } + } + else +#endif + if (music_device == OPL && !music_disabled && !music_stopped) { Sint16 *remaining = samples; int remainingCount = samplesCount; @@ -232,17 +496,25 @@ void deinit_audio(void) if (audioDevice != 0) { SDL_PauseAudioDevice(audioDevice, 1); // pause +#ifdef WITH_MIDI + deinit_midi(); +#endif SDL_CloseAudioDevice(audioDevice); audioDevice = 0; } SDL_QuitSubSystem(SDL_INIT_AUDIO); + song_playing = NO_SONG_PLAYING; + playing = false; + songlooped = false; + music_stopped = true; - memset(channelSampleCount, 0, sizeof channelSampleCount); + memset(channelSampleCount, 0, sizeof(channelSampleCount)); lds_free(); } + void load_music(void) // FKA NortSong.loadSong { if (music_file == NULL) @@ -256,6 +528,9 @@ void load_music(void) // FKA NortSong.loadSong fread_u32_die(song_offset, song_count, music_file); song_offset[song_count] = ftell_eof(music_file); +#ifdef WITH_MIDI + convert_midi_data(); +#endif } } @@ -272,22 +547,50 @@ static void load_song(unsigned int song_num) // FKA NortSong.loadSong } } + + void play_song(unsigned int song_num) // FKA NortSong.playSong { + if (song_num >= song_count) + { + fprintf(stderr, "warning: song %d does not exist\n", song_num + 1); + return; + } if (audio_disabled) return; +#ifdef WITH_MIDI + if (song_num != song_playing || !Mix_PlayingMusic()) +#else if (song_num != song_playing) +#endif { +#ifdef WITH_MIDI + // This is outside of the audio lock because it can take a while and it doesn't require it + if (music_device & IS_MIDI_DEVICE && !load_midi(song_num)){ + return; + } +#endif SDL_LockAudioDevice(audioDevice); music_stopped = true; +#ifdef WITH_MIDI + if (music_device & IS_MIDI_DEVICE){ + _stop_midi(); + _play_midi(song_num); + } +#endif + fading_out = false; + time_playing = 0; + song_playing = song_num; SDL_UnlockAudioDevice(audioDevice); - load_song(song_num); + if (music_device == OPL) + { + load_song(song_num); + } - song_playing = song_num; } SDL_LockAudioDevice(audioDevice); @@ -304,8 +607,20 @@ void restart_song(void) // FKA Player.selectSong(1) SDL_LockAudioDevice(audioDevice); - lds_rewind(); + #ifdef WITH_MIDI + if (music_device & IS_MIDI_DEVICE){ + // Rewind isn't implemented for fluidsynth or native midi, so we have to stop and start it again + _stop_midi(); + _play_midi(song_playing); + } + else + #endif + { + lds_rewind(); + } + fading_out = false; + time_playing = 0; music_stopped = false; SDL_UnlockAudioDevice(audioDevice); @@ -317,6 +632,13 @@ void stop_song(void) // FKA Player.selectSong(0) return; SDL_LockAudioDevice(audioDevice); +#ifdef WITH_MIDI + if (music_device & IS_MIDI_DEVICE){ + _stop_midi(); + } +#endif + fading_out = false; + time_playing = 0; music_stopped = true; @@ -330,7 +652,19 @@ void fade_song(void) // FKA Player.selectSong($C001) SDL_LockAudioDevice(audioDevice); - lds_fade(1); + fading_out = true; +#ifdef WITH_MIDI + if (music_device & IS_MIDI_DEVICE){ + if (playing) + { + Mix_FadeOutMusic(6000); + } + } + else +#endif + { + lds_fade(1); + } SDL_UnlockAudioDevice(audioDevice); } diff --git a/src/loudness.h b/src/loudness.h index 9e735cb5..59e7f304 100644 --- a/src/loudness.h +++ b/src/loudness.h @@ -29,10 +29,22 @@ extern int audioSampleRate; extern unsigned int song_playing; extern bool audio_disabled, music_disabled, samples_disabled; +extern char soundfont[4096]; + +typedef enum { + OPL, + FLUIDSYNTH, + NATIVE_MIDI, + MUSIC_DEVICE_MAX +} MusicDevice; + +extern MusicDevice music_device; + +extern const char *const music_device_names[MUSIC_DEVICE_MAX]; bool init_audio(void); void deinit_audio(void); - +bool restart_audio(void); void load_music(void); void play_song(unsigned int song_num); void restart_song(void); diff --git a/src/opentyr.c b/src/opentyr.c index fc607fae..dbe2f8d7 100644 --- a/src/opentyr.c +++ b/src/opentyr.c @@ -97,6 +97,31 @@ static const char *getScalingModePickerItem(size_t i, char *buffer, size_t buffe return scaling_mode_names[i]; } +static size_t getMusicDevicePickerItemsCount(void) +{ +#ifndef WITH_MIDI + return (size_t)1; +#else + #ifdef NO_NATIVE_MIDI + return (size_t)MUSIC_DEVICE_MAX - 1; + #else + return (size_t)MUSIC_DEVICE_MAX; + #endif +#endif +} + +static const char *getMusicDevicePickerItem(size_t i, char *buffer, size_t bufferSize) +{ + (void)buffer, (void)bufferSize; +#ifndef WITH_MIDI + (void)i; + return music_device_names[0]; +#else + return music_device_names[i]; +#endif +} + + void setupMenu(void) { typedef enum @@ -112,6 +137,7 @@ void setupMenu(void) MENU_ITEM_SCALING_MODE, MENU_ITEM_MUSIC_VOLUME, MENU_ITEM_SOUND_VOLUME, + MENU_ITEM_MUSIC_DEVICE, } MenuItemId; typedef enum @@ -164,6 +190,7 @@ void setupMenu(void) .items = { { MENU_ITEM_MUSIC_VOLUME, "Music Volume", "Change volume with the left/right arrow keys." }, { MENU_ITEM_SOUND_VOLUME, "Sound Volume", "Change volume with the left/right arrow keys." }, + { MENU_ITEM_MUSIC_DEVICE, "Music Device", "Change the music device.", getMusicDevicePickerItemsCount, getMusicDevicePickerItem}, { MENU_ITEM_DONE, "Done", "Return to the previous menu." }, { -1 } }, @@ -271,6 +298,10 @@ void setupMenu(void) JE_rectangle(VGAScreen, xMenuItemValue - 2, y - 2, xMenuItemValue + 96, y + 11, 242); break; + case MENU_ITEM_MUSIC_DEVICE: + draw_font_hv_shadow(VGAScreen, xMenuItemValue, y, music_device_names[music_device], normal_font, left_aligned, 15, -3 + (selected ? 2 : 0) + (disabled ? -4 : 0), false, 2); + break; + default: break; } @@ -376,6 +407,7 @@ void setupMenu(void) case MENU_ITEM_DISPLAY: case MENU_ITEM_SCALER: case MENU_ITEM_SCALING_MODE: + case MENU_ITEM_MUSIC_DEVICE: { action = true; break; @@ -584,6 +616,14 @@ void setupMenu(void) pickerSelectedIndex = scaling_mode; break; } + case MENU_ITEM_MUSIC_DEVICE: + { + JE_playSampleNum(S_CLICK); + + currentPicker = selectedMenuItemId; + pickerSelectedIndex = music_device; + break; + } case MENU_ITEM_MUSIC_VOLUME: { JE_playSampleNum(S_CLICK); @@ -735,6 +775,12 @@ void setupMenu(void) scaling_mode = pickerSelectedIndex; break; } + case MENU_ITEM_MUSIC_DEVICE: + { + music_device = pickerSelectedIndex; + restart_audio(); + break; + } default: break; } diff --git a/src/params.c b/src/params.c index a42ac9b8..aa4e9d61 100644 --- a/src/params.c +++ b/src/params.c @@ -51,6 +51,7 @@ void JE_paramCheck(int argc, char *argv[]) { 'x', 'x', "no-xmas", false }, { 't', 't', "data", true }, + { 'f', 'f', "soundfont", true }, { 'n', 'n', "net", true }, { 256, 0, "net-player-name", true }, // TODO: no short codes because there should @@ -98,7 +99,9 @@ void JE_paramCheck(int argc, char *argv[]) " --net-player-number=NUMBER Sets local player number in a networked game\n" " (1 or 2)\n" " -p, --net-port=PORT Local port to bind (default is 1333)\n" - " -d, --net-delay=FRAMES Set lag-compensation delay (default is 1)\n", argv[0]); + " -d, --net-delay=FRAMES Set lag-compensation delay (default is 1)\n" + " -f, --soundfont=FILE Set the soundfont for MIDI playback\n\n" + , argv[0]); exit(0); break; @@ -106,7 +109,10 @@ void JE_paramCheck(int argc, char *argv[]) // Disables sound/music usage audio_disabled = true; break; - + case 'f': + // Set the soundfont for MIDI playback + strncpy(soundfont, option.arg, MAX(strlen(option.arg) + 1, 4096)); + break; case 'j': // Disables joystick detection ignore_joystick = true; diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 00000000..b7fbbc96 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,15 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "f4456c1b974131b8467c7371a3c302b7f58a99f2", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", + "name": "microsoft" + } + ], + "overlay-ports": ["./ports"] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..a0bb4602 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,41 @@ +{ + "name": "opentyrian2000", + "version": "2000.20240218", + "description": "OpenTyrian2000 is an open-source port of the DOS game Tyrian. It is a fork of OpenTyrian, with the end goal being to replicate the experience of Tyrian 2000 as closely as possible.", + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + }, + { + "name": "sdl2", + "host": true + } + ], + "features": { + "midi": { + "description": "Enable MIDI music support", + "dependencies": [ + "midiproc", + { + "name": "sdl2-mixer-ext", + "features": [ + "fluidsynth", + { + "name": "nativemidi", + "platform": "(windows & !uwp) | osx" + } + ] + } + ] + } + }, + "default-features": [ + "midi" + ] +} \ No newline at end of file