From a53aa10ade9cbbda903fd05dd361344c350167ad Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Tue, 23 Oct 2018 16:01:34 +0200 Subject: [PATCH] add the midi player for Haiku --- CMakeLists.txt | 27 +++++- sources/haikumain.cc | 217 +++++++++++++++++++++++++++++++++++++++++++ sources/haikumain.h | 22 +++++ 3 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 sources/haikumain.cc create mode 100644 sources/haikumain.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bf10278..c6aa112 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,7 +213,7 @@ else() endif() ## RtMidi library -add_library(RtMidi STATIC "thirdparty/rtmidi/RtMidi.cpp") +add_library(RtMidi STATIC EXCLUDE_FROM_ALL "thirdparty/rtmidi/RtMidi.cpp") target_include_directories(RtMidi PUBLIC "thirdparty/rtmidi") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_compile_definitions(RtMidi PUBLIC "__LINUX_ALSA__") @@ -235,7 +235,7 @@ if(JACK_FOUND) endif() ## RtAudio library -add_library(RtAudio STATIC "thirdparty/rtaudio/RtAudio.cpp") +add_library(RtAudio STATIC EXCLUDE_FROM_ALL "thirdparty/rtaudio/RtAudio.cpp") target_include_directories(RtAudio PUBLIC "thirdparty/rtaudio") target_include_directories(RtAudio PRIVATE "thirdparty/rtaudio/include") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -306,6 +306,29 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") install(FILES "${CMAKE_BINARY_DIR}/adlrt.desktop" DESTINATION "share/applications") endif() +## Haiku version +if(CMAKE_SYSTEM_NAME STREQUAL "Haiku") + add_executable(adlhaiku WIN32 "sources/haikumain.cc" ${adl_sources}) + target_compile_definitions(adlhaiku PRIVATE "ADLJACK_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"") + find_library(MEDIA_KIT_LIBRARY "media") + find_library(MIDI2_KIT_LIBRARY "midi2") + target_link_libraries(adlhaiku PRIVATE ADLMIDI_static OPNMIDI_static ring_buffer "${MEDIA_KIT_LIBRARY}" "${MIDI2_KIT_LIBRARY}" ${CMAKE_THREAD_LIBS_INIT}) + if(CURSES_FOUND) + target_compile_definitions(adlhaiku PRIVATE "ADLJACK_USE_CURSES") + target_include_directories(adlhaiku PRIVATE "${CURSES_INCLUDE_DIR}") + target_link_libraries(adlhaiku PRIVATE "${CURSES_LIBRARY}") + elseif(PDCURSES_FOUND) + target_compile_definitions(adlhaiku PRIVATE "ADLJACK_USE_CURSES") + target_compile_definitions(adlhaiku PRIVATE "ADLJACK_USE_GRAPHIC_TERMINAL") + target_link_libraries(adlhaiku PRIVATE pdcurses) + endif() + if(ENABLE_GETTEXT) + target_compile_definitions(adlhaiku PRIVATE "ADLJACK_I18N" ${Iconv_DEFINITIONS}) + target_include_directories(adlhaiku PRIVATE ${Intl_INCLUDE_DIRS} ${Iconv_INCLUDE_DIRS}) + target_link_libraries(adlhaiku PRIVATE ${Intl_LIBRARIES} ${Iconv_LIBRARIES}) + endif() +endif() + ## Data files if(CMAKE_SYSTEM_NAME STREQUAL "Windows") install(FILES "${CMAKE_SOURCE_DIR}/resources/adl.ico" DESTINATION "icons") diff --git a/sources/haikumain.cc b/sources/haikumain.cc new file mode 100644 index 0000000..ed5be6a --- /dev/null +++ b/sources/haikumain.cc @@ -0,0 +1,217 @@ +// Copyright Jean Pierre Cimalando 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "haikumain.h" +#include "insnames.h" +#include "i18n.h" +#include "common.h" +#include + +static std::string program_title = "ADLhaiku"; + +static double arg_latency = 20e-3; // audio latency, 20ms default +static FILE *logstream = stderr; + +struct Midi_Message_Header { + uint8_t size; + double timestamp; +}; + +static void play_buffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format) +{ + Audio_Context &ctx = *(Audio_Context *)cookie; + Ring_Buffer &midi_rb = *ctx.midi_rb; + + size_t nframes = size / (2 * sizeof(float)); + double fs = format.frame_rate; + double ts = 1.0 / fs; + double midi_delta = ctx.midi_delta; + bool midi_stream_started = ctx.midi_stream_started; + + // maximum interval between midi processing cycles + constexpr size_t midi_interval_max = 256; + + for (size_t iframe = 0; iframe != nframes;) { + unsigned segment_nframes = std::min(nframes - iframe, midi_interval_max); + + if (midi_stream_started) + midi_delta += segment_nframes * ts; + else + midi_delta = segment_nframes * ts; + + Midi_Message_Header hdr; + uint8_t evdata[midi_message_max_size]; + while (midi_rb.peek(hdr) && sizeof(hdr) + hdr.size <= midi_rb.size_used()) { + double timestamp = hdr.timestamp; + if (!midi_stream_started) { + timestamp = 0; + midi_stream_started = true; + } + if (midi_delta < timestamp) + break; // not yet + midi_delta -= timestamp; + + midi_rb.discard(sizeof(hdr)); + midi_rb.get(evdata, hdr.size); + play_midi(evdata, hdr.size); + } + + generate_outputs( + (float *)buffer + 2 * iframe, + (float *)buffer + 2 * iframe + 1, + segment_nframes, 2); + + iframe += segment_nframes; + } + + ctx.midi_delta = midi_delta; + ctx.midi_stream_started = midi_stream_started; +} + +static void generic_midi_event(const uint8_t *data, unsigned size, double timestamp, Audio_Context &ctx) +{ + if (size > midi_message_max_size) { + ctx.midi_timestamp_accum += timestamp; + return; + } + Ring_Buffer &midi_rb = *ctx.midi_rb; + Midi_Message_Header hdr; + hdr.size = size; + hdr.timestamp = timestamp + ctx.midi_timestamp_accum; + + // wait for buffer space (this is non-RT!) + while (midi_rb.size_free() < sizeof(hdr) + size) { + // fprintf(logstream, "MIDI buffer full!\n"); + std::this_thread::sleep_for(stc::microseconds(100)); + } + + midi_rb.put(hdr); + midi_rb.put(data, size); + ctx.midi_timestamp_accum = 0; +} + +class ADLSoundPlayer : public BSoundPlayer { +public: + ADLSoundPlayer(Audio_Context &ctx, const media_raw_audio_format &fmt) + : BSoundPlayer(&fmt, ::program_title.c_str(), &play_buffer, nullptr, &ctx) {} +}; + +class ADLMidiConsumer : public BMidiLocalConsumer { +public: + explicit ADLMidiConsumer(Audio_Context &ctx) + : BMidiLocalConsumer(::program_title.c_str()), ctx_(ctx) {} + void Data(uchar *data, size_t length, bool atomic, bigtime_t time) override + { + double timestamp = (time - last_time_) * 1e-6; + last_time_ = time; + generic_midi_event(data, length, timestamp, ctx_); + } +private: + Audio_Context &ctx_; + bigtime_t last_time_ = 0; +}; + +int audio_main() +{ + Audio_Context ctx; + Ring_Buffer midi_rb(midi_buffer_size); + ctx.midi_rb = &midi_rb; + + media_raw_audio_format sound_format; + sound_format.frame_rate = 48000; + sound_format.channel_count = 2; + sound_format.format = media_raw_audio_format::B_AUDIO_FLOAT; +#if B_HOST_IS_LENDIAN + sound_format.byte_order = B_MEDIA_LITTLE_ENDIAN; +#elif B_HOST_IS_BENDIAN + sound_format.byte_order = B_MEDIA_BIG_ENDIAN; +#endif + sound_format.buffer_size = ceil(arg_latency * sound_format.frame_rate); + + ADLSoundPlayer *sound_player = new ADLSoundPlayer(ctx, sound_format); + if (status_t status = sound_player->InitCheck()) { + fprintf(logstream, "Cannot create the sound player (status %ld)\n", (long)status); + return 1; + } + + ADLMidiConsumer *midi_consumer = new ADLMidiConsumer(ctx); + if (!midi_consumer->IsValid()) { + midi_consumer->Release(); + fprintf(logstream, "Cannot create the MIDI consumer\n"); + return 1; + } + + if (!initialize_player(arg_player_type, sound_format.frame_rate, arg_nchip, arg_bankfile, arg_emulator)) + return 1; + + if (status_t status = sound_player->Start()) { + fprintf(logstream, "Cannot start sound playback (status %ld)\n", (long)status); + return 1; + } + if (status_t status = midi_consumer->Register()) { + fprintf(logstream, "Cannot register MIDI consumer (status %ld)\n", (long)status); + return 1; + } + player_ready(); + + // + interface_exec(nullptr, nullptr); + + // + midi_consumer->Unregister(); + sound_player->Stop(); + + // + midi_consumer->Release(); + delete sound_player; + + return 0; +} + +static void usage() +{ + std::string usage_extra; + usage_extra += "\n "; + usage_extra += _("[-L latency-ms]"); + generic_usage("adlhaiku", usage_extra.c_str()); +} + +std::string get_program_title() +{ + return ::program_title; +} + +int main(int argc, char *argv[]) +{ + i18n_setup(); + midi_db.init(); + + for (int c; (c = generic_getopt(argc, argv, "L:A:M:", usage)) != -1;) { + switch (c) { + case 'L': { + double latency = ::arg_latency = std::stod(optarg) * 1e-3; + if (latency <= 0) { + fprintf(stderr, "%s\n", _("Invalid latency.")); + return 1; + } + break; + } + default: + usage(); + return 1; + } + } + + if (argc != optind) + return 1; + + if (0) { + logstream = fopen("log.txt", "w"); + setvbuf(logstream, nullptr, _IOLBF, 0); + } + + handle_signals(); + return audio_main(); +} diff --git a/sources/haikumain.h b/sources/haikumain.h new file mode 100644 index 0000000..8f9c3e6 --- /dev/null +++ b/sources/haikumain.h @@ -0,0 +1,22 @@ +// Copyright Jean Pierre Cimalando 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#pragma once +#include +#include +#include +#include +#include +#include +#include +namespace stc = std::chrono; + +struct Audio_Context +{ + Ring_Buffer *midi_rb = nullptr; + double midi_delta = 0; + bool midi_stream_started = false; + double midi_timestamp_accum = 0; // timestamp accumulation of skipped events +};