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)