0
0
Fork 0
mirror of https://github.com/ZDoom/fluidsynth.git synced 2025-04-13 19:31:10 +00:00

Revise the sequencer's event queue ()

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.
This commit is contained in:
Tom M 2020-05-26 17:16:22 +02:00 committed by GitHub
parent 9995fd88b2
commit 0d98c47545
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 528 additions and 1079 deletions

View file

@ -20,7 +20,7 @@
# CMake based build system. Pedro Lopez-Cabanillas <plcl@users.sf.net>
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 )

View file

@ -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 <code>long long</code> 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?

View file

@ -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);

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 <deque>
#include <algorithm>
/*
* 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<fluid_event_t> 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<fluid_seq_event_type>(left.type);
fluid_seq_event_type rtype = static_cast<fluid_seq_event_type>(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<seq_queue_t*>(que);
}
int fluid_seq_queue_push(void *que, const fluid_event_t *evt)
{
try
{
seq_queue_t& queue = *static_cast<seq_queue_t*>(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<seq_queue_t*>(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<seq_queue_t*>(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;
}
}
}

View file

@ -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 */

View file

@ -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;

View file

@ -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
}

View file

@ -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 */

View file

@ -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 )

View file

@ -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;
}

View file

@ -2,6 +2,7 @@
#include "test.h"
#include "fluidsynth.h" // use local fluidsynth header
#include "fluid_event.h"
#include "fluidsynth_priv.h"
#include <limits.h>
@ -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);

59
test/test_seq_evt_order.c Normal file
View file

@ -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;
}

View file

@ -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)