From 0d98c47545aa2424741c0cc2b244e4f73249302c Mon Sep 17 00:00:00 2001 From: Tom M Date: Tue, 26 May 2020 17:16:22 +0200 Subject: [PATCH] Revise the sequencer's event queue (#604) Proposing a new event queue for the sequencer, based on prior discussion: https://lists.nongnu.org/archive/html/fluid-dev/2019-12/msg00001.html With this change fluidsynth will require a C++98 compliant compiler. Consider this as RFC, feedback is welcome. The "pain-points" from the discussion: #### 1. It is slow. Done (thanks to heap sort), see runtime of `test_seq_event_queue_sort`. #### 2. A meaningful ordering for events with the same tick has not been considered. Done, see comments in `fluid_seq_queue.cpp`. #### 3. Complicated implementation Now uses one single event queue, which requires C++98. Implemented similarly to std::priority_queue by using heap sort. The "queue" I use currently is of type `std::deque`. `deque` does not provide preallocation. `std::vector` does provide it. However, `std::deque` has the huge advantage that appending additional elements is cheap. For `std::vector` appending new elements would require to reallocate all the memory and copy it to the new array. So, * either use `std::deque` with the risk that memory allocation may occur during `fluid_sequencer_send_at()`, or * use `std::vector` with a preallocated pool of events and make `fluid_sequencer_send_at()` when the `vector` runs out of capacity. Comments? #### 4. Events that have been processed are deleted and gone. After having thought about this more, this is the correct behavior. After events have been dispatched, they must be released to free underlying memory, see point 3. For the very rare case that a client (e.g. fluid_player) may require those events in the future, the client should be responsible for storing the events somewhere. #### 5. The sequencer supports the system timer as alternative time source. The conclusion from the mailing list was that the system timer can be removed. This has been done. #### 6. Time Scaling Time scaling can now be used for arbitrary tempo changes. The previous implementation was capable of that as well, however, the time-scale was limited to 1000. The only limitation for the scale is now >0, see `test_seq_scale`. ### Other Points * `fluid_sequencer_remove_events()` turned out to be broken before, as it did not remove all events from the queue. This has been fixed, see `test_seq_event_queue_remove`. * Consider the following code executed by `threadA`: ```c fluid_sequencer_send_at(event0); fluid_sequencer_set_time_scale(); // new scale fluid_sequencer_send_at(event1); ``` The new scale will be definitely applied to `event1`. However, if another concurrently running `threadB` executes `fluid_sequencer_process()`, it was previously not clear, whether the new scale was also applied to event0. This depends on whether `event0` was still in the `preQueue`, and this depends on `event0.time` and the tick count that `fluid_sequencer_process()` is being called with. This has been changed. As of now, events are queued with their timestamp AS-IS. And only the latest call to `fluid_sequencer_set_time_scale()` is being considered during `fluid_sequencer_process()`. This makes the implementation very simple, i.e. no events need to be changed and the sequencer doesn't have to be locked down. On the other hand, it means that `fluid_sequencer_set_time_scale()` can only be used for tempo changes when called from the sequencer callback. In other words, if `threadA` executes the code above followed by `fluid_sequencer_process()`, `event0` and `event1` will be executed with the same tempo, which is the latest scale provided to the seq. Is this acceptable? The old implementation had the same limitation. And when looking through the internet, I only find users who call `fluid_sequencer_set_time_scale()` from the sequencer callback. Still, this is a point I'm raising for discussion. --- CMakeLists.txt | 23 +- doc/fluidsynth-v20-devdoc.txt | 5 + include/fluidsynth/event.h | 4 +- src/CMakeLists.txt | 1 + src/midi/fluid_seq.c | 923 +++-------------------------- src/midi/fluid_seq_queue.cpp | 221 +++++++ src/midi/fluid_seq_queue.h | 44 ++ src/midi/fluid_seqbind.c | 4 - src/synth/fluid_event.c | 183 +----- src/synth/fluid_event.h | 40 +- test/CMakeLists.txt | 2 + test/test_seq_event_queue_remove.c | 80 +++ test/test_seq_event_queue_sort.c | 6 +- test/test_seq_evt_order.c | 59 ++ test/test_seq_scale.c | 12 +- 15 files changed, 528 insertions(+), 1079 deletions(-) create mode 100644 src/midi/fluid_seq_queue.cpp create mode 100644 src/midi/fluid_seq_queue.h create mode 100644 test/test_seq_event_queue_remove.c create mode 100644 test/test_seq_evt_order.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b9bdf83..ce2fb92f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ # CMake based build system. Pedro Lopez-Cabanillas cmake_minimum_required ( VERSION 3.1.0 ) # because of CMAKE_C_STANDARD -project ( FluidSynth C ) +project ( FluidSynth C CXX ) set ( CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake_admin ) # FluidSynth package name @@ -118,6 +118,12 @@ mark_as_advanced ( LIB_SUFFIX ) # the default C standard to use for all targets set(CMAKE_C_STANDARD 90) +# the default C++ standard to use for all targets +set(CMAKE_CXX_STANDARD 98) + +# whether to use gnu extensions +set(CMAKE_CXX_EXTENSIONS OFF) + # Compile with position independent code if the user requested a shared lib, i.e. no PIC if static requested. # This is cmakes default behavior, but here it's explicitly required due to the use of libfluidsynth-OBJ as object library, # which would otherwise always be compiled without PIC. @@ -191,6 +197,7 @@ if ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_C # define some warning flags set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wpointer-arith -Wcast-qual -Wstrict-prototypes -Wno-unused-parameter -Wdeclaration-after-statement -Werror=implicit-function-declaration" ) + set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -W -Wpointer-arith -Wcast-qual -Wno-unused-parameter" ) # prepend to build type specific flags, to allow users to override set ( CMAKE_C_FLAGS_DEBUG "-g ${CMAKE_C_FLAGS_DEBUG}" ) @@ -201,9 +208,11 @@ if ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_C else () # not intel # gcc and clang support bad function cast and alignment warnings; add them as well. set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wbad-function-cast -Wcast-align" ) + set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align" ) if ( enable-ubsan ) set ( CMAKE_C_FLAGS "-fsanitize=address,undefined ${CMAKE_C_FLAGS}" ) + set ( CMAKE_CXX_FLAGS "-fsanitize=address,undefined ${CMAKE_CXX_FLAGS}" ) set ( CMAKE_EXE_LINKER_FLAGS "-fsanitize=address,undefined ${CMAKE_EXE_LINKER_FLAGS}" ) set ( CMAKE_SHARED_LINKER_FLAGS "-fsanitize=address,undefined ${CMAKE_SHARED_LINKER_FLAGS}" ) set ( ENABLE_UBSAN 1 ) @@ -407,7 +416,7 @@ endif ( NOT CMAKE_BUILD_TYPE ) unset ( ENABLE_DEBUG CACHE ) if ( CMAKE_BUILD_TYPE MATCHES "Debug" ) set ( ENABLE_DEBUG 1 ) - add_definitions(-DDEBUG) + add_definitions(-DDEBUG) # -D_GLIBCXX_DEBUG) # for additional C++ STL container debugging endif ( CMAKE_BUILD_TYPE MATCHES "Debug" ) # Additional targets to perform clang-format/clang-tidy @@ -575,13 +584,13 @@ else(NOT enable-pkgconfig) unset ( OBOE_SUPPORT CACHE ) unset ( OBOE_LIBS CACHE ) if ( enable-oboe ) - # enable C++ as it's needed for oboe - enable_language ( CXX ) - pkg_check_modules ( OBOE oboe-1.0 ) - if ( OBOE_FOUND ) + pkg_check_modules ( OBOE oboe-1.0 ) + if ( OBOE_FOUND ) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) set ( OBOE_SUPPORT 1 ) set ( OBOE_LIBS ${OBOE_LIBRARIES} ) - endif ( OBOE_FOUND ) + endif ( OBOE_FOUND ) endif ( enable-oboe ) unset ( WITH_READLINE CACHE ) diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 642fdd26..841a2d45 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -21,6 +21,7 @@ All the source code examples in this document are in the public domain; you can - \ref Disclaimer - \ref Introduction +- \ref NewIn2_2_0 - \ref NewIn2_1_1 - \ref NewIn2_1_0 - \ref NewIn2_0_8 @@ -72,6 +73,10 @@ What is FluidSynth? \section NewIn2_2_0 What's new in 2.2.0? - #fluid_file_callbacks_t now uses long long as file-offset type (see #fluid_long_long_t). This is a breaking change, which allows to load SoundFonts bigger than 2GiB on Windows. This change required to bump fluidsynth's SOVERSION. +- The sequencer has received a major revisal. For you that means: + - The sequencer's queue no longer blocks the synthesizer thread, due to being busy arranging its events internally. + - Eevents that share the same tick was given a new, documented order, see fluid_sequencer_send_at(). + - The sequencer's scale can now be used for arbitrary tempo changes. Previously, the scale of the sequencer was limited to 1000. The only limitation now is >0. \section NewIn2_1_1 What's new in 2.1.1? diff --git a/include/fluidsynth/event.h b/include/fluidsynth/event.h index 3d7a1f7e..0c19f695 100644 --- a/include/fluidsynth/event.h +++ b/include/fluidsynth/event.h @@ -55,7 +55,7 @@ enum fluid_seq_event_type FLUID_SEQ_REVERBSEND, /**< Reverb send set event */ FLUID_SEQ_CHORUSSEND, /**< Chorus send set event */ FLUID_SEQ_TIMER, /**< Timer event (useful for giving a callback at a certain time) */ - FLUID_SEQ_ANYCONTROLCHANGE, /**< Any control change message (only internally used for remove_events) */ + FLUID_SEQ_ANYCONTROLCHANGE, /**< Any control change message (only internally used for remove_events) @deprecated As of fluidsynth 2.2.0 this enum value is no longer used internally and has been deprecated. */ FLUID_SEQ_CHANNELPRESSURE, /**< Channel aftertouch event @since 1.1.0 */ FLUID_SEQ_KEYPRESSURE, /**< Polyphonic aftertouch event @since 2.0.0 */ FLUID_SEQ_SYSTEMRESET, /**< System reset event @since 1.1.0 */ @@ -111,7 +111,7 @@ FLUIDSYNTH_API void fluid_event_system_reset(fluid_event_t *evt); /* Only for removing events */ -FLUIDSYNTH_API void fluid_event_any_control_change(fluid_event_t *evt, int channel); +FLUID_DEPRECATED FLUIDSYNTH_API void fluid_event_any_control_change(fluid_event_t *evt, int channel); /* Only when unregistering clients */ FLUIDSYNTH_API void fluid_event_unregistering(fluid_event_t *evt); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 356bb734..dc3408bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -205,6 +205,7 @@ set ( libfluidsynth_SOURCES midi/fluid_midi_router.h midi/fluid_seqbind.c midi/fluid_seq.c + midi/fluid_seq_queue.cpp drivers/fluid_adriver.c drivers/fluid_adriver.h drivers/fluid_mdriver.c diff --git a/src/midi/fluid_seq.c b/src/midi/fluid_seq.c index 0969ea35..c525a3b5 100644 --- a/src/midi/fluid_seq.c +++ b/src/midi/fluid_seq.c @@ -30,6 +30,7 @@ #include "fluid_event.h" #include "fluid_sys.h" // timer, threads, etc... #include "fluid_list.h" +#include "fluid_seq_queue.h" /*************************************************************** * @@ -41,23 +42,31 @@ /* Private data for SEQUENCER */ struct _fluid_sequencer_t { + // A backup of currentMs when we have received the last scale change unsigned int startMs; + + // The number of milliseconds passed since we have started the sequencer, + // as indicated by the synth's sample timer fluid_atomic_int_t currentMs; + + // A backup of cur_ticks when we have received the last scale change + unsigned int start_ticks; + + // The tick count which we've used for the most recent event dispatching + unsigned int cur_ticks; + int useSystemTimer; - double scale; // ticks per second + + // The current time scale in ticks per second. + // If you think of MIDI, this is equivalent to: (PPQN / 1000) / (USPQN / 1000) + double scale; + fluid_list_t *clients; fluid_seq_id_t clientsID; - /* for queue + heap */ - fluid_evt_entry *preQueue; - fluid_evt_entry *preQueueLast; - fluid_timer_t *timer; - int queue0StartTime; - short prevCellNb; - fluid_evt_entry *queue0[256][2]; - fluid_evt_entry *queue1[255][2]; - fluid_evt_entry *queueLater; - fluid_evt_heap_t *heap; - fluid_mutex_t mutex; + + // Pointer to the C++ event queue + void *queue; + fluid_rec_mutex_t mutex; }; /* Private data for clients */ @@ -69,17 +78,6 @@ typedef struct _fluid_sequencer_client_t void *data; } fluid_sequencer_client_t; -/* prototypes */ -static short _fluid_seq_queue_init(fluid_sequencer_t *seq, int nbEvents); -static void _fluid_seq_queue_end(fluid_sequencer_t *seq); -static short _fluid_seq_queue_pre_insert(fluid_sequencer_t *seq, fluid_event_t *evt); -static void _fluid_seq_queue_pre_remove(fluid_sequencer_t *seq, fluid_seq_id_t src, fluid_seq_id_t dest, int type); -static int _fluid_seq_queue_process(void *data, unsigned int msec); // callback from timer -static void _fluid_seq_queue_insert_entry(fluid_sequencer_t *seq, fluid_evt_entry *evtentry); -static void _fluid_seq_queue_remove_entries_matching(fluid_sequencer_t *seq, fluid_evt_entry *temp); -static void _fluid_seq_queue_send_queued_events(fluid_sequencer_t *seq); -static void _fluid_free_evt_queue(fluid_evt_entry **first, fluid_evt_entry **last); - /* API implementation */ @@ -128,13 +126,14 @@ new_fluid_sequencer2(int use_system_timer) seq->scale = 1000; // default value seq->useSystemTimer = use_system_timer ? 1 : 0; seq->startMs = seq->useSystemTimer ? fluid_curtime() : 0; - seq->clients = NULL; - seq->clientsID = 0; - if(-1 == _fluid_seq_queue_init(seq, FLUID_SEQUENCER_EVENTS_MAX)) + fluid_rec_mutex_init(seq->mutex); + + seq->queue = new_fluid_seq_queue(FLUID_SEQUENCER_EVENTS_MAX); + if(seq->queue == NULL) { - FLUID_FREE(seq); FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); + delete_fluid_sequencer(seq); return NULL; } @@ -158,7 +157,8 @@ delete_fluid_sequencer(fluid_sequencer_t *seq) fluid_sequencer_unregister_client(seq, client->id); } - _fluid_seq_queue_end(seq); + fluid_rec_mutex_destroy(seq->mutex); + delete_fluid_seq_queue(seq->queue); FLUID_FREE(seq); } @@ -431,6 +431,7 @@ fluid_sequencer_send_now(fluid_sequencer_t *seq, fluid_event_t *evt) } } + /** * Schedule an event for sending at a later time. * @param seq Sequencer object @@ -439,11 +440,22 @@ fluid_sequencer_send_now(fluid_sequencer_t *seq, fluid_event_t *evt) * @param absolute TRUE if \a time is absolute sequencer time (time since sequencer * creation), FALSE if relative to current time. * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note The sequencer sorts events according to their timestamp \c time. For events that have + * the same timestamp, fluidsynth (as of version 2.2.0) uses the following order, according to + * which events will be dispatched to the client's callback function. + * - #FLUID_SEQ_SYSTEMRESET events precede any other event type. + * - #FLUID_SEQ_UNREGISTERING events succeed #FLUID_SEQ_SYSTEMRESET and precede other event type. + * - #FLUID_SEQ_NOTEON and #FLUID_SEQ_NOTE events succeed any other event type. + * - Otherwise the order is undefined. + * \n + * Or mathematically: #FLUID_SEQ_SYSTEMRESET < #FLUID_SEQ_UNREGISTERING < ... < (#FLUID_SEQ_NOTEON && #FLUID_SEQ_NOTE) */ int fluid_sequencer_send_at(fluid_sequencer_t *seq, fluid_event_t *evt, unsigned int time, int absolute) { + int res; unsigned int now = fluid_sequencer_get_tick(seq); fluid_return_val_if_fail(seq != NULL, FLUID_FAILED); @@ -458,8 +470,11 @@ fluid_sequencer_send_at(fluid_sequencer_t *seq, fluid_event_t *evt, /* time stamp event */ fluid_event_set_time(evt, time); - /* queue for processing later */ - return _fluid_seq_queue_pre_insert(seq, evt); + fluid_rec_mutex_lock(seq->mutex); + res = fluid_seq_queue_push(seq->queue, evt); + fluid_rec_mutex_unlock(seq->mutex); + + return res; } /** @@ -475,7 +490,9 @@ fluid_sequencer_remove_events(fluid_sequencer_t *seq, fluid_seq_id_t source, { fluid_return_if_fail(seq != NULL); - _fluid_seq_queue_pre_remove(seq, source, dest, type); + fluid_rec_mutex_lock(seq->mutex); + fluid_seq_queue_remove(seq->queue, source, dest, type); + fluid_rec_mutex_unlock(seq->mutex); } @@ -483,13 +500,8 @@ fluid_sequencer_remove_events(fluid_sequencer_t *seq, fluid_seq_id_t source, time **************************************/ -/** - * Get the current tick of a sequencer. - * @param seq Sequencer object - * @return Current tick value - */ -unsigned int -fluid_sequencer_get_tick(fluid_sequencer_t *seq) +static unsigned int +fluid_sequencer_get_tick_LOCAL(fluid_sequencer_t *seq, unsigned int cur_msec) { unsigned int absMs; double nowFloat; @@ -497,76 +509,56 @@ fluid_sequencer_get_tick(fluid_sequencer_t *seq) fluid_return_val_if_fail(seq != NULL, 0u); - absMs = seq->useSystemTimer ? (int) fluid_curtime() : fluid_atomic_int_get(&seq->currentMs); + absMs = seq->useSystemTimer ? (unsigned int) fluid_curtime() : cur_msec; nowFloat = ((double)(absMs - seq->startMs)) * seq->scale / 1000.0f; now = nowFloat; - return now; + return seq->start_ticks + now; +} + +/** + * Get the current tick of the sequencer scaled by the time scale currently set. + * @param seq Sequencer object + * @return Current tick value + */ +unsigned int +fluid_sequencer_get_tick(fluid_sequencer_t *seq) +{ + return fluid_sequencer_get_tick_LOCAL(seq, fluid_atomic_int_get(&seq->currentMs)); } /** * Set the time scale of a sequencer. * @param seq Sequencer object * @param scale Sequencer scale value in ticks per second - * (default is 1000 for 1 tick per millisecond, max is 1000.0) + * (default is 1000 for 1 tick per millisecond) * * If there are already scheduled events in the sequencer and the scale is changed * the events are adjusted accordingly. + * + * @note May only be called from a sequencer callback or initially when no event dispatching happens. + * Otherwise it will mess up your event timing, because you have zero contol over which events are + * affected by the scale change. */ void fluid_sequencer_set_time_scale(fluid_sequencer_t *seq, double scale) { fluid_return_if_fail(seq != NULL); + if(scale != scale) + { + FLUID_LOG(FLUID_WARN, "sequencer: scale NaN\n"); + return; + } + if(scale <= 0) { FLUID_LOG(FLUID_WARN, "sequencer: scale <= 0 : %f\n", scale); return; } - if(scale > 1000.0) - // Otherwise : problems with the timer = 0ms... - { - scale = 1000.0; - } - - if(seq->scale != scale) - { - double oldScale = seq->scale; - - // stop timer - if(seq->timer) - { - delete_fluid_timer(seq->timer); - seq->timer = NULL; - } - - seq->scale = scale; - - // change start0 so that cellNb is preserved - seq->queue0StartTime = (seq->queue0StartTime + seq->prevCellNb) * (seq->scale / oldScale) - seq->prevCellNb; - - // change all preQueue events for new scale - { - fluid_evt_entry *tmp; - tmp = seq->preQueue; - - while(tmp) - { - if(tmp->entryType == FLUID_EVT_ENTRY_INSERT) - { - tmp->evt.time = tmp->evt.time * seq->scale / oldScale; - } - - tmp = tmp->next; - } - } - - /* re-start timer */ - if(seq->useSystemTimer) - { - seq->timer = new_fluid_timer((int)(1000 / seq->scale), _fluid_seq_queue_process, (void *)seq, TRUE, FALSE, TRUE); - } - } + seq->scale = scale; + seq->startMs = fluid_atomic_int_get(&seq->currentMs); + seq->start_ticks = seq->cur_ticks; } /** @@ -581,272 +573,6 @@ fluid_sequencer_get_time_scale(fluid_sequencer_t *seq) return seq->scale; } - -/********************** - - the queue - -**********************/ - -/* - The queue stores all future events to be processed. - - Data structures - - There is a heap, allocated at init time, for managing a pool - of event entries, that is description of an event, its time, - and whether it is a normal event or a removal command. - - The queue is separated in two arrays, and a list. The first - array 'queue0' corresponds to the events to be sent in the - next 256 ticks (0 to 255), the second array 'queue1' contains - the events to be send from now+256 to now+65535. The list - called 'queueLater' contains the events to be sent later than - that. In each array, one cell contains a list of events having - the same time (in the queue0 array), or the same time/256 (in - the queue1 array), and a pointer to the last event in the list - of the cell so as to be able to insert fast at the end of the - list (i.e. a cell = 2 pointers). The 'queueLater' list is - ordered by time and by post time. This way, inserting 'soon' - events is fast (below 65535 ticks, that is about 1 minute if 1 - tick=1ms). Inserting later events is more slow, but this is a - realtime engine, isn't it ? - - The queue0 starts at queue0StartTime. When 256 ticks have - elapsed, the queue0 array is emptied, and the first cell of - the queue1 array is expanded in the queue0 array, according to - the time of each event. The queue1 array is shifted to the - left, and the first events of the queueLater list are inserte - in the last cell of the queue1 array. - - We remember the previously managed cell in queue0 in the - prevCellNb variable. When processing the current cell, we - process the events in between (late events). - - Functions - - The main thread functions first get an event entry from the - heap, and copy the given event into it, then merely enqueue it - in a preQueue. This is in order to protect the data structure: - everything is managed in the callback (thread or interrupt, - depending on the architecture). - - All queue data structure management is done in a timer - callback: '_fluid_seq_queue_process'. The - _fluid_seq_queue_process function first process the preQueue, - inserting or removing event entries from the queue, then - processes the queue, by sending events ready to be sent at the - current time. - - Critical sections between the main thread (or app) and the - sequencer thread (or interrupt) are: - - - the heap management (if two threads get a free event at the - same time) - - the preQueue access. - - These are really small and fast sections (merely a pointer or - two changing value). They are not protected by a mutex for now - (August 2002). Waiting for crossplatform mutex solutions. When - changing this code, beware that the - _fluid_seq_queue_pre_insert function may be called by the - callback of the queue thread (ex : a note event inserts a - noteoff event). - -*/ - - -/********************/ -/* API */ -/********************/ - -static short -_fluid_seq_queue_init(fluid_sequencer_t *seq, int maxEvents) -{ - seq->heap = _fluid_evt_heap_init(maxEvents); - - if(seq->heap == NULL) - { - FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); - return -1; - } - - seq->preQueue = NULL; - seq->preQueueLast = NULL; - - FLUID_MEMSET(seq->queue0, 0, 2 * 256 * sizeof(fluid_evt_entry *)); - FLUID_MEMSET(seq->queue1, 0, 2 * 255 * sizeof(fluid_evt_entry *)); - - seq->queueLater = NULL; - seq->queue0StartTime = fluid_sequencer_get_tick(seq); - seq->prevCellNb = -1; - - fluid_mutex_init(seq->mutex); - - /* start timer */ - if(seq->useSystemTimer) - { - seq->timer = new_fluid_timer((int)(1000 / seq->scale), _fluid_seq_queue_process, - (void *)seq, TRUE, FALSE, TRUE); - } - - return (0); -} - -static void -_fluid_seq_queue_end(fluid_sequencer_t *seq) -{ - int i; - - /* free all remaining events */ - _fluid_free_evt_queue(&seq->preQueue, &seq->preQueueLast); - - for(i = 0; i < 256; i++) - { - _fluid_free_evt_queue(&(seq->queue0[i][0]), &(seq->queue0[i][1])); - } - - for(i = 0; i < 255; i++) - { - _fluid_free_evt_queue(&(seq->queue1[i][0]), &(seq->queue1[i][1])); - } - - _fluid_free_evt_queue(&seq->queueLater, NULL); - - - if(seq->timer) - { - delete_fluid_timer(seq->timer); - seq->timer = NULL; - } - - if(seq->heap) - { - _fluid_evt_heap_free(seq->heap); - seq->heap = NULL; - } - - fluid_mutex_destroy(seq->mutex); -} - - - -/********************/ -/* queue management */ -/********************/ - -/* Create event_entry and append to the preQueue. - * May be called from the main thread (usually) but also recursively - * from the queue thread, when a callback itself does an insert... */ -static short -_fluid_seq_queue_pre_insert(fluid_sequencer_t *seq, fluid_event_t *evt) -{ - fluid_evt_entry *evtentry = _fluid_seq_heap_get_free(seq->heap); - - if(evtentry == NULL) - { - /* should not happen */ - FLUID_LOG(FLUID_PANIC, "sequencer: no more free events\n"); - return -1; - } - - evtentry->next = NULL; - evtentry->entryType = FLUID_EVT_ENTRY_INSERT; - FLUID_MEMCPY(&(evtentry->evt), evt, sizeof(fluid_event_t)); - - fluid_mutex_lock(seq->mutex); - - /* append to preQueue */ - if(seq->preQueueLast) - { - seq->preQueueLast->next = evtentry; - } - else - { - seq->preQueue = evtentry; - } - - seq->preQueueLast = evtentry; - - fluid_mutex_unlock(seq->mutex); - - return (0); -} - -/* Create event_entry and append to the preQueue. - * May be called from the main thread (usually) but also recursively - * from the queue thread, when a callback itself does an insert... */ -static void -_fluid_seq_queue_pre_remove(fluid_sequencer_t *seq, fluid_seq_id_t src, fluid_seq_id_t dest, int type) -{ - fluid_evt_entry *evtentry = _fluid_seq_heap_get_free(seq->heap); - - if(evtentry == NULL) - { - /* should not happen */ - FLUID_LOG(FLUID_PANIC, "sequencer: no more free events\n"); - return; - } - - evtentry->next = NULL; - evtentry->entryType = FLUID_EVT_ENTRY_REMOVE; - { - fluid_event_t *evt = &(evtentry->evt); - fluid_event_set_source(evt, src); - fluid_event_set_source(evt, src); - fluid_event_set_dest(evt, dest); - evt->type = type; - } - - fluid_mutex_lock(seq->mutex); - - /* append to preQueue */ - if(seq->preQueueLast) - { - seq->preQueueLast->next = evtentry; - } - else - { - seq->preQueue = evtentry; - } - - seq->preQueueLast = evtentry; - - fluid_mutex_unlock(seq->mutex); - return; -} - -static void -_fluid_free_evt_queue(fluid_evt_entry **first, fluid_evt_entry **last) -{ - fluid_evt_entry *tmp2; - fluid_evt_entry *tmp = *first; - - while(tmp != NULL) - { - tmp2 = tmp->next; - FLUID_FREE(tmp); - tmp = tmp2; - } - - *first = NULL; - - if(last != NULL) - { - *last = NULL; - } -} - -/* Callback from timer (may be in a different thread, or in an interrupt) */ -static int -_fluid_seq_queue_process(void *data, unsigned int msec) -{ - fluid_sequencer_t *seq = (fluid_sequencer_t *)data; - fluid_sequencer_process(seq, msec); - /* continue timer */ - return 1; -} - /** * Advance a sequencer. * @@ -860,497 +586,10 @@ _fluid_seq_queue_process(void *data, unsigned int msec) void fluid_sequencer_process(fluid_sequencer_t *seq, unsigned int msec) { - - /* process prequeue */ - fluid_evt_entry *tmp; - fluid_evt_entry *next; - - fluid_mutex_lock(seq->mutex); - - /* get the preQueue */ - tmp = seq->preQueue; - seq->preQueue = NULL; - seq->preQueueLast = NULL; - - fluid_mutex_unlock(seq->mutex); - - /* walk all the preQueue and process them in order : inserts and removes */ - while(tmp) - { - next = tmp->next; - - if(tmp->entryType == FLUID_EVT_ENTRY_REMOVE) - { - _fluid_seq_queue_remove_entries_matching(seq, tmp); - } - else - { - _fluid_seq_queue_insert_entry(seq, tmp); - } - - tmp = next; - } - - /* send queued events */ fluid_atomic_int_set(&seq->currentMs, msec); - _fluid_seq_queue_send_queued_events(seq); + seq->cur_ticks = fluid_sequencer_get_tick_LOCAL(seq, msec); -} - -#if 0 -static void -_fluid_seq_queue_print_later(fluid_sequencer_t *seq) -{ - int count = 0; - fluid_evt_entry *tmp = seq->queueLater; - - printf("queueLater:\n"); - - while(tmp) - { - unsigned int delay = tmp->evt.time - seq->queue0StartTime; - printf("queueLater: Delay = %i\n", delay); - tmp = tmp->next; - count++; - } - - printf("queueLater: Total of %i events\n", count); -} -#endif - -static void -_fluid_seq_queue_insert_queue0(fluid_sequencer_t *seq, fluid_evt_entry *tmp, int cell) -{ - if(seq->queue0[cell][1] == NULL) - { - seq->queue0[cell][1] = seq->queue0[cell][0] = tmp; - } - else - { - seq->queue0[cell][1]->next = tmp; - seq->queue0[cell][1] = tmp; - } - - tmp->next = NULL; -} - -static void -_fluid_seq_queue_insert_queue1(fluid_sequencer_t *seq, fluid_evt_entry *tmp, int cell) -{ - if(seq->queue1[cell][1] == NULL) - { - seq->queue1[cell][1] = seq->queue1[cell][0] = tmp; - } - else - { - seq->queue1[cell][1]->next = tmp; - seq->queue1[cell][1] = tmp; - } - - tmp->next = NULL; -} - -static void -_fluid_seq_queue_insert_queue_later(fluid_sequencer_t *seq, fluid_evt_entry *evtentry) -{ - fluid_evt_entry *prev; - fluid_evt_entry *tmp; - unsigned int time = evtentry->evt.time; - - /* insert in 'queueLater', after the ones that have the same - * time */ - - /* first? */ - if((seq->queueLater == NULL) - || (seq->queueLater->evt.time > time)) - { - evtentry->next = seq->queueLater; - seq->queueLater = evtentry; - return; - } - - /* walk queueLater */ - /* this is the only slow thing : if the event is more - than 65535 ticks after the current time */ - - prev = seq->queueLater; - tmp = prev->next; - - while(tmp) - { - if(tmp->evt.time > time) - { - /* insert before tmp */ - evtentry->next = tmp; - prev->next = evtentry; - return; - } - - prev = tmp; - tmp = prev->next; - } - - /* last */ - evtentry->next = NULL; - prev->next = evtentry; -} - -static void -_fluid_seq_queue_insert_entry(fluid_sequencer_t *seq, fluid_evt_entry *evtentry) -{ - /* time is relative to seq origin, in ticks */ - fluid_event_t *evt = &(evtentry->evt); - unsigned int time = evt->time; - unsigned int delay; - - if(seq->queue0StartTime > 0) - { - /* queue0StartTime could be < 0 if the scale changed a - lot early, breaking the following comparison - */ - if(time < (unsigned int)seq->queue0StartTime) - { - /* we are late, send now */ - fluid_sequencer_send_now(seq, evt); - - _fluid_seq_heap_set_free(seq->heap, evtentry); - return; - } - } - - if(seq->prevCellNb >= 0) - { - /* prevCellNb could be -1 is seq was just started - unlikely */ - /* prevCellNb can also be -1 if cellNb was reset to 0 in - _fluid_seq_queue_send_queued_events() */ - if(time <= (unsigned int)(seq->queue0StartTime + seq->prevCellNb)) - { - /* we are late, send now */ - fluid_sequencer_send_now(seq, evt); - - _fluid_seq_heap_set_free(seq->heap, evtentry); - return; - } - } - - delay = time - seq->queue0StartTime; - - if(delay > 65535) - { - _fluid_seq_queue_insert_queue_later(seq, evtentry); - - } - else if(delay > 255) - { - _fluid_seq_queue_insert_queue1(seq, evtentry, delay / 256 - 1); - - } - else - { - _fluid_seq_queue_insert_queue0(seq, evtentry, delay); - } -} - -static int -_fluid_seq_queue_matchevent(fluid_event_t *evt, int templType, fluid_seq_id_t templSrc, fluid_seq_id_t templDest) -{ - int eventType; - - if(templSrc != -1 && templSrc != fluid_event_get_source(evt)) - { - return 0; - } - - if(templDest != -1 && templDest != fluid_event_get_dest(evt)) - { - return 0; - } - - if(templType == -1) - { - return 1; - } - - eventType = fluid_event_get_type(evt); - - if(templType == eventType) - { - return 1; - } - - if(templType == FLUID_SEQ_ANYCONTROLCHANGE) - { - if(eventType == FLUID_SEQ_PITCHBEND || - eventType == FLUID_SEQ_MODULATION || - eventType == FLUID_SEQ_SUSTAIN || - eventType == FLUID_SEQ_PAN || - eventType == FLUID_SEQ_VOLUME || - eventType == FLUID_SEQ_REVERBSEND || - eventType == FLUID_SEQ_CONTROLCHANGE || - eventType == FLUID_SEQ_CHORUSSEND) - { - return 1; - } - } - - return 0; -} - -static void -_fluid_seq_queue_remove_entries_matching(fluid_sequencer_t *seq, fluid_evt_entry *templ) -{ - /* we walk everything : this is slow, but that is life */ - int i, type; - fluid_seq_id_t src, dest; - - src = templ->evt.src; - dest = templ->evt.dest; - type = templ->evt.type; - - /* we can set it free now */ - _fluid_seq_heap_set_free(seq->heap, templ); - - /* queue0 */ - for(i = 0 ; i < 256 ; i++) - { - fluid_evt_entry *tmp = seq->queue0[i][0]; - fluid_evt_entry *prev = NULL; - - while(tmp) - { - /* remove and/or walk */ - if(_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) - { - /* remove */ - if(prev) - { - prev->next = tmp->next; - - if(tmp == seq->queue0[i][1]) // last one in list - { - seq->queue0[i][1] = prev; - } - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = prev->next; - } - else - { - /* first one in list */ - seq->queue0[i][0] = tmp->next; - - if(tmp == seq->queue0[i][1]) // last one in list - { - seq->queue0[i][1] = NULL; - } - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = seq->queue0[i][0]; - } - } - else - { - prev = tmp; - tmp = prev->next; - } - } - } - - /* queue1 */ - for(i = 0 ; i < 255 ; i++) - { - fluid_evt_entry *tmp = seq->queue1[i][0]; - fluid_evt_entry *prev = NULL; - - while(tmp) - { - if(_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) - { - /* remove */ - if(prev) - { - prev->next = tmp->next; - - if(tmp == seq->queue1[i][1]) // last one in list - { - seq->queue1[i][1] = prev; - } - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = prev->next; - } - else - { - /* first one in list */ - seq->queue1[i][0] = tmp->next; - - if(tmp == seq->queue1[i][1]) // last one in list - { - seq->queue1[i][1] = NULL; - } - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = seq->queue1[i][0]; - } - } - else - { - prev = tmp; - tmp = prev->next; - } - } - } - - /* queueLater */ - { - fluid_evt_entry *tmp = seq->queueLater; - fluid_evt_entry *prev = NULL; - - while(tmp) - { - if(_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) - { - /* remove */ - if(prev) - { - prev->next = tmp->next; - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = prev->next; - } - else - { - seq->queueLater = tmp->next; - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = seq->queueLater; - } - } - else - { - prev = tmp; - tmp = prev->next; - } - } - } -} - -static void -_fluid_seq_queue_send_cell_events(fluid_sequencer_t *seq, int cellNb) -{ - fluid_evt_entry *next; - fluid_evt_entry *tmp; - - tmp = seq->queue0[cellNb][0]; - - while(tmp) - { - fluid_sequencer_send_now(seq, &(tmp->evt)); - - next = tmp->next; - - _fluid_seq_heap_set_free(seq->heap, tmp); - tmp = next; - } - - seq->queue0[cellNb][0] = NULL; - seq->queue0[cellNb][1] = NULL; -} - -static void -_fluid_seq_queue_slide(fluid_sequencer_t *seq) -{ - short i; - fluid_evt_entry *next; - fluid_evt_entry *tmp; - int count = 0; - - /* do the slide */ - seq->queue0StartTime += 256; - - /* sort all queue1[0] into queue0 according to new queue0StartTime */ - tmp = seq->queue1[0][0]; - - while(tmp) - { - unsigned int delay = tmp->evt.time - seq->queue0StartTime; - next = tmp->next; - - if(delay > 255) - { - /* should not happen !! */ - /* append it to queue1[1] */ - _fluid_seq_queue_insert_queue1(seq, tmp, 1); - } - else - { - _fluid_seq_queue_insert_queue0(seq, tmp, delay); - } - - tmp = next; - count++; - } - - /* slide all queue1[i] into queue1[i-1] */ - for(i = 1 ; i < 255 ; i++) - { - seq->queue1[i - 1][0] = seq->queue1[i][0]; - seq->queue1[i - 1][1] = seq->queue1[i][1]; - } - - seq->queue1[254][0] = NULL; - seq->queue1[254][1] = NULL; - - - /* append queueLater to queue1[254] */ - count = 0; - tmp = seq->queueLater; - - while(tmp) - { - unsigned int delay = tmp->evt.time - seq->queue0StartTime; - - if(delay > 65535) - { - break; - } - - next = tmp->next; - - /* append it */ - _fluid_seq_queue_insert_queue1(seq, tmp, 254); - tmp = next; - count++; - } - - seq->queueLater = tmp; -} - -static void -_fluid_seq_queue_send_queued_events(fluid_sequencer_t *seq) -{ - unsigned int nowTicks = fluid_sequencer_get_tick(seq); - short cellNb; - - cellNb = seq->prevCellNb + 1; - - while(cellNb <= (int)(nowTicks - seq->queue0StartTime)) - { - if(cellNb == 256) - { - cellNb = 0; - _fluid_seq_queue_slide(seq); - } /* slide */ - - /* process queue0[cellNb] */ - _fluid_seq_queue_send_cell_events(seq, cellNb); - - /* the current scale may have changed through a callback event */ - nowTicks = fluid_sequencer_get_tick(seq); - - /* next cell */ - cellNb++; - } - - seq->prevCellNb = cellNb - 1; + fluid_rec_mutex_lock(seq->mutex); + fluid_seq_queue_process(seq->queue, seq, seq->cur_ticks); + fluid_rec_mutex_unlock(seq->mutex); } diff --git a/src/midi/fluid_seq_queue.cpp b/src/midi/fluid_seq_queue.cpp new file mode 100644 index 00000000..f0065420 --- /dev/null +++ b/src/midi/fluid_seq_queue.cpp @@ -0,0 +1,221 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2019 Tom Moebert and others. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_seq_queue.h" + +#include +#include + +/* + * This is an implementation of an event queue, sorted according to their timestamp. + * + * Very similar to std::priority_queue. + * + * However, we cannot use std::priority_queue, because fluid_sequencer_remove_events() requires + * access to the underlying container, which however std::priority_queue doesn't provide (without hacks). + * On the other hand, implementing a priority_queue while managing our own heap structure is very + * simple to do using std::make_heap() and friends. + */ + +// we are using a deque rather than vector, because: +// "Expansion of a deque is cheaper than the expansion of a std::vector because it does not involve +// copying of the existing elements to a new memory location." +typedef std::deque seq_queue_t; + +static bool event_compare(const fluid_event_t& left, const fluid_event_t& right) +{ + bool leftIsBeforeRight; + + unsigned int ltime = left.time, rtime = right.time; + if(ltime < rtime) + { + leftIsBeforeRight = true; + } + else if (ltime == rtime) + { + fluid_seq_event_type ltype = static_cast(left.type); + fluid_seq_event_type rtype = static_cast(right.type); + + // Both events have the same tick value. Per MIDI standard, the order is undefined. However, most implementations use a FIFO ordering here, + // which we cannot use, because heap sort is not stable. To make sure that fluidsynth behaves correctly from a user perspective, + // we do the following: + // * System reset events are always first, + // * Unregistering events are second (this gives clients the chance to reset themselves before unregistering at the same tick), + // * Bank changes must precede Prog changes (to ensure correct preset fallback AND preset selection within a certain bank), + // * NoteOn events are always last (this makes sure that all other "state-change" events have been processed and NoteOff events + // with the same key as the NoteOn have been processed (zero-length notes are not a use-case here)). + // * For any other event type, the order is undefined (because the processing order of those event types doesn't matter). + // See: + // https://lists.nongnu.org/archive/html/fluid-dev/2019-12/msg00001.html + // https://lists.nongnu.org/archive/html/fluid-dev/2017-05/msg00004.html + // + // The expression below ensures the order described above. + // This boolean expression is written in disjunctive normal form and can be obtained by using the Karnaugh map below, + // which contains + // * possible values of rtype in columns, + // * possible values of ltype in rows, + // * the boolean values to indicate whether leftIsBeforeRight, + // * X meaning any other event type, and + // * the '*' means that it could be zero, but making it 1 simplyfies the boolean expression. + // + // | ltype \ rtype | SYSR | UNREG | BANK | PROG | NOTEON | X | + // | SYSR | 1 | 1 | 1 | 1 | 1 | 1 | + // | UNREG | 0 | 1 | 1 | 1 | 1 | 1 | + // | BANK | 0 | 0 | 1 | 1 | 1 | 1* | + // | PROG | 0 | 0 | 0 | 1 | 1 | 1* | + // | NOTEON | 0 | 0 | 0 | 0 | 1 | 0 | + // | X | 0 | 0 | 0 | 0 | 1 | 1 | + // + // The values in the diagonal (i.e. comparision with itself) must be true to make them become false after leaving this + // function in order to satisfy the irreflexive requirement, i.e. assert(!(a < a)) + + leftIsBeforeRight = + // first row in table + (ltype == FLUID_SEQ_SYSTEMRESET) + // the rtype NOTEON column + || (rtype == FLUID_SEQ_NOTEON || rtype == FLUID_SEQ_NOTE) + // the second row in table + || (rtype != FLUID_SEQ_SYSTEMRESET && ltype == FLUID_SEQ_UNREGISTERING) + // the third row in table + || (rtype != FLUID_SEQ_SYSTEMRESET && rtype != FLUID_SEQ_UNREGISTERING && ltype == FLUID_SEQ_BANKSELECT) + // the fourth row in table + || (rtype != FLUID_SEQ_SYSTEMRESET && rtype != FLUID_SEQ_UNREGISTERING && rtype != FLUID_SEQ_BANKSELECT && ltype == FLUID_SEQ_PROGRAMCHANGE) + // the bottom right value, i.e. any other type compared to any other type + || (ltype != FLUID_SEQ_SYSTEMRESET && ltype != FLUID_SEQ_UNREGISTERING && ltype != FLUID_SEQ_BANKSELECT && ltype != FLUID_SEQ_PROGRAMCHANGE && ltype != FLUID_SEQ_NOTEON && ltype != FLUID_SEQ_NOTE && + rtype != FLUID_SEQ_SYSTEMRESET && rtype != FLUID_SEQ_UNREGISTERING && rtype != FLUID_SEQ_BANKSELECT && rtype != FLUID_SEQ_PROGRAMCHANGE && rtype != FLUID_SEQ_NOTEON && rtype != FLUID_SEQ_NOTE); + } + else + { + leftIsBeforeRight = false; + } + + // we must negate the return value, because we are building a max-heap, i.e. + // the smallest element is at ::front() + return !leftIsBeforeRight; +} + +int event_compare_for_test(const fluid_event_t* left, const fluid_event_t* right) +{ + return event_compare(*left, *right); +} + +void* new_fluid_seq_queue(int nb_events) +{ + try + { + // As workaround for a missing deque::reserve(), allocate a deque with a size of nb_events + seq_queue_t* queue = new seq_queue_t(nb_events); + // and clear the queue again + queue->clear(); + // C++11 introduced a deque::shrink_to_fit(), so hopefully not calling shrink_to_fit() will + // leave the previously used memory allocated, avoiding reallocation for subsequent insertions. + + return queue; + } + catch(...) + { + return 0; + } +} + +void delete_fluid_seq_queue(void *que) +{ + delete static_cast(que); +} + +int fluid_seq_queue_push(void *que, const fluid_event_t *evt) +{ + try + { + seq_queue_t& queue = *static_cast(que); + + queue.push_back(*evt); + std::push_heap(queue.begin(), queue.end(), event_compare); + + return FLUID_OK; + } + catch(...) + { + return FLUID_FAILED; + } +} + +void fluid_seq_queue_remove(void *que, fluid_seq_id_t src, fluid_seq_id_t dest, int type) +{ + seq_queue_t& queue = *static_cast(que); + + if(src == -1 && dest == -1 && type == -1) + { + // shortcut for deleting everything + queue.clear(); + } + else + { + for (seq_queue_t::iterator it = queue.begin(); it != queue.end();) + { + if((src == -1 || it->src == src) && + (dest == -1 || it->dest == dest) && + (type == -1 || it->type == type)) + { + it = queue.erase(it); + } + else + { + ++it; + } + } + + std::make_heap(queue.begin(), queue.end(), event_compare); + } +} + +static void fluid_seq_queue_pop(seq_queue_t &queue) +{ + std::pop_heap(queue.begin(), queue.end(), event_compare); + queue.pop_back(); +} + +void fluid_seq_queue_process(void *que, fluid_sequencer_t *seq, unsigned int cur_ticks) +{ + seq_queue_t& queue = *static_cast(que); + + while(!queue.empty()) + { + // Get the top most event. + const fluid_event_t& top = queue.front(); + if(top.time <= cur_ticks) + { + // First, copy it to a local buffer. + // This is required because the content of the queue should be read-only to the client, + // however, most client function receive a non-const fluid_event_t pointer + fluid_event_t local_evt = top; + + // Then, pop the queue, so that client-callbacks may add new events without + // messing up the heap structure while we are still processing + fluid_seq_queue_pop(queue); + fluid_sequencer_send_now(seq, &local_evt); + } + else + { + break; + } + } +} + diff --git a/src/midi/fluid_seq_queue.h b/src/midi/fluid_seq_queue.h new file mode 100644 index 00000000..db89c4aa --- /dev/null +++ b/src/midi/fluid_seq_queue.h @@ -0,0 +1,44 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_SEQ_QUE_H +#define _FLUID_SEQ_QUE_H + +#include "fluidsynth.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "fluid_event.h" + +void* new_fluid_seq_queue(int nbEvents); +void delete_fluid_seq_queue(void *queue); +int fluid_seq_queue_push(void *queue, const fluid_event_t *evt); +void fluid_seq_queue_remove(void *queue, fluid_seq_id_t src, fluid_seq_id_t dest, int type); +void fluid_seq_queue_process(void *que, fluid_sequencer_t *seq, unsigned int cur_ticks); + +int event_compare_for_test(const fluid_event_t* left, const fluid_event_t* right); + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUID_SEQ_QUE_H */ diff --git a/src/midi/fluid_seqbind.c b/src/midi/fluid_seqbind.c index 42033dac..53be6be2 100644 --- a/src/midi/fluid_seqbind.c +++ b/src/midi/fluid_seqbind.c @@ -214,10 +214,6 @@ fluid_seq_fluidsynth_callback(unsigned int time, fluid_event_t *evt, fluid_seque fluid_event_get_program(evt)); break; - case FLUID_SEQ_ANYCONTROLCHANGE: - /* nothing = only used by remove_events */ - break; - case FLUID_SEQ_PITCHBEND: fluid_synth_pitch_bend(synth, fluid_event_get_channel(evt), fluid_event_get_pitch(evt)); break; diff --git a/src/synth/fluid_event.c b/src/synth/fluid_event.c index d7962eac..b2ac2c97 100644 --- a/src/synth/fluid_event.c +++ b/src/synth/fluid_event.c @@ -252,12 +252,11 @@ fluid_event_program_select(fluid_event_t *evt, int channel, * Set a sequencer event to be an any control change event (for internal use). * @param evt Sequencer event structure * @param channel MIDI channel number + * @deprecated As of fluidsynth 2.2.0, this function has been deprecated and became a no-op. */ void fluid_event_any_control_change(fluid_event_t *evt, int channel) { - evt->type = FLUID_SEQ_ANYCONTROLCHANGE; - evt->channel = channel; } /** @@ -731,183 +730,3 @@ fluid_event_get_sfont_id(fluid_event_t *evt) { return evt->duration; } - - - -/********************/ -/* heap management */ -/********************/ - -fluid_evt_heap_t * -_fluid_evt_heap_init(int nbEvents) -{ -#ifdef HEAP_WITH_DYNALLOC - - int i; - fluid_evt_heap_t *heap; - fluid_evt_entry *tmp; - - heap = FLUID_NEW(fluid_evt_heap_t); - - if(heap == NULL) - { - FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); - return NULL; - } - - heap->freelist = NULL; - fluid_mutex_init(heap->mutex); - - /* LOCK */ - fluid_mutex_lock(heap->mutex); - - /* Allocate the event entries */ - for(i = 0; i < nbEvents; i++) - { - tmp = FLUID_NEW(fluid_evt_entry); - tmp->next = heap->freelist; - heap->freelist = tmp; - } - - /* UNLOCK */ - fluid_mutex_unlock(heap->mutex); - - -#else - int i; - fluid_evt_heap_t *heap; - int siz = 2 * sizeof(fluid_evt_entry *) + sizeof(fluid_evt_entry) * nbEvents; - - heap = (fluid_evt_heap_t *)FLUID_MALLOC(siz); - - if(heap == NULL) - { - FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); - return NULL; - } - - FLUID_MEMSET(heap, 0, siz); - - /* link all heap events */ - { - fluid_evt_entry *tmp = &(heap->pool); - - for(i = 0 ; i < nbEvents - 1 ; i++) - { - tmp[i].next = &(tmp[i + 1]); - } - - tmp[nbEvents - 1].next = NULL; - - /* set head & tail */ - heap->tail = &(tmp[nbEvents - 1]); - heap->head = &(heap->pool); - } -#endif - return (heap); -} - -void -_fluid_evt_heap_free(fluid_evt_heap_t *heap) -{ -#ifdef HEAP_WITH_DYNALLOC - fluid_evt_entry *tmp, *next; - - /* LOCK */ - fluid_mutex_lock(heap->mutex); - - tmp = heap->freelist; - - while(tmp) - { - next = tmp->next; - FLUID_FREE(tmp); - tmp = next; - } - - /* UNLOCK */ - fluid_mutex_unlock(heap->mutex); - fluid_mutex_destroy(heap->mutex); - - FLUID_FREE(heap); - -#else - FLUID_FREE(heap); -#endif -} - -fluid_evt_entry * -_fluid_seq_heap_get_free(fluid_evt_heap_t *heap) -{ -#ifdef HEAP_WITH_DYNALLOC - fluid_evt_entry *evt = NULL; - - /* LOCK */ - fluid_mutex_lock(heap->mutex); - -#if !defined(MACOS9) - - if(heap->freelist == NULL) - { - heap->freelist = FLUID_NEW(fluid_evt_entry); - - if(heap->freelist != NULL) - { - heap->freelist->next = NULL; - } - } - -#endif - - evt = heap->freelist; - - if(evt != NULL) - { - heap->freelist = heap->freelist->next; - evt->next = NULL; - } - - /* UNLOCK */ - fluid_mutex_unlock(heap->mutex); - - return evt; - -#else - fluid_evt_entry *evt; - - if(heap->head == NULL) - { - return NULL; - } - - /* take from head of the heap */ - /* critical - should threadlock ? */ - evt = heap->head; - heap->head = heap->head->next; - - return evt; -#endif -} - -void -_fluid_seq_heap_set_free(fluid_evt_heap_t *heap, fluid_evt_entry *evt) -{ -#ifdef HEAP_WITH_DYNALLOC - - /* LOCK */ - fluid_mutex_lock(heap->mutex); - - evt->next = heap->freelist; - heap->freelist = evt; - - /* UNLOCK */ - fluid_mutex_unlock(heap->mutex); - -#else - /* append to the end of the heap */ - /* critical - should threadlock ? */ - heap->tail->next = evt; - heap->tail = evt; - evt->next = NULL; -#endif -} diff --git a/src/synth/fluid_event.h b/src/synth/fluid_event.h index 4beb0147..5190fc28 100644 --- a/src/synth/fluid_event.h +++ b/src/synth/fluid_event.h @@ -23,7 +23,10 @@ #define _FLUID_EVENT_PRIV_H #include "fluidsynth.h" -#include "fluid_sys.h" + +#ifdef __cplusplus +extern "C" { +#endif /* Private data for event */ /* ?? should be optimized in size, using unions */ @@ -49,39 +52,8 @@ void fluid_event_set_time(fluid_event_t *evt, unsigned int time); void fluid_event_clear(fluid_event_t *evt); -/* private data for sorter + heap */ -enum fluid_evt_entry_type -{ - FLUID_EVT_ENTRY_INSERT = 0, - FLUID_EVT_ENTRY_REMOVE -}; - -typedef struct _fluid_evt_entry fluid_evt_entry; -struct _fluid_evt_entry -{ - fluid_evt_entry *next; - short entryType; - fluid_event_t evt; -}; - -#define HEAP_WITH_DYNALLOC 1 -/* #undef HEAP_WITH_DYNALLOC */ - -typedef struct _fluid_evt_heap_t -{ -#ifdef HEAP_WITH_DYNALLOC - fluid_evt_entry *freelist; - fluid_mutex_t mutex; -#else - fluid_evt_entry *head; - fluid_evt_entry *tail; - fluid_evt_entry pool; +#ifdef __cplusplus +} #endif -} fluid_evt_heap_t; - -fluid_evt_heap_t *_fluid_evt_heap_init(int nbEvents); -void _fluid_evt_heap_free(fluid_evt_heap_t *heap); -fluid_evt_entry *_fluid_seq_heap_get_free(fluid_evt_heap_t *heap); -void _fluid_seq_heap_set_free(fluid_evt_heap_t *heap, fluid_evt_entry *evt); #endif /* _FLUID_EVENT_PRIV_H */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d1325c5e..7981148c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,6 +21,8 @@ ADD_FLUID_TEST(test_ct2hz) ADD_FLUID_TEST(test_sample_validate) ADD_FLUID_TEST(test_seq_event_queue_sort) ADD_FLUID_TEST(test_seq_scale) +ADD_FLUID_TEST(test_seq_evt_order) +ADD_FLUID_TEST(test_seq_event_queue_remove) ADD_FLUID_TEST(test_jack_obtaining_synth) # if ( LIBSNDFILE_HASVORBIS ) diff --git a/test/test_seq_event_queue_remove.c b/test/test_seq_event_queue_remove.c new file mode 100644 index 00000000..b7da07fc --- /dev/null +++ b/test/test_seq_event_queue_remove.c @@ -0,0 +1,80 @@ + +#include "test.h" +#include "fluidsynth.h" // use local fluidsynth header + + +static int callback_triggered = 0; +void callback_remove_events(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) +{ + callback_triggered = 1; +} + +void test_remove_events(fluid_sequencer_t *seq, fluid_event_t *evt) +{ + // silently creates a fluid_seqbind_t + unsigned int i; + int seqid = fluid_sequencer_register_client(seq, "remove event test", callback_remove_events, NULL); + TEST_SUCCESS(seqid); + + fluid_event_set_source(evt, -1); + fluid_event_set_dest(evt, seqid); + fluid_event_control_change(evt, 0, 1, 127); + + for(i = 0; i < 10000; i++) + { + TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); + } + + fluid_sequencer_remove_events(seq, -1, seqid, -1); + fluid_sequencer_process(seq, i + fluid_sequencer_get_tick(seq)); + TEST_ASSERT(!callback_triggered); + + for(i = 0; i < 10000; i++) + { + fluid_event_control_change(evt, 0, 1, 127); + TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); + + fluid_event_noteon(evt, 0, 64, 127); + TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); + } + + fluid_sequencer_remove_events(seq, -1, seqid, FLUID_SEQ_CONTROLCHANGE); + fluid_sequencer_process(seq, i + fluid_sequencer_get_tick(seq)); + TEST_ASSERT(callback_triggered); + + callback_triggered = 0; + + for(i = 0; i < 10000; i++) + { + fluid_event_any_control_change(evt, 0); + TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); + + fluid_event_noteon(evt, 0, 64, 127); + TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); + } + + fluid_sequencer_remove_events(seq, -1, -1, -1); + fluid_sequencer_process(seq, i + fluid_sequencer_get_tick(seq)); + TEST_ASSERT(!callback_triggered); + + fluid_sequencer_unregister_client(seq, seqid); +} + +// simple test to ensure that manually unregistering and deleting the internal fluid_seqbind_t works without crashing +int main(void) +{ + fluid_event_t *evt; + fluid_sequencer_t *seq = new_fluid_sequencer2(0 /*i.e. use sample timer*/); + TEST_ASSERT(seq != NULL); + TEST_ASSERT(fluid_sequencer_get_use_system_timer(seq) == 0); + evt = new_fluid_event(); + TEST_ASSERT(evt != NULL); + + test_remove_events(seq, evt); + + // client should be removed, deleting the seq should not free the struct again + delete_fluid_event(evt); + delete_fluid_sequencer(seq); + + return EXIT_SUCCESS; +} diff --git a/test/test_seq_event_queue_sort.c b/test/test_seq_event_queue_sort.c index 069f04a5..3944463c 100644 --- a/test/test_seq_event_queue_sort.c +++ b/test/test_seq_event_queue_sort.c @@ -2,6 +2,7 @@ #include "test.h" #include "fluidsynth.h" // use local fluidsynth header #include "fluid_event.h" +#include "fluidsynth_priv.h" #include @@ -9,7 +10,8 @@ static short order = 0; void callback_stable_sort(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { static const int expected_type_order[] = - { FLUID_SEQ_NOTEOFF, FLUID_SEQ_NOTEON, FLUID_SEQ_NOTEOFF, FLUID_SEQ_NOTEON, FLUID_SEQ_SYSTEMRESET, FLUID_SEQ_UNREGISTERING }; + { FLUID_SEQ_NOTEOFF, FLUID_SEQ_NOTEON, FLUID_SEQ_SYSTEMRESET, FLUID_SEQ_UNREGISTERING + /* technically, FLUID_SEQ_NOTEOFF and FLUID_SEQ_NOTEON are to follow, but we've already unregisted */ }; TEST_ASSERT(fluid_event_get_type(event) == expected_type_order[order++]); } @@ -41,7 +43,7 @@ void test_order_same_tick(fluid_sequencer_t *seq, fluid_event_t *evt) TEST_ASSERT(order == 2); fluid_sequencer_process(seq, 2); - TEST_ASSERT(order == 6); + TEST_ASSERT(order == 4); fluid_sequencer_unregister_client(seq, seqid); TEST_ASSERT(fluid_sequencer_count_clients(seq) == 0); diff --git a/test/test_seq_evt_order.c b/test/test_seq_evt_order.c new file mode 100644 index 00000000..78150cd5 --- /dev/null +++ b/test/test_seq_evt_order.c @@ -0,0 +1,59 @@ + +#include "test.h" +#include "fluidsynth.h" // use local fluidsynth header +#include "fluid_seq_queue.h" + +// simple test to ensure that manually unregistering and deleting the internal fluid_seqbind_t works without crashing +int main(void) +{ + fluid_event_t* evt1 = new_fluid_event(); + fluid_event_t* evt2 = new_fluid_event(); + + fluid_event_set_time(evt1, 1); + fluid_event_set_time(evt2, 1); + + // double negation below, because we want to check for leftIsBeforeRight, however, event_compare() returns !leftIsBeforeRight + + TEST_ASSERT( !event_compare_for_test(evt1, evt1)); + TEST_ASSERT( !event_compare_for_test(evt2, evt2)); + + fluid_event_bank_select(evt1, 0, 0); + fluid_event_program_change(evt2, 0, 0); + + TEST_ASSERT( !event_compare_for_test(evt1, evt2)); + TEST_ASSERT(!!event_compare_for_test(evt2, evt1)); + + fluid_event_note(evt1, 0, 0, 0, 1); + + TEST_ASSERT(!!event_compare_for_test(evt1, evt2)); + TEST_ASSERT( !event_compare_for_test(evt2, evt1)); + + fluid_event_noteon(evt1, 0, 0, 0); + fluid_event_noteoff(evt2, 0, 0); + + TEST_ASSERT(!!event_compare_for_test(evt1, evt2)); + TEST_ASSERT( !event_compare_for_test(evt2, evt1)); + + fluid_event_unregistering(evt1); + fluid_event_system_reset(evt2); + + TEST_ASSERT(!!event_compare_for_test(evt1, evt2)); + TEST_ASSERT( !event_compare_for_test(evt2, evt1)); + + fluid_event_unregistering(evt1); + fluid_event_pan(evt2, 0, 0); + + TEST_ASSERT( !event_compare_for_test(evt1, evt2)); + TEST_ASSERT(!!event_compare_for_test(evt2, evt1)); + + fluid_event_modulation(evt1, 0, 0); + fluid_event_pan(evt2, 0, 0); + + TEST_ASSERT( !event_compare_for_test(evt1, evt2)); + TEST_ASSERT( !event_compare_for_test(evt2, evt1)); + + delete_fluid_event(evt1); + delete_fluid_event(evt2); + + return EXIT_SUCCESS; +} diff --git a/test/test_seq_scale.c b/test/test_seq_scale.c index 6d1e2b29..832d619d 100644 --- a/test/test_seq_scale.c +++ b/test/test_seq_scale.c @@ -38,12 +38,12 @@ static void schedule_next_callback(unsigned int time) static const unsigned int expected_callback_times[] = { - 0, 0, 120, 120, 240, 240, 360, 360, 480, 1440, - 1560, 1560, 1680, 1680, 1800, 1800, 1920, 2700, - 2820, 2820, 2940, 2940, 3060, 3060, 3180, 4275, - 4395, 4395, 4515, 4515, 4635, 4635, 4755, - 799, 919, 919, 1039, 1039, 1159, 1159, 1279, - 15999 + 0, 0, 120, 120, 240, 240, 360, 360, 480, 720, + 840, 840, 960, 960, 1080, 1080, 1200, 1440, + 1560, 1560, 1680, 1680, 1800, 1800, 1920, 2160, + 2280, 2280, 2400, 2400, 2520, 2520, 2640, + 2880, 3000, 3000, 3120, 3120, 3240, 3240, 3360, + 18080 }; static void fake_synth_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data)