aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--applause.lua2
-rw-r--r--dssi.lua848
-rw-r--r--ladspa.lua519
3 files changed, 849 insertions, 520 deletions
diff --git a/applause.lua b/applause.lua
index 2b86280..91507fa 100644
--- a/applause.lua
+++ b/applause.lua
@@ -2333,4 +2333,4 @@ Client.__gc = Client.kill
-- Additional modules are loaded with dofile(),
-- so they react to reload()
--
-dofile "ladspa.lua"
+dofile "dssi.lua"
diff --git a/dssi.lua b/dssi.lua
new file mode 100644
index 0000000..ba7f47c
--- /dev/null
+++ b/dssi.lua
@@ -0,0 +1,848 @@
+local bit = require "bit"
+local ffi = require "ffi"
+local C = ffi.C
+
+-- ladspa.h/dssi.h extracts.
+-- Comments have been removed for simplicity and defines
+-- have been converted into enums.
+cdef_safe[[
+/*
+ * From ladspa.h (Version 1.1)
+ */
+
+typedef float LADSPA_Data;
+
+typedef int LADSPA_Properties;
+
+typedef int LADSPA_PortDescriptor;
+
+enum {
+ LADSPA_PORT_INPUT = 0x1,
+ LADSPA_PORT_OUTPUT = 0x2,
+ LADSPA_PORT_CONTROL = 0x4,
+ LADSPA_PORT_AUDIO = 0x8
+};
+
+typedef int LADSPA_PortRangeHintDescriptor;
+
+enum {
+ LADSPA_HINT_BOUNDED_BELOW = 0x1,
+ LADSPA_HINT_BOUNDED_ABOVE = 0x2,
+ LADSPA_HINT_TOGGLED = 0x4,
+ LADSPA_HINT_SAMPLE_RATE = 0x8,
+ LADSPA_HINT_LOGARITHMIC = 0x10,
+ LADSPA_HINT_INTEGER = 0x20,
+ LADSPA_HINT_DEFAULT_MASK = 0x3C0,
+ LADSPA_HINT_DEFAULT_NONE = 0x0,
+ LADSPA_HINT_DEFAULT_MINIMUM = 0x40,
+ LADSPA_HINT_DEFAULT_LOW = 0x80,
+ LADSPA_HINT_DEFAULT_MIDDLE = 0xC0,
+ LADSPA_HINT_DEFAULT_HIGH = 0x100,
+ LADSPA_HINT_DEFAULT_MAXIMUM = 0x140,
+ LADSPA_HINT_DEFAULT_0 = 0x200,
+ LADSPA_HINT_DEFAULT_1 = 0x240,
+ LADSPA_HINT_DEFAULT_100 = 0x280,
+ LADSPA_HINT_DEFAULT_440 = 0x2C0
+};
+
+typedef struct _LADSPA_PortRangeHint {
+ LADSPA_PortRangeHintDescriptor HintDescriptor;
+ LADSPA_Data LowerBound;
+ LADSPA_Data UpperBound;
+} LADSPA_PortRangeHint;
+
+typedef void * LADSPA_Handle;
+
+typedef struct _LADSPA_Descriptor {
+ unsigned long UniqueID;
+
+ const char * Label;
+
+ LADSPA_Properties Properties;
+
+ const char * Name;
+ const char * Maker;
+ const char * Copyright;
+
+ unsigned long PortCount;
+ const LADSPA_PortDescriptor * PortDescriptors;
+ const char * const * PortNames;
+ const LADSPA_PortRangeHint * PortRangeHints;
+
+ void * ImplementationData;
+
+ LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
+ unsigned long SampleRate);
+
+ void (*connect_port)(LADSPA_Handle Instance,
+ unsigned long Port,
+ LADSPA_Data * DataLocation);
+
+ void (*activate)(LADSPA_Handle Instance);
+
+ void (*run)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+ void (*run_adding)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+ void (*set_run_adding_gain)(LADSPA_Handle Instance,
+ LADSPA_Data Gain);
+
+ void (*deactivate)(LADSPA_Handle Instance);
+ void (*cleanup)(LADSPA_Handle Instance);
+} LADSPA_Descriptor;
+
+const LADSPA_Descriptor *ladspa_descriptor(unsigned long Index);
+
+/*
+ * From alsa/seq_event.h.
+ * This is practically the entire header but we have to reproduce it
+ * to replace macros with enums.
+ */
+
+typedef unsigned char snd_seq_event_type_t;
+
+/** Sequencer event type */
+enum snd_seq_event_type {
+ /** system status; event data type = #snd_seq_result_t */
+ SND_SEQ_EVENT_SYSTEM = 0,
+ /** returned result status; event data type = #snd_seq_result_t */
+ SND_SEQ_EVENT_RESULT,
+
+ /** note on and off with duration; event data type = #snd_seq_ev_note_t */
+ SND_SEQ_EVENT_NOTE = 5,
+ /** note on; event data type = #snd_seq_ev_note_t */
+ SND_SEQ_EVENT_NOTEON,
+ /** note off; event data type = #snd_seq_ev_note_t */
+ SND_SEQ_EVENT_NOTEOFF,
+ /** key pressure change (aftertouch); event data type = #snd_seq_ev_note_t */
+ SND_SEQ_EVENT_KEYPRESS,
+
+ /** controller; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_CONTROLLER = 10,
+ /** program change; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_PGMCHANGE,
+ /** channel pressure; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_CHANPRESS,
+ /** pitchwheel; event data type = #snd_seq_ev_ctrl_t; data is from -8192 to 8191) */
+ SND_SEQ_EVENT_PITCHBEND,
+ /** 14 bit controller value; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_CONTROL14,
+ /** 14 bit NRPN; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_NONREGPARAM,
+ /** 14 bit RPN; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_REGPARAM,
+
+ /** SPP with LSB and MSB values; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_SONGPOS = 20,
+ /** Song Select with song ID number; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_SONGSEL,
+ /** midi time code quarter frame; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_QFRAME,
+ /** SMF Time Signature event; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_TIMESIGN,
+ /** SMF Key Signature event; event data type = #snd_seq_ev_ctrl_t */
+ SND_SEQ_EVENT_KEYSIGN,
+
+ /** MIDI Real Time Start message; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_START = 30,
+ /** MIDI Real Time Continue message; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_CONTINUE,
+ /** MIDI Real Time Stop message; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_STOP,
+ /** Set tick queue position; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_SETPOS_TICK,
+ /** Set real-time queue position; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_SETPOS_TIME,
+ /** (SMF) Tempo event; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_TEMPO,
+ /** MIDI Real Time Clock message; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_CLOCK,
+ /** MIDI Real Time Tick message; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_TICK,
+ /** Queue timer skew; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_QUEUE_SKEW,
+ /** Sync position changed; event data type = #snd_seq_ev_queue_control_t */
+ SND_SEQ_EVENT_SYNC_POS,
+
+ /** Tune request; event data type = none */
+ SND_SEQ_EVENT_TUNE_REQUEST = 40,
+ /** Reset to power-on state; event data type = none */
+ SND_SEQ_EVENT_RESET,
+ /** Active sensing event; event data type = none */
+ SND_SEQ_EVENT_SENSING,
+
+ /** Echo-back event; event data type = any type */
+ SND_SEQ_EVENT_ECHO = 50,
+ /** OSS emulation raw event; event data type = any type */
+ SND_SEQ_EVENT_OSS,
+
+ /** New client has connected; event data type = #snd_seq_addr_t */
+ SND_SEQ_EVENT_CLIENT_START = 60,
+ /** Client has left the system; event data type = #snd_seq_addr_t */
+ SND_SEQ_EVENT_CLIENT_EXIT,
+ /** Client status/info has changed; event data type = #snd_seq_addr_t */
+ SND_SEQ_EVENT_CLIENT_CHANGE,
+ /** New port was created; event data type = #snd_seq_addr_t */
+ SND_SEQ_EVENT_PORT_START,
+ /** Port was deleted from system; event data type = #snd_seq_addr_t */
+ SND_SEQ_EVENT_PORT_EXIT,
+ /** Port status/info has changed; event data type = #snd_seq_addr_t */
+ SND_SEQ_EVENT_PORT_CHANGE,
+
+ /** Ports connected; event data type = #snd_seq_connect_t */
+ SND_SEQ_EVENT_PORT_SUBSCRIBED,
+ /** Ports disconnected; event data type = #snd_seq_connect_t */
+ SND_SEQ_EVENT_PORT_UNSUBSCRIBED,
+
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR0 = 90,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR1,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR2,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR3,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR4,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR5,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR6,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR7,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR8,
+ /** user-defined event; event data type = any (fixed size) */
+ SND_SEQ_EVENT_USR9,
+
+ /** system exclusive data (variable length); event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_SYSEX = 130,
+ /** error event; event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_BOUNCE,
+ /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_USR_VAR0 = 135,
+ /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_USR_VAR1,
+ /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_USR_VAR2,
+ /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_USR_VAR3,
+ /** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+ SND_SEQ_EVENT_USR_VAR4,
+
+ /** NOP; ignored in any case */
+ SND_SEQ_EVENT_NONE = 255
+};
+
+/** Sequencer event address */
+typedef struct snd_seq_addr {
+ unsigned char client; /**< Client id */
+ unsigned char port; /**< Port id */
+} snd_seq_addr_t;
+
+/** Connection (subscription) between ports */
+typedef struct snd_seq_connect {
+ snd_seq_addr_t sender; /**< sender address */
+ snd_seq_addr_t dest; /**< destination address */
+} snd_seq_connect_t;
+
+
+/** Real-time data record */
+typedef struct snd_seq_real_time {
+ unsigned int tv_sec; /**< seconds */
+ unsigned int tv_nsec; /**< nanoseconds */
+} snd_seq_real_time_t;
+
+/** (MIDI) Tick-time data record */
+typedef unsigned int snd_seq_tick_time_t;
+
+/** unioned time stamp */
+typedef union snd_seq_timestamp {
+ snd_seq_tick_time_t tick; /**< tick-time */
+ struct snd_seq_real_time time; /**< real-time */
+} snd_seq_timestamp_t;
+
+/**
+ * Event mode flags
+ *
+ * NOTE: only 8 bits available!
+ *
+ * NOTE: These were #defines in the original
+ * header.
+ */
+enum {
+ SND_SEQ_TIME_STAMP_TICK = (0<<0), /**< timestamp in clock ticks */
+ SND_SEQ_TIME_STAMP_REAL = (1<<0), /**< timestamp in real time */
+ SND_SEQ_TIME_STAMP_MASK = (1<<0), /**< mask for timestamp bits */
+
+ SND_SEQ_TIME_MODE_ABS = (0<<1), /**< absolute timestamp */
+ SND_SEQ_TIME_MODE_REL = (1<<1), /**< relative to current time */
+ SND_SEQ_TIME_MODE_MASK = (1<<1), /**< mask for time mode bits */
+
+ SND_SEQ_EVENT_LENGTH_FIXED = (0<<2), /**< fixed event size */
+ SND_SEQ_EVENT_LENGTH_VARIABLE = (1<<2), /**< variable event size */
+ SND_SEQ_EVENT_LENGTH_VARUSR = (2<<2), /**< variable event size - user memory space */
+ SND_SEQ_EVENT_LENGTH_MASK = (3<<2), /**< mask for event length bits */
+
+ SND_SEQ_PRIORITY_NORMAL = (0<<4), /**< normal priority */
+ SND_SEQ_PRIORITY_HIGH = (1<<4), /**< event should be processed before others */
+ SND_SEQ_PRIORITY_MASK = (1<<4) /**< mask for priority bits */
+};
+
+/** Note event */
+typedef struct snd_seq_ev_note {
+ unsigned char channel; /**< channel number */
+ unsigned char note; /**< note */
+ unsigned char velocity; /**< velocity */
+ unsigned char off_velocity; /**< note-off velocity; only for #SND_SEQ_EVENT_NOTE */
+ unsigned int duration; /**< duration until note-off; only for #SND_SEQ_EVENT_NOTE */
+} snd_seq_ev_note_t;
+
+/** Controller event */
+typedef struct snd_seq_ev_ctrl {
+ unsigned char channel; /**< channel number */
+ unsigned char unused[3]; /**< reserved */
+ unsigned int param; /**< control parameter */
+ signed int value; /**< control value */
+} snd_seq_ev_ctrl_t;
+
+/** generic set of bytes (12x8 bit) */
+typedef struct snd_seq_ev_raw8 {
+ unsigned char d[12]; /**< 8 bit value */
+} snd_seq_ev_raw8_t;
+
+/** generic set of integers (3x32 bit) */
+typedef struct snd_seq_ev_raw32 {
+ unsigned int d[3]; /**< 32 bit value */
+} snd_seq_ev_raw32_t;
+
+/** external stored data */
+struct snd_seq_ev_ext {
+ unsigned int len; /**< length of data */
+ void *ptr; /**< pointer to data (note: can be 64-bit) */
+} __attribute__((packed));
+/** external stored data */
+typedef struct snd_seq_ev_ext snd_seq_ev_ext_t;
+
+/** Result events */
+typedef struct snd_seq_result {
+ int event; /**< processed event type */
+ int result; /**< status */
+} snd_seq_result_t;
+
+/** Queue skew values */
+typedef struct snd_seq_queue_skew {
+ unsigned int value; /**< skew value */
+ unsigned int base; /**< skew base */
+} snd_seq_queue_skew_t;
+
+/** queue timer control */
+typedef struct snd_seq_ev_queue_control {
+ unsigned char queue; /**< affected queue */
+ unsigned char unused[3]; /**< reserved */
+ union {
+ signed int value; /**< affected value (e.g. tempo) */
+ snd_seq_timestamp_t time; /**< time */
+ unsigned int position; /**< sync position */
+ snd_seq_queue_skew_t skew; /**< queue skew */
+ unsigned int d32[2]; /**< any data */
+ unsigned char d8[8]; /**< any data */
+ } param; /**< data value union */
+} snd_seq_ev_queue_control_t;
+
+/** Sequencer event */
+typedef struct snd_seq_event {
+ snd_seq_event_type_t type; /**< event type */
+ unsigned char flags; /**< event flags */
+ unsigned char tag; /**< tag */
+
+ unsigned char queue; /**< schedule queue */
+ snd_seq_timestamp_t time; /**< schedule time */
+
+ snd_seq_addr_t source; /**< source address */
+ snd_seq_addr_t dest; /**< destination address */
+
+ union {
+ snd_seq_ev_note_t note; /**< note information */
+ snd_seq_ev_ctrl_t control; /**< MIDI control information */
+ snd_seq_ev_raw8_t raw8; /**< raw8 data */
+ snd_seq_ev_raw32_t raw32; /**< raw32 data */
+ snd_seq_ev_ext_t ext; /**< external data */
+ snd_seq_ev_queue_control_t queue; /**< queue control */
+ snd_seq_timestamp_t time; /**< timestamp */
+ snd_seq_addr_t addr; /**< address */
+ snd_seq_connect_t connect; /**< connect information */
+ snd_seq_result_t result; /**< operation result code */
+ } data; /**< event data... */
+} snd_seq_event_t;
+
+/*
+ * From alsa/seq_midi_event.h
+ */
+
+/** container for sequencer midi event parsers */
+typedef struct snd_midi_event snd_midi_event_t;
+
+int snd_midi_event_new(size_t bufsize, snd_midi_event_t **rdev);
+void snd_midi_event_free(snd_midi_event_t *dev);
+void snd_midi_event_reset_encode(snd_midi_event_t *dev);
+
+int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev);
+
+/*
+ * From dssi.h (Version 1.0)
+ */
+
+typedef struct _DSSI_Program_Descriptor {
+ unsigned long Bank;
+ unsigned long Program;
+ const char * Name;
+} DSSI_Program_Descriptor;
+
+typedef struct _DSSI_Descriptor {
+ int DSSI_API_Version;
+
+ const LADSPA_Descriptor *LADSPA_Plugin;
+
+ char *(*configure)(LADSPA_Handle Instance,
+ const char *Key,
+ const char *Value);
+
+ const DSSI_Program_Descriptor *(*get_program)(LADSPA_Handle Instance,
+ unsigned long Index);
+ void (*select_program)(LADSPA_Handle Instance,
+ unsigned long Bank,
+ unsigned long Program);
+
+ int (*get_midi_controller_for_port)(LADSPA_Handle Instance,
+ unsigned long Port);
+
+ void (*run_synth)(LADSPA_Handle Instance,
+ unsigned long SampleCount,
+ snd_seq_event_t *Events,
+ unsigned long EventCount);
+ void (*run_synth_adding)(LADSPA_Handle Instance,
+ unsigned long SampleCount,
+ snd_seq_event_t *Events,
+ unsigned long EventCount);
+ void (*run_multiple_synths)(unsigned long InstanceCount,
+ LADSPA_Handle *Instances,
+ unsigned long SampleCount,
+ snd_seq_event_t **Events,
+ unsigned long *EventCounts);
+ void (*run_multiple_synths_adding)(unsigned long InstanceCount,
+ LADSPA_Handle *Instances,
+ unsigned long SampleCount,
+ snd_seq_event_t **Events,
+ unsigned long *EventCounts);
+} DSSI_Descriptor;
+
+const DSSI_Descriptor *dssi_descriptor(unsigned long Index);
+]]
+
+local asound = ffi.load("asound")
+
+-- Check for symbol existence in C library table.
+-- There does not seem to be a more elegant way to do this.
+local function checkClibSymbol(lib, symbol)
+ return pcall(getmetatable(lib).__index, lib, symbol) == true
+end
+
+-- NOTE: Not a DSSIStream method, so we can call it from ctor()
+local function getPortDefault(hint)
+ local default = bit.band(hint.HintDescriptor,
+ C.LADSPA_HINT_DEFAULT_MASK)
+
+ -- This map contains only the simple defaults since we want
+ -- to avoid more complex calculations if possible
+ local default_to_value = {
+ [C.LADSPA_HINT_DEFAULT_MINIMUM] = hint.LowerBound,
+ [C.LADSPA_HINT_DEFAULT_MAXIMUM] = hint.UpperBound,
+ [C.LADSPA_HINT_DEFAULT_0] = 0,
+ [C.LADSPA_HINT_DEFAULT_1] = 1,
+ [C.LADSPA_HINT_DEFAULT_100] = 100,
+ [C.LADSPA_HINT_DEFAULT_440] = 440
+ }
+
+ local value = default_to_value[default]
+ if value then return value end
+
+ -- Could still be a value dependent on LowerBound/UpperBound...
+
+ local logarithmic = bit.band(hint.HintDescriptor,
+ C.LADSPA_HINT_LOGARITHMIC) ~= 0
+
+ if default == C.LADSPA_HINT_DEFAULT_LOW then
+ return logarithmic and
+ math.exp(math.log(hint.LowerBound)*0.75 + math.log(hint.UpperBound)*0.25) or
+ hint.LowerBound*0.75 + hint.UpperBound*0.25
+ elseif default == C.LADSPA_HINT_DEFAULT_MIDDLE then
+ return logarithmic and
+ math.exp(math.log(hint.LowerBound)*0.5 + math.log(hint.UpperBound)*0.5) or
+ hint.LowerBound*0.5 + hint.UpperBound*0.5
+ elseif default == C.LADSPA_HINT_DEFAULT_HIGH then
+ return logarithmic and
+ math.exp(math.log(hint.LowerBound)*0.25 + math.log(hint.UpperBound)*0.75) or
+ hint.LowerBound*0.25 + hint.UpperBound*0.75
+ end
+
+ -- We can still return nil, if there is no default value
+ -- (or an unknown default)
+end
+
+local function mangleInputPorts(input_ports, ...)
+ -- NOTE: Theoretically, an array can be converted to a
+ -- stream like any other type and used to provide the
+ -- first plugin input port.
+ -- We prevent this (at least for the first stream) to allow
+ -- passing in all input ports in a single table argument.
+ if type(input_ports) ~= "table" or
+ input_ports.is_a_stream then
+ input_ports = {input_ports}
+ end
+ for _, stream in ipairs{...} do
+ table.insert(input_ports, stream)
+ end
+
+ return input_ports
+end
+
+DSSIStream = DeriveClass(Stream)
+
+-- `file` is either the full path to a plugin library or a basename
+-- looked up in $DSSI_PATH and $LADSPA_PATH.
+-- It may be followed by an optional ":Label" to select a plugin by type
+-- from this file (otherwise, the first one is used).
+--
+-- If the plugin is DSSI, the second argument may be a MIDI
+-- event stream but may also be nil.
+--
+-- `input_ports` are tables defining the mapping from
+-- LADSPA port names to Streams or constants for audio and control input ports.
+-- This host does not make a difference between audio and control ports.
+-- A mapping from port Id to Streams (ie. an array of Streams corresponding
+-- with the ports) is also allowed.
+-- All additional arguments are added to this table as an array,
+-- so port mappings can be specified as a list of arguments as well.
+-- Every plugin input port must either be mapped or have a default
+-- value.
+-- Constants are handled specially and are faster than streams.
+-- Multi-channel input streams do not result in muxing of the DSSIStream,
+-- so every input stream must be mono.
+-- However, to ease binding the individual channels of a multi-channel
+-- stream, they are automatically expanded to consecutive input streams.
+--
+-- Multi-channel output plugins are always muxed. But you may use
+-- DSSIStream(...):demux(...) to discard uninteresting output channels.
+--
+-- FIXME: We could simplify things by just assuming a flat array of
+-- input ports
+function DSSIStream:ctor(file, midi_event_stream, ...)
+ local plugin_file, label = file:match("^([^:]+):(.+)")
+ plugin_file = plugin_file or file
+
+ -- NOTE: The FFI clib is saved in the object
+ -- to keep it alive even though we call only function pointers
+ -- after the constructor.
+ if plugin_file:sub(1,1) == "/" then
+ -- Absolute path
+ self.lib = ffi.load(plugin_file)
+ else
+ -- Search in $DSSI_PATH:$LADSPA_PATH
+ local DSSI_PATH = os.getenv("DSSI_PATH") or
+ "/usr/local/lib/dssi:/usr/lib/dssi"
+ local LADSPA_PATH = os.getenv("LADSPA_PATH") or
+ "/usr/local/lib/ladspa:/usr/lib/ladspa"
+
+ for dir in string.gmatch(DSSI_PATH..":"..LADSPA_PATH, "[^:]+") do
+ if dir:sub(-1) ~= "/" then dir = dir.."/" end
+
+ -- Simply try to load the plugin library in this
+ -- directory. We have no standard way of
+ -- checking for file existence anyway.
+ local state, lib = pcall(ffi.load, dir..plugin_file..".so")
+
+ -- If it could be loaded, still make sure it is a DSSI/LADSPA
+ -- library
+ if state and
+ (checkClibSymbol(lib, "dssi_descriptor") or
+ checkClibSymbol(lib, "ladspa_descriptor")) then
+ self.lib = lib
+ break
+ end
+ end
+
+ if not self.lib then
+ error('DSSI/LADSPA plugin library "'..plugin_file..'" not found')
+ end
+ end
+
+ -- Look up plugin by label or just take the first one
+ do
+ local i = 0
+ repeat
+ -- Look for a DSSI entry point
+ if checkClibSymbol(self.lib, "dssi_descriptor") then
+ self.dssi_descriptor = self.lib.dssi_descriptor(i)
+ if self.dssi_descriptor ~= nil then
+ self.ladspa_descriptor = self.dssi_descriptor.LADSPA_Plugin
+ end
+ else
+ -- Otherwise, we are guaranteed to have a LADSPA entry point
+ self.ladspa_descriptor = self.lib.ladspa_descriptor(i)
+ end
+
+ if self.ladspa_descriptor == nil then
+ error('No matching plugin found for "'..file..'"')
+ end
+
+ i = i + 1
+ until not label or ffi.string(self.ladspa_descriptor.Label) == label
+ end
+
+ local input_ports
+
+ if self.dssi_descriptor == nil then
+ input_ports = mangleInputPorts(midi_event_stream, ...)
+ else
+ input_ports = mangleInputPorts(...)
+ self.midi_event_stream = tostream(midi_event_stream)
+ end
+
+ -- Expand all multi-channel input streams in arrays.
+ -- This is handy, since often stereo inputs are defined
+ -- as consecutive input ports.
+ do
+ local i = 1
+ while i <= table.maxn(input_ports) do
+ local port = input_ports[i]
+
+ if type(port) == "table" and port.is_a_stream and
+ port.channels > 1 then
+ table.remove(input_ports, i)
+ for c = port.channels, 1, -1 do
+ table.insert(input_ports, i, port:demux(c))
+ end
+ end
+
+ i = i + 1
+ end
+ end
+
+ -- Array of all input port numbers (origin 0)
+ -- with a corresponding Stream
+ self.input_ports = {}
+ -- Array of streams connected to the input ports.
+ -- Each element corresponds with an port number in self.input_ports
+ self.input_streams = {}
+
+ -- Array of input port numbers with constant values.
+ self.const_input_ports = {}
+ -- Array of constants connected to the `const_input_ports`.
+ local const_input_data = {}
+
+ -- List of output port numbers (origin 0)
+ self.output_ports = {}
+
+ local input_port_count = 0
+ for i = 0, tonumber(self.ladspa_descriptor.PortCount)-1 do
+ local port_descriptor = self.ladspa_descriptor.PortDescriptors[i]
+
+ if bit.band(port_descriptor, C.LADSPA_PORT_INPUT) ~= 0 then
+ input_port_count = input_port_count + 1
+
+ local port_name = ffi.string(self.ladspa_descriptor.PortNames[i])
+
+ -- We must connect all ports, so if the user does not provide
+ -- an input stream or constant, we try to provide a default.
+ -- There may be no default, in which case we throw an error.
+ local data = input_ports[input_port_count] or input_ports[port_name] or
+ getPortDefault(self.ladspa_descriptor.PortRangeHints[i]) or
+ error('Input stream/constant for port "'..port_name..'" in plugin '..
+ '"'..file..'" required')
+
+ if type(data) == "table" and data.is_a_stream then
+ -- Every LADSPA port is single channel, so for the time being
+ -- we allow only single channel input Streams
+ assert(data.channels == 1)
+ -- Since LADSPA plugins can always produce data infinitely,
+ -- the DSSIStream is infinite as well.
+ -- To avoid problems with input streams ending early,
+ -- we enforce them to be infinite as well.
+ -- FIXME: Perhaps DSSIStream should be bounded to
+ -- the shortest input stream.
+ assert(data:len() == math.huge)
+
+ table.insert(self.input_ports, i)
+ table.insert(self.input_streams, data)
+ else
+ table.insert(self.const_input_ports, i)
+ table.insert(const_input_data, data)
+ end
+ elseif bit.band(port_descriptor, C.LADSPA_PORT_OUTPUT) ~= 0 then
+ table.insert(self.output_ports, i)
+ end
+ end
+ assert(#self.output_ports > 0)
+
+ -- Constant input data can be converted to LADSPA_Data array and shared
+ -- among all instances of this Stream.
+ self.const_input_buffers = ffi.new("LADSPA_Data[?]", #const_input_data,
+ unpack(const_input_data))
+
+ -- Just like in SndfileStreams, plugins with multiple output channels
+ -- must be wrapped in a MuxStream
+ if #self.output_ports > 1 then
+ local cached = self:cache()
+ local streams = {}
+ for i = 0, #self.output_ports-1 do
+ streams[i+1] = cached:map(function(frame)
+ return tonumber(frame[i])
+ end)
+ end
+ return MuxStream:new(unpack(streams))
+ end
+end
+
+function DSSIStream:getName()
+ return ffi.string(self.ladspa_descriptor.Name)
+end
+
+function DSSIStream:gtick()
+ -- Get the tick for every (non-constant) input port stream
+ local ticks = table.new(#self.input_streams, 0)
+ for i = 1, #self.input_streams do
+ ticks[i] = self.input_streams[i]:gtick()
+ end
+
+ -- Every input and output port has its own
+ -- one-sample buffer, so we simply allocate a consecutive
+ -- array of LADSPA_Data.
+ local input_buffers = ffi.new("LADSPA_Data[?]", #self.input_ports)
+ -- For output buffers, this also has the advantage that they can
+ -- be returned like an output frame.
+ local output_buffers = ffi.new("LADSPA_Data[?]", #self.output_ports)
+ -- If true, we output frames (multi-channel output)
+ local output_frames = #self.output_ports > 1
+
+ -- The deactivate() handler, if it exists and must be called
+ -- before cleanup.
+ local deactivate
+
+ -- Instantiate plugin. This may fail.
+ -- It is done in gtick(), so the stream can be reused multiple
+ -- times.
+ local handle = self.ladspa_descriptor:instantiate(samplerate)
+ if handle == nil then
+ error('Instantiating LADSPA plugin "'..self:getName()..'" failed')
+ end
+ handle = ffi.gc(handle, function(handle)
+ -- Make sure that deactivate() is called, but only
+ -- after activate(). ladspa.h is unclear whether
+ -- deactivate() can be called without activate().
+ if deactivate then deactivate(handle) end
+ self.ladspa_descriptor.cleanup(handle)
+
+ -- This makes sure that the buffers are only garbage
+ -- collected AFTER cleanup().
+ input_buffers, output_buffers = nil, nil
+ end)
+
+ -- Connect all non-constant input ports
+ for i = 1, #self.input_ports do
+ self.ladspa_descriptor.connect_port(handle, self.input_ports[i],
+ input_buffers + i - 1)
+ end
+
+ -- Connect all constant input ports
+ for i = 1, #self.const_input_ports do
+ self.ladspa_descriptor.connect_port(handle, self.const_input_ports[i],
+ self.const_input_buffers + i - 1)
+ end
+
+ -- Connect output ports
+ for i = 1, #self.output_ports do
+ self.ladspa_descriptor.connect_port(handle, self.output_ports[i],
+ output_buffers + i - 1)
+ end
+
+ local run
+
+ if self.dssi_descriptor ~= nil and
+ self.dssi_descriptor.run_synth ~= nil then
+ local run_synth = self.dssi_descriptor.run_synth
+ local midi_event_tick = self.midi_event_stream:gtick()
+ local seq_event = ffi.new("snd_seq_event_t[1]")
+ local parser = ffi.new("snd_midi_event_t*[1]")
+
+ -- NOTE: Every MIDI stream sample contains one
+ -- MIDI message which is usually 2 or 3 bytes long.
+ -- The first byte is the LSB.
+ assert(asound.snd_midi_event_new(4, parser) == 0)
+
+ parser = ffi.gc(parser[0], asound.snd_midi_event_free)
+
+ local band, rshift = bit.band, bit.rshift
+
+ function run(instance, sample_count)
+ local midi_event = midi_event_tick()
+ local seq_event_num = 0
+
+ if midi_event ~= 0 then
+ asound.snd_midi_event_reset_encode(parser)
+
+ for i = 1, 4 do
+ if asound.snd_midi_event_encode_byte(parser, band(midi_event, 0xFF),
+ seq_event) ~= 0 then
+ seq_event_num = 1
+ break
+ end
+
+ midi_event = rshift(midi_event, 8)
+ end
+ end
+
+ run_synth(instance, sample_count, seq_event, seq_event_num)
+ end
+ else
+ -- Any LADSPA and DSSI plugin must have the run() method.
+ run = self.ladspa_descriptor.run
+ end
+
+ -- Activate plugin.
+ -- It should be safe to do here instead of in the tick function.
+ if self.ladspa_descriptor.activate ~= nil then
+ deactivate = self.ladspa_descriptor.deactivate ~= nil and
+ self.ladspa_descriptor.deactivate
+ self.ladspa_descriptor.activate(handle)
+ end
+
+ return function()
+ -- Fill each input buffer.
+ -- NOTE: Constants have their own buffers and are initialized
+ -- only once in ctor().
+ -- NOTE: Currently every input stream is guaranteed to be
+ -- infinite.
+ for i = 1, #ticks do
+ input_buffers[i-1] = ticks[i]()
+ end
+
+ -- Run for 1 sample
+ run(handle, 1)
+
+ -- For multi-channel output plugins, we return frames.
+ -- This is an intermediate output that the user never sees
+ -- since it is wrapped in a MuxStream (see ctor()).
+ return output_frames and output_buffers or
+ tonumber(output_buffers[0])
+ end
+end
+
+-- For the Stream method, we just assume that the
+-- subject stream is the MIDI stream (DSSI plugin),
+-- or first input stream.
+-- FIXME: This doesn't work for symbolic
+-- port mappings, though.
+function Stream:DSSI(file, ...)
+ return DSSIStream:new(file, self, ...)
+end
diff --git a/ladspa.lua b/ladspa.lua
deleted file mode 100644
index d86c6f0..0000000
--- a/ladspa.lua
+++ /dev/null
@@ -1,519 +0,0 @@
-local bit = require "bit"
-local ffi = require "ffi"
-
--- ladspa.h/dssi.h extracts.
--- Comments have been removed for simplicity and defines
--- have been converted into enums.
-cdef_safe[[
-/*
- * From ladspa.h (Version 1.1)
- */
-
-typedef float LADSPA_Data;
-
-typedef int LADSPA_Properties;
-
-typedef int LADSPA_PortDescriptor;
-
-enum {
- LADSPA_PORT_INPUT = 0x1,
- LADSPA_PORT_OUTPUT = 0x2,
- LADSPA_PORT_CONTROL = 0x4,
- LADSPA_PORT_AUDIO = 0x8
-};
-
-typedef int LADSPA_PortRangeHintDescriptor;
-
-enum {
- LADSPA_HINT_BOUNDED_BELOW = 0x1,
- LADSPA_HINT_BOUNDED_ABOVE = 0x2,
- LADSPA_HINT_TOGGLED = 0x4,
- LADSPA_HINT_SAMPLE_RATE = 0x8,
- LADSPA_HINT_LOGARITHMIC = 0x10,
- LADSPA_HINT_INTEGER = 0x20,
- LADSPA_HINT_DEFAULT_MASK = 0x3C0,
- LADSPA_HINT_DEFAULT_NONE = 0x0,
- LADSPA_HINT_DEFAULT_MINIMUM = 0x40,
- LADSPA_HINT_DEFAULT_LOW = 0x80,
- LADSPA_HINT_DEFAULT_MIDDLE = 0xC0,
- LADSPA_HINT_DEFAULT_HIGH = 0x100,
- LADSPA_HINT_DEFAULT_MAXIMUM = 0x140,
- LADSPA_HINT_DEFAULT_0 = 0x200,
- LADSPA_HINT_DEFAULT_1 = 0x240,
- LADSPA_HINT_DEFAULT_100 = 0x280,
- LADSPA_HINT_DEFAULT_440 = 0x2C0
-};
-
-typedef struct _LADSPA_PortRangeHint {
- LADSPA_PortRangeHintDescriptor HintDescriptor;
- LADSPA_Data LowerBound;
- LADSPA_Data UpperBound;
-} LADSPA_PortRangeHint;
-
-typedef void * LADSPA_Handle;
-
-typedef struct _LADSPA_Descriptor {
- unsigned long UniqueID;
-
- const char * Label;
-
- LADSPA_Properties Properties;
-
- const char * Name;
- const char * Maker;
- const char * Copyright;
-
- unsigned long PortCount;
- const LADSPA_PortDescriptor * PortDescriptors;
- const char * const * PortNames;
- const LADSPA_PortRangeHint * PortRangeHints;
-
- void * ImplementationData;
-
- LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
- unsigned long SampleRate);
-
- void (*connect_port)(LADSPA_Handle Instance,
- unsigned long Port,
- LADSPA_Data * DataLocation);
-
- void (*activate)(LADSPA_Handle Instance);
-
- void (*run)(LADSPA_Handle Instance,
- unsigned long SampleCount);
- void (*run_adding)(LADSPA_Handle Instance,
- unsigned long SampleCount);
- void (*set_run_adding_gain)(LADSPA_Handle Instance,
- LADSPA_Data Gain);
-
- void (*deactivate)(LADSPA_Handle Instance);
- void (*cleanup)(LADSPA_Handle Instance);
-} LADSPA_Descriptor;
-
-const LADSPA_Descriptor *ladspa_descriptor(unsigned long Index);
-
-/*
- * From dssi.h (Version 1.0)
- */
-
-/*
- * Dummy type for snd_seq_event_t.
- * Originally defined in alsa/seq_event.h.
- * We don't need it since we do not support delivery of MIDI events.
- */
-typedef void snd_seq_event_t;
-
-typedef struct _DSSI_Program_Descriptor {
- unsigned long Bank;
- unsigned long Program;
- const char * Name;
-} DSSI_Program_Descriptor;
-
-typedef struct _DSSI_Descriptor {
- int DSSI_API_Version;
-
- const LADSPA_Descriptor *LADSPA_Plugin;
-
- char *(*configure)(LADSPA_Handle Instance,
- const char *Key,
- const char *Value);
-
- const DSSI_Program_Descriptor *(*get_program)(LADSPA_Handle Instance,
- unsigned long Index);
- void (*select_program)(LADSPA_Handle Instance,
- unsigned long Bank,
- unsigned long Program);
-
- int (*get_midi_controller_for_port)(LADSPA_Handle Instance,
- unsigned long Port);
-
- void (*run_synth)(LADSPA_Handle Instance,
- unsigned long SampleCount,
- snd_seq_event_t *Events,
- unsigned long EventCount);
- void (*run_synth_adding)(LADSPA_Handle Instance,
- unsigned long SampleCount,
- snd_seq_event_t *Events,
- unsigned long EventCount);
- void (*run_multiple_synths)(unsigned long InstanceCount,
- LADSPA_Handle *Instances,
- unsigned long SampleCount,
- snd_seq_event_t **Events,
- unsigned long *EventCounts);
- void (*run_multiple_synths_adding)(unsigned long InstanceCount,
- LADSPA_Handle *Instances,
- unsigned long SampleCount,
- snd_seq_event_t **Events,
- unsigned long *EventCounts);
-} DSSI_Descriptor;
-
-const DSSI_Descriptor *dssi_descriptor(unsigned long Index);
-]]
-
--- Check for symbol existence in C library table.
--- There does not seem to be a more elegant way to do this.
-local function checkClibSymbol(lib, symbol)
- return pcall(getmetatable(lib).__index, lib, symbol) == true
-end
-
--- NOTE: Not a LADSPAStream method, so we can call it from ctor()
-local function getPortDefault(hint)
- local default = bit.band(hint.HintDescriptor,
- ffi.C.LADSPA_HINT_DEFAULT_MASK)
-
- -- This map contains only the simple defaults since we want
- -- to avoid more complex calculations if possible
- local default_to_value = {
- [ffi.C.LADSPA_HINT_DEFAULT_MINIMUM] = hint.LowerBound,
- [ffi.C.LADSPA_HINT_DEFAULT_MAXIMUM] = hint.UpperBound,
- [ffi.C.LADSPA_HINT_DEFAULT_0] = 0,
- [ffi.C.LADSPA_HINT_DEFAULT_1] = 1,
- [ffi.C.LADSPA_HINT_DEFAULT_100] = 100,
- [ffi.C.LADSPA_HINT_DEFAULT_440] = 440
- }
-
- local value = default_to_value[default]
- if value then return value end
-
- -- Could still be a value dependent on LowerBound/UpperBound...
-
- local logarithmic = bit.band(hint.HintDescriptor,
- ffi.C.LADSPA_HINT_LOGARITHMIC) ~= 0
-
- if default == ffi.C.LADSPA_HINT_DEFAULT_LOW then
- return logarithmic and
- math.exp(math.log(hint.LowerBound)*0.75 + math.log(hint.UpperBound)*0.25) or
- hint.LowerBound*0.75 + hint.UpperBound*0.25
- elseif default == ffi.C.LADSPA_HINT_DEFAULT_MIDDLE then
- return logarithmic and
- math.exp(math.log(hint.LowerBound)*0.5 + math.log(hint.UpperBound)*0.5) or
- hint.LowerBound*0.5 + hint.UpperBound*0.5
- elseif default == ffi.C.LADSPA_HINT_DEFAULT_HIGH then
- return logarithmic and
- math.exp(math.log(hint.LowerBound)*0.25 + math.log(hint.UpperBound)*0.75) or
- hint.LowerBound*0.25 + hint.UpperBound*0.75
- end
-
- -- We can still return nil, if there is no default value
- -- (or an unknown default)
-end
-
-local function mangleInputPorts(input_ports, ...)
- -- NOTE: Theoretically, an array can be converted to a
- -- stream like any other type and used to provide the
- -- first plugin input port.
- -- We prevent this (at least for the first stream) to allow
- -- passing in all input ports in a single table argument.
- if type(input_ports) ~= "table" or
- input_ports.is_a_stream then
- input_ports = {input_ports}
- end
- for _, stream in ipairs{...} do
- table.insert(input_ports, stream)
- end
-
- return input_ports
-end
-
-LADSPAStream = DeriveClass(Stream)
-
--- `file` is either the full path to a plugin library or a basename
--- looked up in $DSSI_PATH and $LADSPA_PATH.
--- It may be followed by an optional ":Label" to select a plugin by type
--- from this file (otherwise, the first one is used).
---
--- `input_ports` are tables defining the mapping from
--- LADSPA port names to Streams or constants for audio and control input ports.
--- This host does not make a difference between audio and control ports.
--- A mapping from port Id to Streams (ie. an array of Streams corresponding
--- with the ports) is also allowed.
--- All additional arguments are added to this table as an array,
--- so port mappings can be specified as a list of arguments as well.
--- Every plugin input port must either be mapped or have a default
--- value.
--- Constants are handled specially and are faster than streams.
--- Multi-channel input streams do not result in muxing of the LADSPAStream,
--- so every input stream must be mono.
--- However, to ease binding the individual channels of a multi-channel
--- stream, they are automatically expanded to consecutive input streams.
---
--- Multi-channel output plugins are always muxed. But you may use
--- LADSPAStream(...):demux(...) to discard uninteresting output channels.
---
--- There is some limited support for DSSI plugins.
--- Currently, DSSI plugins are simply handled like wrappers around
--- LADSPA plugins, which should work for DSSI-based effects that
--- do not expose a LADSPA entry point.
--- However to trigger a soft-synth, some kind of MIDI event delivery
--- appears to be necessary.
--- FIXME: This may be resolved by adding support for a special
--- MIDI event input stream that, e.g. raw MIDI commands, which are
--- parsed into snd_seq_event_t's.
---
--- FIXME: We could simplify things by just assuming a flat array of
--- input ports
-function LADSPAStream:ctor(file, ...)
- local plugin_file, label = file:match("^([^:]+):(.+)")
- plugin_file = plugin_file or file
-
- local input_ports = mangleInputPorts(...)
-
- -- Expand all multi-channel input streams in arrays.
- -- This is handy, since often stereo inputs are defined
- -- as consecutive input ports.
- do
- local i = 1
- while i <= table.maxn(input_ports) do
- local port = input_ports[i]
-
- if type(port) == "table" and port.is_a_stream and
- port.channels > 1 then
- table.remove(input_ports, i)
- for c = port.channels, 1, -1 do
- table.insert(input_ports, i, port:demux(c))
- end
- end
-
- i = i + 1
- end
- end
-
- -- NOTE: The FFI clib is saved in the object
- -- to keep it alive even though we call only function pointers
- -- after the constructor.
- if plugin_file:sub(1,1) == "/" then
- -- Absolute path
- self.lib = ffi.load(plugin_file)
- else
- -- Search in $DSSI_PATH:$LADSPA_PATH
- local DSSI_PATH = os.getenv("DSSI_PATH") or
- "/usr/local/lib/dssi:/usr/lib/dssi"
- local LADSPA_PATH = os.getenv("LADSPA_PATH") or
- "/usr/local/lib/ladspa:/usr/lib/ladspa"
-
- for dir in string.gmatch(DSSI_PATH..":"..LADSPA_PATH, "[^:]+") do
- if dir:sub(-1) ~= "/" then dir = dir.."/" end
-
- -- Simply try to load the plugin library in this
- -- directory. We have no standard way of
- -- checking for file existence anyway.
- local state, lib = pcall(ffi.load, dir..plugin_file..".so")
-
- -- If it could be loaded, still make sure it is a DSSI/LADSPA
- -- library
- if state and
- (checkClibSymbol(lib, "dssi_descriptor") or
- checkClibSymbol(lib, "ladspa_descriptor")) then
- self.lib = lib
- break
- end
- end
-
- if not self.lib then
- error('LADSPA plugin library "'..plugin_file..'" not found')
- end
- end
-
- -- Look up plugin by label or just take the first one
- do
- local i = 0
- repeat
- -- Look for a DSSI entry point
- if checkClibSymbol(self.lib, "dssi_descriptor") then
- local dssi_descriptor = self.lib.dssi_descriptor(i)
- if dssi_descriptor ~= nil then
- self.descriptor = dssi_descriptor.LADSPA_Plugin
- end
- else
- -- Otherwise, we are guaranteed to have a LADSPA entry point
- self.descriptor = self.lib.ladspa_descriptor(i)
- end
-
- if self.descriptor == nil then
- error('No matching plugin found for "'..file..'"')
- end
-
- i = i + 1
- until not label or ffi.string(self.descriptor.Label) == label
- end
-
- -- Array of all input port numbers (origin 0)
- -- with a corresponding Stream
- self.input_ports = {}
- -- Array of streams connected to the input ports.
- -- Each element corresponds with an port number in self.input_ports
- self.input_streams = {}
-
- -- Array of input port numbers with constant values.
- self.const_input_ports = {}
- -- Array of constants connected to the `const_input_ports`.
- local const_input_data = {}
-
- -- List of output port numbers (origin 0)
- self.output_ports = {}
-
- local input_port_count = 0
- for i = 0, tonumber(self.descriptor.PortCount)-1 do
- local port_descriptor = self.descriptor.PortDescriptors[i]
-
- if bit.band(port_descriptor, ffi.C.LADSPA_PORT_INPUT) ~= 0 then
- input_port_count = input_port_count + 1
-
- local port_name = ffi.string(self.descriptor.PortNames[i])
-
- -- We must connect all ports, so if the user does not provide
- -- an input stream or constant, we try to provide a default.
- -- There may be no default, in which case we throw an error.
- local data = input_ports[input_port_count] or input_ports[port_name] or
- getPortDefault(self.descriptor.PortRangeHints[i]) or
- error('Input stream/constant for port "'..port_name..'" in plugin '..
- '"'..file..'" required')
-
- if type(data) == "table" and data.is_a_stream then
- -- Every LADSPA port is single channel, so for the time being
- -- we allow only single channel input Streams
- assert(data.channels == 1)
- -- Since LADSPA plugins can always produce data infinitely,
- -- the LADSPAStream is infinite as well.
- -- To avoid problems with input streams ending early,
- -- we enforce them to be infinite as well.
- -- FIXME: Perhaps LADSPAStream should be bounded to
- -- the shortest input stream.
- assert(data:len() == math.huge)
-
- table.insert(self.input_ports, i)
- table.insert(self.input_streams, data)
- else
- table.insert(self.const_input_ports, i)
- table.insert(const_input_data, data)
- end
- elseif bit.band(port_descriptor, ffi.C.LADSPA_PORT_OUTPUT) ~= 0 then
- table.insert(self.output_ports, i)
- end
- end
- assert(#self.output_ports > 0)
-
- -- Constant input data can be converted to LADSPA_Data array and shared
- -- among all instances of this Stream.
- self.const_input_buffers = ffi.new("LADSPA_Data[?]", #const_input_data,
- unpack(const_input_data))
-
- -- Just like in SndfileStreams, plugins with multiple output channels
- -- must be wrapped in a MuxStream
- if #self.output_ports > 1 then
- local cached = self:cache()
- local streams = {}
- for i = 0, #self.output_ports-1 do
- streams[i+1] = cached:map(function(frame)
- return tonumber(frame[i])
- end)
- end
- return MuxStream:new(unpack(streams))
- end
-end
-
-function LADSPAStream:getName()
- return ffi.string(self.descriptor.Name)
-end
-
-function LADSPAStream:gtick()
- -- Get the tick for every (non-constant) input port stream
- local ticks = table.new(#self.input_streams, 0)
- for i = 1, #self.input_streams do
- ticks[i] = self.input_streams[i]:gtick()
- end
-
- -- Every input and output port has its own
- -- one-sample buffer, so we simply allocate a consecutive
- -- array of LADSPA_Data.
- local input_buffers = ffi.new("LADSPA_Data[?]", #self.input_ports)
- -- For output buffers, this also has the advantage that they can
- -- be returned like an output frame.
- local output_buffers = ffi.new("LADSPA_Data[?]", #self.output_ports)
- -- If true, we output frames (multi-channel output)
- local output_frames = #self.output_ports > 1
-
- -- The deactivate() handler, if it exists and must be called
- -- before cleanup.
- local deactivate
-
- -- Instantiate plugin. This may fail.
- -- It is done in gtick(), so the stream can be reused multiple
- -- times.
- local handle = self.descriptor:instantiate(samplerate)
- if handle == nil then
- error('Instantiating LADSPA plugin "'..self:getName()..'" failed')
- end
- handle = ffi.gc(handle, function(handle)
- -- Make sure that deactivate() is called, but only
- -- after activate(). ladspa.h is unclear whether
- -- deactivate() can be called without activate().
- if deactivate then deactivate(handle) end
- self.descriptor.cleanup(handle)
-
- -- This makes sure that the buffers are only garbage
- -- collected AFTER cleanup().
- input_buffers, output_buffers = nil, nil
- end)
-
- -- Connect all non-constant input ports
- for i = 1, #self.input_ports do
- self.descriptor.connect_port(handle, self.input_ports[i],
- input_buffers + i - 1)
- end
-
- -- Connect all constant input ports
- for i = 1, #self.const_input_ports do
- self.descriptor.connect_port(handle, self.const_input_ports[i],
- self.const_input_buffers + i - 1)
- end
-
- -- Connect output ports
- for i = 1, #self.output_ports do
- self.descriptor.connect_port(handle, self.output_ports[i],
- output_buffers + i - 1)
- end
-
- local run = self.descriptor.run
-
- -- Activate plugin.
- -- It should be safe to do here instead of in the tick function.
- if self.descriptor.activate ~= nil then
- deactivate = self.descriptor.deactivate ~= nil and
- self.descriptor.deactivate
- self.descriptor.activate(handle)
- end
-
- return function()
- -- Fill each input buffer.
- -- NOTE: Constants have their own buffers and are initialized
- -- only once in ctor().
- -- NOTE: Currently every input stream is guaranteed to be
- -- infinite.
- for i = 1, #ticks do
- input_buffers[i-1] = ticks[i]()
- end
-
- -- Run for 1 sample
- run(handle, 1)
-
- -- For multi-channel output plugins, we return frames.
- -- This is an intermediate output that the user never sees
- -- since it is wrapped in a MuxStream (see ctor()).
- return output_frames and output_buffers or
- tonumber(output_buffers[0])
- end
-end
-
--- LADSPA plugins always seem to be infinite,
--- and we force all input streams to be infinite as well
-function LADSPAStream:len() return math.huge end
-
--- For the Stream method, we just assume that the
--- subject stream is passed in as the first input port
-function Stream:LADSPA(file, ...)
- local input_ports = mangleInputPorts(...)
- table.insert(input_ports, 1, self)
-
- return LADSPAStream:new(file, input_ports)
-end