mirror of
https://github.com/ZDoom/fluidsynth.git
synced 2025-04-13 19:31:10 +00:00
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.
This commit is contained in:
parent
9995fd88b2
commit
0d98c47545
15 changed files with 528 additions and 1079 deletions
|
@ -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 )
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
221
src/midi/fluid_seq_queue.cpp
Normal file
221
src/midi/fluid_seq_queue.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
src/midi/fluid_seq_queue.h
Normal file
44
src/midi/fluid_seq_queue.h
Normal 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 */
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 )
|
||||
|
|
80
test/test_seq_event_queue_remove.c
Normal file
80
test/test_seq_event_queue_remove.c
Normal 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;
|
||||
}
|
|
@ -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
59
test/test_seq_evt_order.c
Normal 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;
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue