diff --git a/.coderabbit.yaml b/.coderabbit.yaml index dbc8cc7..c14d3bf 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -3,3 +3,5 @@ reviews: - "!7.x/src/stk/src/**" - "!7.x/src/stk/include/**" - "!7.x/src/stk/rawwaves/**" + - "!7.x/src/dssi4cs/include/dssi/**" + - "!7.x/src/dssi4cs/include/ladspa/**" diff --git a/.github/workflows/plugins_build_7.yml b/.github/workflows/plugins_build_7.yml index 2379153..64b8eaf 100644 --- a/.github/workflows/plugins_build_7.yml +++ b/.github/workflows/plugins_build_7.yml @@ -142,6 +142,7 @@ jobs: cp build/src/chua/*.so \ build/src/linear_algebra/*.so \ build/src/fluidOpcodes/*.so \ + build/src/dssi4cs/*.so \ build/src/image/*.so \ build/src/websockets/*.so \ build/src/hdf5/*.so \ @@ -481,6 +482,7 @@ jobs: cmake -S 7.x -B build -DCMAKE_BUILD_TYPE=Release \ -DBUILD_CUDA_OPCODES=ON \ -DBUILD_CHUA_OPCODES=OFF \ + -DBUILD_DSSI_OPCODES=OFF \ -DBUILD_FLUID_OPCODES=OFF \ -DBUILD_HDF5_OPCODES=OFF \ -DBUILD_IMAGE_OPCODES=OFF \ diff --git a/7.x/CMakeLists.txt b/7.x/CMakeLists.txt index 90dda4c..35fa501 100644 --- a/7.x/CMakeLists.txt +++ b/7.x/CMakeLists.txt @@ -321,6 +321,7 @@ endif() add_subdirectory(./src/chua) add_subdirectory(./src/linear_algebra) add_subdirectory(./src/fluidOpcodes) +add_subdirectory(./src/dssi4cs) add_subdirectory(./src/image) add_subdirectory(./src/websockets) add_subdirectory(./src/hdf5) diff --git a/7.x/README.md b/7.x/README.md index cf9ef7d..d558c38 100644 --- a/7.x/README.md +++ b/7.x/README.md @@ -9,6 +9,9 @@ Currently, the plugins available in this tree are - fluidOpcodes (requires fluidsynth) **fluidEngine fluidLoad fluidNote fluidCCi fluidCCk fluidControl fluidProgramSelect fluidAllOut fluidOut fluidInfo fluidSetInterpMethod** +- dssi4cs (hosts native DSSI/LADSPA plugins; Linux only, requires the ALSA headers) +**dssiinit dssiactivate dssiaudio dssisynth dssictls dssiget dssinote dssievent dssinrpn dssiprogram dssiconfigure dssiprograminfo dssiinfo dssilist** + - faust (requires the Faust compiler library and headers, `libfaust`) **faustgen faustcompile faustaudio faustdsp faustplay faustctl** @@ -261,6 +264,46 @@ and includes the plugin (and the standard library) in the Linux, macOS and Windows release packages. +DSSI and LADSPA opcodes +--- + +The `dssi4cs` plugin hosts native DSSI synths and LADSPA effects: + +``` +ih dssiinit Sfilename, iindex [, iverbose] + dssilist + dssiinfo ih + dssiactivate ih, kactive +a1[,a2,...] dssisynth ih [, ain1, ...] +a1[,a2,...] dssiaudio ih [, ain1, ...] + dssinote ktrigger, ih, knote, kvelocity, kduration [, ichannel] + dssievent ktrigger, ih, knote, kvelocity + dssievent ktrigger, ih, kstatus, kchannel, kdata1, kdata2 [, koffset] + dssinrpn ktrigger, ih, kchannel, kparameter, kvalue [, koffset] + dssictls ih, iport, kvalue, ktrigger +kvalue dssiget ih, iport + dssiprogram ih, kbank, kprogram, ktrigger +Sname, ibank, iprogram dssiprograminfo ih, iindex + dssiconfigure ih, Skey, Svalue +``` + +The opcodes load a plugin library, instantiate a DSSI synth or LADSPA effect, +feed it Csound audio and MIDI events, and return its audio. Libraries are +looked up by path or through `DSSI_PATH` and `LADSPA_PATH`. See the +[module README](src/dssi4cs/README.md) for the full opcode reference. + +Hosting native plugins is Linux-only: the opcodes load the shared objects with +`dlopen` and use the ALSA sequencer event types, so they need the ALSA headers +(`libasound2-dev` on Debian/Ubuntu) but not the ALSA library or a running +sequencer. The DSSI and LADSPA API headers are **vendored under this +directory** (in `src/dssi4cs/include`), so no system DSSI/LADSPA development +package is required. + +It is built by default on Linux when the ALSA headers are found and silently +disabled elsewhere; configure with `-DBUILD_DSSI_OPCODES=OFF` to exclude it. +It is included in the Linux release package only. + + Release binaries and macOS architecture -------------- @@ -318,10 +361,10 @@ $ make By default, all the plugins are built. If one wants to exclude a plugin from the build process, one can pass an option to the cmake command. The options are `BUILD_CHUA_OPCODES`, `BUILD_CUDA_OPCODES`, -`BUILD_OPENCL_OPCODES`, `BUILD_FAUST_OPCODES`, `BUILD_FLUID_OPCODES`, -`BUILD_IMAGE_OPCODES`, `BUILD_HDF5_OPCODES`, `BUILD_LINEAR_ALGEBRA_OPCODES`, -`BUILD_P5GLOVE_OPCODES`, `BUILD_STK_OPCODES`, `BUILD_WIIMOTE_OPCODES`, -`BUILD_WEBSOCKET_OPCODE` and +`BUILD_DSSI_OPCODES`, `BUILD_OPENCL_OPCODES`, `BUILD_FAUST_OPCODES`, +`BUILD_FLUID_OPCODES`, `BUILD_IMAGE_OPCODES`, `BUILD_HDF5_OPCODES`, +`BUILD_LINEAR_ALGEBRA_OPCODES`, `BUILD_P5GLOVE_OPCODES`, `BUILD_STK_OPCODES`, +`BUILD_WIIMOTE_OPCODES`, `BUILD_WEBSOCKET_OPCODE` and `BUILD_PYTHON_OPCODES`. For example, to exclude the chua plugin, the `cmake` command would be: diff --git a/7.x/src/dssi4cs/CMakeLists.txt b/7.x/src/dssi4cs/CMakeLists.txt new file mode 100644 index 0000000..ea3de70 --- /dev/null +++ b/7.x/src/dssi4cs/CMakeLists.txt @@ -0,0 +1,34 @@ +# Build the DSSI/LADSPA hosting opcodes. +# +# Hosting native DSSI synths and LADSPA effects is Linux-only: the opcodes load +# plugin libraries with dlopen and exchange ALSA sequencer events, so they need +# the system ALSA headers (no ALSA library, and no running sequencer, is +# required). The DSSI and LADSPA API headers are vendored under ./include. +# +# The plugin is built by default and silently disabled where the platform is +# not Linux or the ALSA headers are missing. +option(BUILD_DSSI_OPCODES "Build the DSSI/LADSPA opcodes" ON) + +find_path(DSSI_ALSA_ASOUNDLIB_H alsa/asoundlib.h /usr/local/include /usr/include) +find_path(DSSI_ALSA_SEQ_EVENT_H alsa/seq_event.h /usr/local/include /usr/include) + +check_deps(BUILD_DSSI_OPCODES LINUX DSSI_ALSA_ASOUNDLIB_H DSSI_ALSA_SEQ_EVENT_H) + +if(BUILD_DSSI_OPCODES) + make_plugin(dssi4cs "load.c;dssi4cs.c" ${CMAKE_DL_LIBS} m) + target_include_directories(dssi4cs PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include/dssi + ${CMAKE_CURRENT_SOURCE_DIR}/include/ladspa + ${DSSI_ALSA_ASOUNDLIB_H} + ${CSOUND_INCLUDE_DIRS}) + target_compile_definitions(dssi4cs PRIVATE BUILD_PLUGINS) + + # On multiarch distributions (Debian, Ubuntu, ...) plugins may be + # installed under /usr/lib//dssi and /usr/lib//ladspa. + # Prepend the triplet detected for this build to the default search path; + # DSSI_PATH and LADSPA_PATH still take precedence at runtime. + if(CMAKE_LIBRARY_ARCHITECTURE) + target_compile_definitions(dssi4cs PRIVATE + "DSSI4CS_DEFAULT_PATH=\"/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/dssi:/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/ladspa:/usr/local/lib/dssi:/usr/lib/dssi:/usr/lib64/dssi:/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/lib64/ladspa\"") + endif() +endif() diff --git a/7.x/src/dssi4cs/README.md b/7.x/src/dssi4cs/README.md new file mode 100644 index 0000000..3421178 --- /dev/null +++ b/7.x/src/dssi4cs/README.md @@ -0,0 +1,195 @@ +# DSSI and LADSPA opcodes + +This module hosts native DSSI synths and LADSPA effects on Linux. It is built +by default on Linux when the ALSA headers are available (`libasound2-dev` on +Debian/Ubuntu); configure with `-DBUILD_DSSI_OPCODES=OFF` to exclude it, and it +is silently disabled on other platforms. The module does not need the ALSA +sequencer service or link to libasound. Plugins must match the host OS, CPU, +and DSSI/LADSPA ABI. + +## Loading + +```csound +ih dssiinit Sfilename, iindex [, iverbose] +dssilist +dssiinfo ih +``` + +`iindex` selects a plugin within a library. A successful load returns an +instance handle starting at zero. Each call creates a separate instance. +`iverbose` defaults to 1 and prints ports and MIDI mappings. `dssiinfo` prints +that information for an existing handle. `dssilist` lists both DSSI and LADSPA +libraries, including libraries that export only `dssi_descriptor`. + +A name containing `/` is a path. For other names, search `DSSI_PATH`, then +`LADSPA_PATH`, then platform defaults. Each environment variable contains +colon-separated directories; empty entries are skipped. Lookup tries the +name as supplied, then `.so`. + +Linux defaults include `/usr/local/lib`, `/usr/lib`, and `/usr/lib64`, each +with `dssi` and `ladspa` subdirectories. On multiarch distributions the build +also adds the detected triplet directory, such as +`/usr/lib/x86_64-linux-gnu/dssi`; set the environment paths to search any +other location. + +Instances and libraries remain loaded until Csound reset or destruction. +Failed loads do not publish a handle. Cleanup calls the plugin's deactivate +and cleanup methods before releasing its buffers and library. + +## Audio and activation + +```csound +dssiactivate ih, kactive +a1 [, a2, ...] dssisynth ih [, ain1, ain2, ...] +a1 [, a2, ...] dssiaudio ih [, ain1, ain2, ...] +``` + +`kactive` must be 0 or 1. Activate before sending events or rendering. +`dssisynth` requires a DSSI synth. `dssiaudio` accepts a LADSPA effect or DSSI +plugin and chooses its synth callback when available. Audio ports follow +the descriptor order, with input and output ports counted separately. +Unused inputs receive silence. Csound's `0dbfs` scales audio to/from LADSPA +float buffers. + +Give each instance one audio renderer. The renderer's `ksmps` may be smaller +than the `ksmps` used to create the instance. Partial first/last blocks produce +silence outside the instrument's active samples. Ending the renderer +instrument deactivates the instance and discards pending events. Keep a +renderer running if notes or release tails must outlive their source +instrument. Deactivation clears pending notes; resuming sends note-offs for +old sounding notes, releases sustain, and sends all-sound-off/all-notes-off +even when the plugin has no activation callbacks. + +The host supports plugins that provide only `run_multiple_synths`. Every +call includes all active instances from the same library and LADSPA label. +Activate all grouped instances before the first renderer in that block. +Late activation reports an error rather than advancing a peer twice. +For groups with more than one instance, all renderers must have the same +`ksmps`, use full blocks, and the plugin must have no audio inputs. Send events +for every instance before the group's first renderer. Later renderers read +the outputs from that same group call. + +## Notes and MIDI + +```csound +dssinote ktrigger, ih, knote, kvelocity, kduration [, ichannel] +dssievent ktrigger, ih, knote, kvelocity +dssievent ktrigger, ih, kstatus, kchannel, kdata1, kdata2 [, koffset] +dssinrpn ktrigger, ih, kchannel, kparameter, kvalue [, koffset] +``` + +Each nonzero trigger sends an event in that control cycle; it is not an +edge detector. Use a one-cycle trigger for one note. Notes/velocities range +from 0 to 127. Channels range from 0 to 15 and default to 0 in `dssinote`. +The four-argument `dssievent` sends a note on channel 0; velocity zero sends +a true note-off. `dssinote` schedules a note-off after `kduration` seconds, +rounded to the nearest sample. Its pending note-off survives the source +instrument if a separate renderer remains active. + +The full MIDI form accepts status bytes without channel bits: + +| Status | Meaning | Data | +| --- | --- | --- | +| 128 / 0x80 | Note off | note, release velocity | +| 144 / 0x90 | Note on | note, velocity; zero means note off | +| 160 / 0xA0 | Key pressure | note, pressure | +| 176 / 0xB0 | Controller | controller, value | +| 192 / 0xC0 | Program change | program, 0 | +| 208 / 0xD0 | Channel pressure | pressure, 0 | +| 224 / 0xE0 | Pitch bend | low 7 bits, high 7 bits | + +`koffset` defaults to zero and counts samples from the instrument's first +active sample in this block. It must fall within that block's active range. +Events sort by sample time; equal-time events retain insertion order. +Queue events before rendering their block. Sending an event after its +samples have rendered reports a performance error. + +Bank select controllers 0/32 and MIDI program changes call `select_program` +instead of reaching the synth as MIDI. CC and NRPN mappings use the plugin's +control-port hints and do not also deliver the mapped event. Rendering splits +at mapped control/program changes so they take effect at the requested +sample. Use `dssinrpn` for assembled 14-bit NRPN messages: parameters and values +range from 0 to 16383. The host does not assemble NRPN from raw CC 98/99/6/38. + +## Controls, programs, and configuration + +```csound +dssictls ih, iport, kvalue, ktrigger +kvalue dssiget ih, iport +dssiprogram ih, kbank, kprogram, ktrigger +Sname, ibank, iprogram dssiprograminfo ih, iindex +dssiconfigure ih, Skey, Svalue +``` + +Port numbers are the descriptor's absolute port indices, including audio +ports. `dssictls` writes input control ports on each nonzero trigger, and +retains its existing sample-rate-hint scaling. `dssiget` reads an input or +output control port in the plugin's units. Output controls reflect the last +render. Input controls start with LADSPA defaults; selecting a program may +replace them. Put control writes after program selection when overriding a +preset. Audio ports and output controls cannot be written through `dssictls`. + +`dssiprograminfo` enumerates by list index, not by bank/program number. It +copies the name because plugins may reuse their descriptor storage. An +index beyond the list returns an empty name and -1 for bank/program. +`dssiprogram` selects the given bank/program when triggered. Select a program +explicitly when the plugin needs one; plugins need not choose a default on +activation. Program selection does not imply a note-on. + +`dssiconfigure` runs at initialization. Use it to load sample files, patches, +or other plugin settings before performance. It reports and frees plugin +error strings. `GLOBAL:` keys apply to all currently loaded instances of the +same plugin. Configuration invalidates program information; enumerate and +select again afterwards. Put configuration calls and sample paths in the +orchestra so reloading it restores them. This module does not save separate +plugin state files or launch plugin GUIs. + +## Example + +```csound +sr = 48000 +ksmps = 64 +nchnls = 2 +0dbfs = 1 + +gih dssiinit "my_synth.so", 0, 1 + +instr 1 + dssiactivate gih, 1 + konce init 1 + ; If needed: dssiprogram gih, 0, 0, konce + dssinote konce, gih, 60, 100, 0.5 + konce = 0 + aleft, aright dssisynth gih + outs aleft, aright +endin +``` + +Run the instrument long enough for the note and its release tail. Request +one output for a mono plugin. + +## Limits + +- Native Linux only; no macOS or WASM backend or OSC plugin GUI host. +- One Csound performance thread (`-j` values greater than 1 are rejected). +- At most 256 instances per engine and 1024 pending events per instance. + A timed note reserves both its on and off events before enqueueing either. + Queue overflow reports an error. +- Up to nine audio inputs and nine outputs per opcode, as in the existing API. +- Grouped rendering has the restrictions described above. +- No SysEx, MIDI system messages, or raw NRPN assembly in this version. +- Control ports are scalar LADSPA controls. Mixing uses Csound; the optional + additive DSSI/LADSPA callbacks are not needed. + +## ABI sources + +The [DSSI](include/dssi/README.md) and [LADSPA](include/ladspa/README.md) +headers are unmodified copies of upstream releases, with their licenses and +source details. They retain upstream's C ABI types (`unsigned long`, `int`, +and `float`). See the [DSSI specification](https://dssi.sourceforge.net/RFC.html) +and [LADSPA SDK](https://www.ladspa.org/ladspa_sdk/). +ALSA declarations come from the installed system headers. + +Run `include/dssi/update.py` and `include/ladspa/update.py` to update the +vendored headers, or pass `--pinned` to reproduce the recorded releases and +check their archive hashes. diff --git a/7.x/src/dssi4cs/dssi4cs.c b/7.x/src/dssi4cs/dssi4cs.c new file mode 100644 index 0000000..0f6f2e0 --- /dev/null +++ b/7.x/src/dssi4cs/dssi4cs.c @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2005 Andres Cabrera + * + * The dssi4cs 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. + * + * The dssi4cs 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 The dssi4cs library; if not, write to the Free Software + * Foundation, Inc., 31 Milk Street, #960789, Boston, MA, 02196, USA + * + * Uses code by Richard W.E. Furse from the ladspa sdk + */ + +#include "dssi4cs.h" +#include "utils.h" +#include +#include +#include +#include +#include + +/* State belongs to one Csound engine, and survives until reset. */ +static DSSI_HOST *host(CSOUND *csound) +{ + return csound->QueryGlobalVariable(csound, "$DSSI4CS"); +} + +static int integer(cs_float value, int64_t maximum) +{ + /* Compare as integers so float builds do not round UINT32_MAX up. */ + return value >= 0 && value < 0x1p63 && floor(value) == value && + (int64_t)value <= maximum; +} + +static DSSI_PLUGIN *lookup(CSOUND *csound, cs_float id) +{ + DSSI_HOST *h = host(csound); + return h && h->count && integer(id, h->count - 1) ? + h->plugins[(unsigned int)id] : NULL; +} + +static int64_t block_start(OPDS *p) +{ + return ((int64_t)GetLocalKcounter(p) - 1) * GetLocalKsmps(p); +} + +static int64_t event_time(OPDS *p) +{ + return block_start(p) + GetKsmpsOffset(p); +} + +static void set_active(DSSI_PLUGIN *p, int active) +{ + if (active == p->active) return; + if (active) { + if (p->ladspa->activate) p->ladspa->activate(p->handle); + } + else { + if (p->ladspa->deactivate) p->ladspa->deactivate(p->handle); + p->queued = 0; + p->panic = 1; + } + p->active = active; + p->last_block = -1; +} + +static void destroy_plugin(CSOUND *csound, DSSI_PLUGIN *p) +{ + if (!p) return; + if (p->handle) { + set_active(p, 0); + p->ladspa->cleanup(p->handle); + } + if (p->audio) { + for (unsigned long i = 0; i < p->ladspa->PortCount; ++i) + if (p->audio[i]) csound->Free(csound, p->audio[i]); + csound->Free(csound, p->audio); + } + if (p->control) csound->Free(csound, p->control); + if (p->mapping) csound->Free(csound, p->mapping); + if (p->inputs) csound->Free(csound, p->inputs); + if (p->outputs) csound->Free(csound, p->outputs); + if (p->library) dlclose(p->library); + csound->Free(csound, p); +} + +static int32_t reset(CSOUND *csound, void *data) +{ + DSSI_HOST *h = data; + for (unsigned int i = 0; i < h->count; ++i) + destroy_plugin(csound, h->plugins[i]); + csound->DestroyGlobalVariable(csound, "$DSSI4CS"); + return OK; +} + +static cs_double bound(DSSI_PLUGIN *p, unsigned long port, int upper) +{ + const LADSPA_PortRangeHint *hint = &p->ladspa->PortRangeHints[port]; + return (upper ? hint->UpperBound : hint->LowerBound) * + (LADSPA_IS_HINT_SAMPLE_RATE(hint->HintDescriptor) ? p->sample_rate : 1); +} + +static cs_double interpolate(DSSI_PLUGIN *p, unsigned long port, cs_double fraction) +{ + cs_double lo = bound(p, port, 0), hi = bound(p, port, 1); + LADSPA_PortRangeHintDescriptor flags = p->ladspa->PortRangeHints[port].HintDescriptor; + if (LADSPA_IS_HINT_LOGARITHMIC(flags) && lo > 0 && hi > 0) + return exp(log(lo) * (1 - fraction) + log(hi) * fraction); + return lo * (1 - fraction) + hi * fraction; +} + +static LADSPA_Data default_control(DSSI_PLUGIN *p, unsigned long port) +{ + LADSPA_PortRangeHintDescriptor flags = p->ladspa->PortRangeHints[port].HintDescriptor; + cs_double value = 0; + switch (flags & LADSPA_HINT_DEFAULT_MASK) { + case LADSPA_HINT_DEFAULT_MINIMUM: value = bound(p, port, 0); break; + case LADSPA_HINT_DEFAULT_LOW: value = interpolate(p, port, .25); break; + case LADSPA_HINT_DEFAULT_MIDDLE: value = interpolate(p, port, .5); break; + case LADSPA_HINT_DEFAULT_HIGH: value = interpolate(p, port, .75); break; + case LADSPA_HINT_DEFAULT_MAXIMUM: value = bound(p, port, 1); break; + case LADSPA_HINT_DEFAULT_1: value = 1; break; + case LADSPA_HINT_DEFAULT_100: value = 100; break; + case LADSPA_HINT_DEFAULT_440: value = 440; break; + default: + if (LADSPA_IS_HINT_BOUNDED_BELOW(flags)) value = fmax(value, bound(p, port, 0)); + if (LADSPA_IS_HINT_BOUNDED_ABOVE(flags)) value = fmin(value, bound(p, port, 1)); + } + if (LADSPA_IS_HINT_INTEGER(flags)) value = round(value); + return (LADSPA_Data)value; +} + +static void describe(CSOUND *csound, DSSI_PLUGIN *p) +{ + csound->Message(csound, Str("DSSI4CS: %s: %s (%s), %lu audio inputs, %lu outputs\n"), + p->dssi ? "DSSI" : "LADSPA", p->ladspa->Name, + p->ladspa->Label, p->input_count, p->output_count); + for (unsigned long i = 0; i < p->ladspa->PortCount; ++i) { + LADSPA_PortDescriptor flags = p->ladspa->PortDescriptors[i]; + csound->Message(csound, " %lu: %s, %s %s", i, p->ladspa->PortNames[i], + LADSPA_IS_PORT_INPUT(flags) ? "input" : "output", + LADSPA_IS_PORT_AUDIO(flags) ? "audio" : "control"); + if (LADSPA_IS_PORT_CONTROL(flags)) + csound->Message(csound, Str(", value %g, MIDI mapping %d"), + (cs_double)p->control[i], p->mapping[i]); + csound->Message(csound, "\n"); + } +} + +static int32_t dssiinit(CSOUND *csound, DSSIINIT *op) +{ + *op->result = -1; + if (!integer(*op->index, UINT32_MAX)) + return csound->InitError(csound, Str("DSSI4CS: invalid plugin index\n")); + if (csound->GetOParms(csound)->numThreads > 1) + return csound->InitError(csound, Str("DSSI4CS: use one Csound performance thread\n")); + DSSI_HOST *h = host(csound); + if (!h) { + if (csound->CreateGlobalVariable(csound, "$DSSI4CS", sizeof(DSSI_HOST))) + return csound->InitError(csound, Str("DSSI4CS: cannot create host state\n")); + h = host(csound); + csound->RegisterResetCallback(csound, h, reset); + } + if (h->count == DSSI4CS_INSTANCES) + return csound->InitError(csound, Str("DSSI4CS: instance limit reached\n")); + char legacy_name[MAXNAME]; + const char *name; + if (!strcmp(GetTypeForArg(op->filename)->varTypeName, "S")) + name = ((STRINGDAT *)op->filename)->data; + else { + csound->StringArg2Name(csound, legacy_name, + IsStringCode(*op->filename) ? csound->GetArgString(csound, *op->filename) : + (char *)op->filename, "dssiinit.", IsStringCode(*op->filename)); + name = legacy_name; + } + DSSI_PLUGIN *p = csound->Calloc(csound, sizeof(*p)); + p->last_block = p->rendered_until = -1; + p->capacity = GetLocalKsmps(&op->h); + p->sample_rate = csound->GetEngineSr(csound); + p->library = dlopenLADSPA(csound, name, RTLD_NOW); + const char *error = Str_noop("cannot load library"); + if (!p->library) goto fail; + DSSI_Descriptor_Function dssi = (DSSI_Descriptor_Function)dlsym(p->library, "dssi_descriptor"); + LADSPA_Descriptor_Function ladspa = (LADSPA_Descriptor_Function)dlsym(p->library, "ladspa_descriptor"); + error = Str_noop("missing descriptor or plugin index"); + if (dssi) { + p->dssi = dssi((unsigned long)*op->index); + if (!p->dssi) goto fail; + error = Str_noop("unsupported DSSI API version"); + if (p->dssi->DSSI_API_Version != 1) goto fail; + p->ladspa = p->dssi->LADSPA_Plugin; + } + else if (ladspa) p->ladspa = ladspa((unsigned long)*op->index); + error = Str_noop("invalid LADSPA descriptor"); + const LADSPA_Descriptor *d = p->ladspa; + if (!d || !d->instantiate || !d->connect_port || !d->cleanup || + !d->Label || !d->Name || d->PortCount > 65536 || + (d->PortCount && (!d->PortDescriptors || !d->PortNames || !d->PortRangeHints))) goto fail; + error = Str_noop("plugin has no render callback"); + if (!d->run && !(p->dssi && (p->dssi->run_synth || p->dssi->run_multiple_synths))) goto fail; + error = Str_noop("invalid port descriptor"); + for (unsigned long i = 0; i < d->PortCount; ++i) { + LADSPA_PortDescriptor flags = d->PortDescriptors[i]; + if (!!LADSPA_IS_PORT_AUDIO(flags) == !!LADSPA_IS_PORT_CONTROL(flags) || + !!LADSPA_IS_PORT_INPUT(flags) == !!LADSPA_IS_PORT_OUTPUT(flags) || + !d->PortNames[i]) goto fail; + } + p->handle = d->instantiate(d, (unsigned long)p->sample_rate); + error = Str_noop("cannot instantiate plugin"); + if (!p->handle) goto fail; + size_t ports = d->PortCount ? d->PortCount : 1; + p->audio = csound->Calloc(csound, ports * sizeof(*p->audio)); + p->control = csound->Calloc(csound, ports * sizeof(*p->control)); + p->mapping = csound->Malloc(csound, ports * sizeof(*p->mapping)); + p->inputs = csound->Malloc(csound, ports * sizeof(*p->inputs)); + p->outputs = csound->Malloc(csound, ports * sizeof(*p->outputs)); + for (unsigned long i = 0; i < d->PortCount; ++i) { + LADSPA_PortDescriptor flags = d->PortDescriptors[i]; + p->mapping[i] = DSSI_NONE; + if (LADSPA_IS_PORT_AUDIO(flags)) { + p->audio[i] = csound->Calloc(csound, p->capacity * sizeof(LADSPA_Data)); + d->connect_port(p->handle, i, p->audio[i]); + if (LADSPA_IS_PORT_INPUT(flags)) p->inputs[p->input_count++] = i; + else p->outputs[p->output_count++] = i; + } + else { + if (LADSPA_IS_PORT_INPUT(flags)) { + p->control[i] = default_control(p, i); + if (p->dssi && p->dssi->get_midi_controller_for_port) + p->mapping[i] = p->dssi->get_midi_controller_for_port(p->handle, i); + } + d->connect_port(p->handle, i, &p->control[i]); + } + } + h->plugins[h->count] = p; + *op->result = h->count++; + if (*op->verbose != 0) describe(csound, p); + return OK; +fail: + destroy_plugin(csound, p); + return csound->InitError(csound, "DSSI4CS: %s: %s\n", name, Str(error)); +} + +static int32_t dssiactivate_init(CSOUND *csound, DSSIACTIVATE *p) +{ + p->plugin = lookup(csound, *p->id); + return p->plugin ? OK : csound->InitError(csound, Str("DSSI4CS: invalid handle\n")); +} + +static int same_plugin(DSSI_PLUGIN *, DSSI_PLUGIN *); + +static int32_t dssiactivate(CSOUND *csound, DSSIACTIVATE *p) +{ + if (!integer(*p->trigger, 1)) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: activation must be 0 or 1\n")); + DSSI_PLUGIN *q = p->plugin; + if (*p->trigger && !q->active && q->dssi && + !q->dssi->run_synth && q->dssi->run_multiple_synths) { + DSSI_HOST *h = host(csound); + for (unsigned int i = 0; i < h->count; ++i) + if (same_plugin(q, h->plugins[i]) && + h->plugins[i]->rendered_until > block_start(&p->h)) + return csound->PerfError(csound, &p->h, + Str("DSSI4CS: activate all grouped instances before rendering their block\n")); + } + set_active(q, *p->trigger != 0); + return OK; +} + +static int has_synth(DSSI_PLUGIN *p) +{ + return p && p->dssi && (p->dssi->run_synth || p->dssi->run_multiple_synths); +} + +static int32_t synth_handle(CSOUND *csound, cs_float id, DSSI_PLUGIN **p) +{ + *p = lookup(csound, id); + return has_synth(*p) ? OK : csound->InitError(csound, Str("DSSI4CS: handle is not a DSSI synth\n")); +} + +/* Stable insertion preserves note-on/note-off order at the same sample. */ +static void enqueue(DSSI_PLUGIN *p, int64_t time, snd_seq_event_t event) +{ + unsigned int i = p->queued++; + while (i && p->queue[i-1].time > time) { + p->queue[i] = p->queue[i-1]; + --i; + } + p->queue[i].time = time; + p->queue[i].event = event; +} + +static int32_t reserve_events(CSOUND *csound, OPDS *op, DSSI_PLUGIN *p, + unsigned int count, int64_t time) +{ + if (!p->active) + return csound->PerfError(csound, op, Str("DSSI4CS: activate the synth before sending events\n")); + if (time < p->rendered_until) + return csound->PerfError(csound, op, Str("DSSI4CS: send events before rendering their block\n")); + if (count > DSSI4CS_EVENTS - p->queued) + return csound->PerfError(csound, op, Str("DSSI4CS: event queue is full\n")); + return OK; +} + +static int32_t dssinote_init(CSOUND *csound, DSSINOTE *p) +{ + return synth_handle(csound, *p->id, &p->plugin); +} + +static int32_t dssinote(CSOUND *csound, DSSINOTE *p) +{ + if (*p->trigger == 0) return OK; + int64_t now = event_time(&p->h); + cs_double samples = (cs_double)*p->duration * p->plugin->sample_rate; + if (!integer(*p->note, 127) || !integer(*p->velocity, 127) || + !integer(*p->channel, 15) || !isfinite(samples) || samples < 0 || + samples > (cs_double)(INT64_MAX / 2)) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: invalid note, velocity, channel or duration\n")); + if (reserve_events(csound, &p->h, p->plugin, *p->velocity ? 2 : 1, now)) return NOTOK; + snd_seq_event_t event = {0}; + event.type = *p->velocity ? SND_SEQ_EVENT_NOTEON : SND_SEQ_EVENT_NOTEOFF; + event.data.note.channel = (unsigned char)*p->channel; + event.data.note.note = (unsigned char)*p->note; + event.data.note.velocity = (unsigned char)*p->velocity; + enqueue(p->plugin, now, event); + if (*p->velocity) { + event.type = SND_SEQ_EVENT_NOTEOFF; + event.data.note.velocity = 0; + enqueue(p->plugin, now + (int64_t)llround(samples), event); + } + return OK; +} + +static int32_t dssinoteon_init(CSOUND *csound, DSSINOTEON *p) +{ + return synth_handle(csound, *p->id, &p->plugin); +} + +static int32_t dssinoteon(CSOUND *csound, DSSINOTEON *p) +{ + if (*p->trigger == 0) return OK; + if (!integer(*p->note, 127) || !integer(*p->velocity, 127)) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: invalid note or velocity\n")); + int64_t time = event_time(&p->h); + if (reserve_events(csound, &p->h, p->plugin, 1, time)) return NOTOK; + snd_seq_event_t event = {0}; + event.type = *p->velocity ? SND_SEQ_EVENT_NOTEON : SND_SEQ_EVENT_NOTEOFF; + event.data.note.note = (unsigned char)*p->note; + event.data.note.velocity = (unsigned char)*p->velocity; + enqueue(p->plugin, time, event); + return OK; +} + +static int32_t dssievent_init(CSOUND *csound, DSSIEVENT *p) +{ + return synth_handle(csound, *p->id, &p->plugin); +} + +static int32_t dssievent(CSOUND *csound, DSSIEVENT *p) +{ + if (*p->trigger == 0) return OK; + if (!integer(*p->channel, 15) || !integer(*p->status, 0xe0) || + !integer(*p->data1, 127) || !integer(*p->data2, 127) || + !integer(*p->offset, (int64_t)GetLocalKsmps(&p->h) - GetKsmpsOffset(&p->h) - + GetEarlySmps(&p->h) - 1)) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: invalid MIDI event or sample offset\n")); + snd_seq_event_t event = {0}; + int status = (int)*p->status, channel = (int)*p->channel; + int a = (int)*p->data1, b = (int)*p->data2; + if (status == 0x80 || status == 0x90 || status == 0xa0) { + event.type = status == 0xa0 ? SND_SEQ_EVENT_KEYPRESS : + status == 0x80 || b == 0 ? SND_SEQ_EVENT_NOTEOFF : SND_SEQ_EVENT_NOTEON; + event.data.note.channel = channel; + event.data.note.note = a; + event.data.note.velocity = b; + } + else { + event.data.control.channel = channel; + switch (status) { + case 0xb0: event.type = SND_SEQ_EVENT_CONTROLLER; event.data.control.param = a; + event.data.control.value = b; break; + case 0xc0: event.type = SND_SEQ_EVENT_PGMCHANGE; event.data.control.value = a; break; + case 0xd0: event.type = SND_SEQ_EVENT_CHANPRESS; event.data.control.value = a; break; + case 0xe0: event.type = SND_SEQ_EVENT_PITCHBEND; event.data.control.value = a + 128*b - 8192; break; + default: return csound->PerfError(csound, &p->h, Str("DSSI4CS: unsupported MIDI status\n")); + } + } + int64_t time = event_time(&p->h) + (int64_t)*p->offset; + if (reserve_events(csound, &p->h, p->plugin, 1, time)) return NOTOK; + enqueue(p->plugin, time, event); + return OK; +} + +static int32_t dssinrpn_init(CSOUND *csound, DSSINRPN *p) +{ + return synth_handle(csound, *p->id, &p->plugin); +} + +static int32_t dssinrpn(CSOUND *csound, DSSINRPN *p) +{ + if (*p->trigger == 0) return OK; + if (!integer(*p->channel, 15) || !integer(*p->parameter, 16383) || + !integer(*p->value, 16383) || + !integer(*p->offset, (int64_t)GetLocalKsmps(&p->h) - GetKsmpsOffset(&p->h) - + GetEarlySmps(&p->h) - 1)) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: invalid NRPN event\n")); + int64_t time = event_time(&p->h) + (int64_t)*p->offset; + if (reserve_events(csound, &p->h, p->plugin, 1, time)) return NOTOK; + snd_seq_event_t event = {0}; + event.type = SND_SEQ_EVENT_NONREGPARAM; + event.data.control.channel = (unsigned char)*p->channel; + event.data.control.param = (unsigned int)*p->parameter; + event.data.control.value = (int)*p->value; + enqueue(p->plugin, time, event); + return OK; +} + +static int mapped(DSSI_PLUGIN *p, snd_seq_event_t *e, int apply) +{ + if (e->type != SND_SEQ_EVENT_CONTROLLER && e->type != SND_SEQ_EVENT_NONREGPARAM) return 0; + int found = 0; + for (unsigned long i = 0; i < p->ladspa->PortCount; ++i) { + int m = p->mapping[i]; + if (m == DSSI_NONE) continue; + int match = e->type == SND_SEQ_EVENT_CONTROLLER ? + (DSSI_IS_CC(m) && DSSI_CC_NUMBER(m) == e->data.control.param) : + (DSSI_IS_NRPN(m) && DSSI_NRPN_NUMBER(m) == e->data.control.param); + if (!match) continue; + found = 1; + if (apply) { + cs_double f = e->data.control.value / + (e->type == SND_SEQ_EVENT_CONTROLLER ? 127. : 16383.); + LADSPA_PortRangeHintDescriptor flags = p->ladspa->PortRangeHints[i].HintDescriptor; + cs_double value = interpolate(p, i, f); + if (LADSPA_IS_HINT_TOGGLED(flags)) value = f >= .5; + else if (LADSPA_IS_HINT_INTEGER(flags)) value = round(value); + p->control[i] = (LADSPA_Data)value; + } + } + return found; +} + +static int host_event(DSSI_PLUGIN *p, snd_seq_event_t *e) +{ + return e->type == SND_SEQ_EVENT_PGMCHANGE || + (e->type == SND_SEQ_EVENT_CONTROLLER && + (e->data.control.param == 0 || e->data.control.param == 32)) || mapped(p, e, 0); +} + +static void apply_event(DSSI_PLUGIN *p, snd_seq_event_t *e) +{ + unsigned int ch = e->data.control.channel; + if (e->type == SND_SEQ_EVENT_PGMCHANGE) { + if (p->dssi->select_program) + p->dssi->select_program(p->handle, p->bank[ch], e->data.control.value); + } + else if (e->type == SND_SEQ_EVENT_CONTROLLER && e->data.control.param == 0) + p->bank[ch] = (p->bank[ch] & 127) | (e->data.control.value << 7); + else if (e->type == SND_SEQ_EVENT_CONTROLLER && e->data.control.param == 32) + p->bank[ch] = (p->bank[ch] & (127 << 7)) | e->data.control.value; + else mapped(p, e, 1); +} + +static int same_plugin(DSSI_PLUGIN *a, DSSI_PLUGIN *b) +{ + return a->library == b->library && !strcmp(a->ladspa->Label, b->ladspa->Label); +} + +static void connect_audio(DSSI_PLUGIN *p, uint32_t offset) +{ + for (unsigned long i = 0; i < p->ladspa->PortCount; ++i) + if (p->audio[i]) p->ladspa->connect_port(p->handle, i, p->audio[i] + offset); +} + +/* Split only at host-handled MIDI events, so controls/programs change at the + requested sample. Other MIDI retains its relative timestamp within a run. */ +static void render(DSSI_PLUGIN **group, unsigned long count, int multiple, + int64_t start, uint32_t frames, uint32_t offset) +{ + int64_t cursor = start, end = start + frames; + LADSPA_Handle handles[DSSI4CS_INSTANCES]; + snd_seq_event_t *events[DSSI4CS_INSTANCES]; + unsigned long counts[DSSI4CS_INSTANCES]; + while (cursor < end) { + int64_t next = end; + for (unsigned long g = 0; g < count; ++g) { + DSSI_PLUGIN *p = group[g]; + for (unsigned int i = 0; i < p->queued; ++i) + if (p->queue[i].time > cursor && p->queue[i].time < next && + host_event(p, &p->queue[i].event)) next = p->queue[i].time; + } + for (unsigned long g = 0; g < count; ++g) { + DSSI_PLUGIN *p = group[g]; + unsigned int used = 0; + p->event_count = 0; + if (p->panic) { + /* A plugin may omit activate/deactivate. End old notes before + delivering new notes when that instance resumes. */ + for (int channel = 0; channel < 16; ++channel) { + const unsigned int controllers[] = {64, 120, 123}; + for (unsigned int i = 0; i < 3; ++i) { + snd_seq_event_t event = {0}; + event.type = SND_SEQ_EVENT_CONTROLLER; + event.data.control.channel = channel; + event.data.control.param = controllers[i]; + if (!mapped(p, &event, 1)) p->events[p->event_count++] = event; + } + } + for (int channel = 0; channel < 16; ++channel) + for (int note = 0; note < 128; ++note) + if (p->notes[channel][note]) { + snd_seq_event_t *e = &p->events[p->event_count++]; + memset(e, 0, sizeof(*e)); + e->type = SND_SEQ_EVENT_NOTEOFF; + e->data.note.channel = channel; + e->data.note.note = note; + } + memset(p->notes, 0, sizeof(p->notes)); + p->panic = 0; + } + while (used < p->queued && p->queue[used].time < next) { + DSSI_EVENT *q = &p->queue[used++]; + if (host_event(p, &q->event)) apply_event(p, &q->event); + else { + snd_seq_event_t *e = &p->events[p->event_count++]; + *e = q->event; + if (e->type == SND_SEQ_EVENT_NOTEON || e->type == SND_SEQ_EVENT_NOTEOFF) + p->notes[e->data.note.channel][e->data.note.note] = e->type == SND_SEQ_EVENT_NOTEON; + e->time.tick = q->time > cursor ? (unsigned int)(q->time - cursor) : 0; + } + } + p->queued -= used; + memmove(p->queue, p->queue + used, p->queued * sizeof(*p->queue)); + connect_audio(p, offset + (uint32_t)(cursor - start)); + handles[g] = p->handle; + events[g] = p->events; + counts[g] = p->event_count; + } + DSSI_PLUGIN *p = group[0]; + if (multiple) + p->dssi->run_multiple_synths(count, handles, (unsigned long)(next-cursor), events, counts); + else if (has_synth(p)) + p->dssi->run_synth(p->handle, (unsigned long)(next-cursor), events[0], counts[0]); + else p->ladspa->run(p->handle, (unsigned long)(next-cursor)); + cursor = next; + } + for (unsigned long g = 0; g < count; ++g) { + group[g]->rendered_until = end; + connect_audio(group[g], 0); + } +} + +static int32_t dssiaudio_init(CSOUND *csound, DSSIAUDIO *p) +{ + p->plugin = lookup(csound, *p->id); + DSSI_PLUGIN *q = p->plugin; + if (!q) return csound->InitError(csound, Str("DSSI4CS: invalid handle\n")); + if (q->owner && q->owner != &p->h) + return csound->InitError(csound, Str("DSSI4CS: each instance needs exactly one audio renderer\n")); + if (GetLocalKsmps(&p->h) > q->capacity || + GetInputArgCnt(&p->h) - 1 > q->input_count || GetOutputArgCnt(&p->h) > q->output_count) + return csound->InitError(csound, Str("DSSI4CS: audio port count or ksmps mismatch\n")); + q->render_ksmps = GetLocalKsmps(&p->h); + q->owner = &p->h; + return OK; +} + +static int32_t dssisynth_init(CSOUND *csound, DSSIAUDIO *p) +{ + if (synth_handle(csound, *p->id, &p->plugin)) return NOTOK; + return dssiaudio_init(csound, p); +} + +static int32_t dssiaudio_deinit(CSOUND *csound, DSSIAUDIO *p) +{ + (void)csound; + if (p->plugin && p->plugin->owner == &p->h) { + p->plugin->owner = NULL; + set_active(p->plugin, 0); + } + return OK; +} + +static int32_t dssiaudio(CSOUND *csound, DSSIAUDIO *p) +{ + DSSI_PLUGIN *q = p->plugin; + uint32_t size = GetLocalKsmps(&p->h), offset = GetKsmpsOffset(&p->h); + uint32_t end = size - GetEarlySmps(&p->h); + int inputs = GetInputArgCnt(&p->h) - 1, outputs = GetOutputArgCnt(&p->h); + for (int i = 0; i < outputs; ++i) memset(p->out[i], 0, size * sizeof(cs_float)); + if (!q->active || offset >= end) return OK; + int64_t block = block_start(&p->h); + if (q->last_block != block) { + if (block + offset < q->rendered_until) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: these samples have already rendered\n")); + for (unsigned long i = 0; i < q->input_count; ++i) { + LADSPA_Data *data = q->audio[q->inputs[i]]; + memset(data, 0, size * sizeof(*data)); + if (i < (unsigned long)inputs) + for (uint32_t n = offset; n < end; ++n) + data[n] = (LADSPA_Data)(p->in[i][n] / csound->Get0dBFS(csound)); + } + DSSI_PLUGIN *group[DSSI4CS_INSTANCES] = {q}; + unsigned long count = 1; + int multiple = has_synth(q) && !q->dssi->run_synth; + if (multiple) { + DSSI_HOST *h = host(csound); + for (unsigned int i = 0; i < h->count; ++i) { + DSSI_PLUGIN *other = h->plugins[i]; + if (other != q && other->active && same_plugin(q, other)) { + if (other->rendered_until > block + offset) + return csound->PerfError(csound, &p->h, + Str("DSSI4CS: activate all grouped instances before rendering their block\n")); + group[count++] = other; + } + } + if (count > 1) { + for (unsigned long i = 0; i < count; ++i) + if (group[i]->input_count || group[i]->render_ksmps != size || + !group[i]->owner || GetKsmpsOffset(group[i]->owner) || + GetEarlySmps(group[i]->owner) || offset || end != size) + return csound->PerfError(csound, &p->h, + Str("DSSI4CS: grouped synths need no audio inputs, matching ksmps and full blocks\n")); + } + } + render(group, count, multiple, block + offset, end - offset, offset); + for (unsigned long i = 0; i < count; ++i) group[i]->last_block = block; + } + for (int i = 0; i < outputs; ++i) + for (uint32_t n = offset; n < end; ++n) + p->out[i][n] = q->audio[q->outputs[i]][n] * csound->Get0dBFS(csound); + return OK; +} + +static int control_port(DSSI_PLUGIN *p, cs_float port, int input) +{ + if (!p || !p->ladspa->PortCount || + !integer(port, (int64_t)(p->ladspa->PortCount - 1))) return 0; + LADSPA_PortDescriptor flags = p->ladspa->PortDescriptors[(unsigned long)port]; + return LADSPA_IS_PORT_CONTROL(flags) && (!input || LADSPA_IS_PORT_INPUT(flags)); +} + +static int32_t dssictls_init(CSOUND *csound, DSSICTLS *p) +{ + p->plugin = lookup(csound, *p->id); + if (!control_port(p->plugin, *p->port, 1)) + return csound->InitError(csound, Str("DSSI4CS: expected an input control port\n")); + p->index = (unsigned long)*p->port; + return OK; +} + +static int32_t dssictls(CSOUND *csound, DSSICTLS *p) +{ + if (*p->trigger == 0) return OK; + cs_double value = *p->value; + if (LADSPA_IS_HINT_SAMPLE_RATE(p->plugin->ladspa->PortRangeHints[p->index].HintDescriptor)) + value *= p->plugin->sample_rate; + if (!isfinite(value) || fabs(value) > FLT_MAX) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: invalid control value\n")); + p->plugin->control[p->index] = (LADSPA_Data)value; + return OK; +} + +static int32_t dssiget_init(CSOUND *csound, DSSIGET *p) +{ + p->plugin = lookup(csound, *p->id); + if (!control_port(p->plugin, *p->port, 0)) + return csound->InitError(csound, Str("DSSI4CS: expected a control port\n")); + p->index = (unsigned long)*p->port; + *p->value = p->plugin->control[p->index]; + return OK; +} + +static int32_t dssiget(CSOUND *csound, DSSIGET *p) +{ + (void)csound; + *p->value = p->plugin->control[p->index]; + return OK; +} + +static int32_t dssiprogram_init(CSOUND *csound, DSSIPROGRAM *p) +{ + p->plugin = lookup(csound, *p->id); + if (!p->plugin || !p->plugin->dssi || !p->plugin->dssi->select_program) + return csound->InitError(csound, Str("DSSI4CS: plugin does not support programs\n")); + return OK; +} + +static int32_t dssiprogram(CSOUND *csound, DSSIPROGRAM *p) +{ + if (*p->trigger == 0) return OK; + if (!integer(*p->bank, UINT32_MAX) || !integer(*p->program, UINT32_MAX)) + return csound->PerfError(csound, &p->h, Str("DSSI4CS: invalid bank or program\n")); + p->plugin->dssi->select_program(p->plugin->handle, (unsigned long)*p->bank, + (unsigned long)*p->program); + return OK; +} + +static int32_t dssiconfigure(CSOUND *csound, DSSICONFIGURE *p) +{ + DSSI_PLUGIN *q = lookup(csound, *p->id); + if (!q || !q->dssi || !q->dssi->configure) + return csound->InitError(csound, Str("DSSI4CS: plugin does not support configuration\n")); + DSSI_HOST *h = host(csound); + int global = !strncmp(p->key->data, DSSI_GLOBAL_CONFIGURE_PREFIX, + strlen(DSSI_GLOBAL_CONFIGURE_PREFIX)); + for (unsigned int i = 0; i < h->count; ++i) { + DSSI_PLUGIN *target = h->plugins[i]; + if (target != q && !(global && same_plugin(q, target))) continue; + char *error = target->dssi->configure(target->handle, p->key->data, p->value->data); + if (error) { + int32_t result = csound->InitError(csound, "DSSI4CS: configure: %s\n", error); + free(error); /* DSSI specifies malloc/free ownership for this string. */ + return result; + } + } + return OK; +} + +static int32_t dssiprograminfo(CSOUND *csound, DSSIPROGRAMINFO *p) +{ + DSSI_PLUGIN *q = lookup(csound, *p->id); + if (!q || !q->dssi || !q->dssi->get_program || !integer(*p->index, UINT32_MAX)) + return csound->InitError(csound, Str("DSSI4CS: cannot enumerate programs\n")); + const DSSI_Program_Descriptor *program = q->dssi->get_program(q->handle, (unsigned long)*p->index); + const char *name = program && program->Name ? program->Name : ""; + size_t size = strlen(name) + 1; + if (p->name->size < size) { + p->name->data = csound->ReAlloc(csound, p->name->data, size); + p->name->size = size; + } + memcpy(p->name->data, name, size); + *p->bank = program ? (cs_float)program->Bank : -1; + *p->program = program ? (cs_float)program->Program : -1; + return OK; +} + +static int32_t dssiinfo(CSOUND *csound, DSSIINFO *p) +{ + DSSI_PLUGIN *q = lookup(csound, *p->id); + if (!q) return csound->InitError(csound, Str("DSSI4CS: invalid handle\n")); + describe(csound, q); + return OK; +} + +static void list_directory(CSOUND *csound, const char *directory) +{ + DIR *dir = opendir(directory); + if (!dir) return; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + size_t size = strlen(directory) + strlen(entry->d_name) + 2; + char *path = csound->Malloc(csound, size); + snprintf(path, size, "%s/%s", directory, entry->d_name); + void *library = dlopen(path, RTLD_LAZY | RTLD_LOCAL); + if (library) { + DSSI_Descriptor_Function df = (DSSI_Descriptor_Function)dlsym(library, "dssi_descriptor"); + LADSPA_Descriptor_Function lf = (LADSPA_Descriptor_Function)dlsym(library, "ladspa_descriptor"); + for (unsigned long i = 0; df || lf; ++i) { + const DSSI_Descriptor *d = df ? df(i) : NULL; + const LADSPA_Descriptor *l = df ? (d ? d->LADSPA_Plugin : NULL) : lf(i); + if (!l) break; + csound->Message(csound, "%s: %lu: %s (%s)\n", path, i, l->Name, l->Label); + } + dlclose(library); + } + csound->Free(csound, path); + } + closedir(dir); +} + +static int32_t dssilist(CSOUND *csound, DSSILIST *p) +{ + (void)p; + const char *paths[] = {csound->GetEnv(csound, "DSSI_PATH"), + csound->GetEnv(csound, "LADSPA_PATH"), DSSI4CS_DEFAULT_PATH}; + for (unsigned int i = 0; i < sizeof(paths)/sizeof(paths[0]); ++i) { + const char *start = paths[i]; + while (start && *start) { + const char *end = strchr(start, ':'); + size_t length = end ? (size_t)(end-start) : strlen(start); + if (length) { + char *path = csound->Malloc(csound, length+1); + memcpy(path, start, length); + path[length] = '\0'; + list_directory(csound, path); + csound->Free(csound, path); + } + start = end ? end+1 : NULL; + } + } + return OK; +} + +static OENTRY localops[] = { + {"dssiinit", sizeof(DSSIINIT), 0, "i", "Tip", (SUBR)dssiinit}, + {"dssiactivate", sizeof(DSSIACTIVATE), 0, "", "ik", (SUBR)dssiactivate_init, (SUBR)dssiactivate}, + {"dssiaudio", sizeof(DSSIAUDIO), 0, "mmmmmmmmm", "iyyyyyyyyy", (SUBR)dssiaudio_init, (SUBR)dssiaudio, (SUBR)dssiaudio_deinit}, + {"dssisynth", sizeof(DSSIAUDIO), 0, "mmmmmmmmm", "iyyyyyyyyy", (SUBR)dssisynth_init, (SUBR)dssiaudio, (SUBR)dssiaudio_deinit}, + {"dssictls", sizeof(DSSICTLS), 0, "", "iikk", (SUBR)dssictls_init, (SUBR)dssictls}, + {"dssiget", sizeof(DSSIGET), 0, "k", "ii", (SUBR)dssiget_init, (SUBR)dssiget}, + {"dssinote", sizeof(DSSINOTE), 0, "", "kikkko", (SUBR)dssinote_init, (SUBR)dssinote}, + {"dssievent", sizeof(DSSINOTEON), 0, "", "kikk", (SUBR)dssinoteon_init, (SUBR)dssinoteon}, + {"dssievent", sizeof(DSSIEVENT), 0, "", "kikkkkO", (SUBR)dssievent_init, (SUBR)dssievent}, + {"dssinrpn", sizeof(DSSINRPN), 0, "", "kikkkO", (SUBR)dssinrpn_init, (SUBR)dssinrpn}, + {"dssiprogram", sizeof(DSSIPROGRAM), 0, "", "ikkk", (SUBR)dssiprogram_init, (SUBR)dssiprogram}, + {"dssiconfigure", sizeof(DSSICONFIGURE), 0, "", "iSS", (SUBR)dssiconfigure}, + {"dssiprograminfo", sizeof(DSSIPROGRAMINFO), 0, "Sii", "ii", (SUBR)dssiprograminfo}, + {"dssiinfo", sizeof(DSSIINFO), 0, "", "i", (SUBR)dssiinfo}, + {"dssilist", sizeof(DSSILIST), 0, "", "", (SUBR)dssilist} +}; +LINKAGE diff --git a/7.x/src/dssi4cs/dssi4cs.h b/7.x/src/dssi4cs/dssi4cs.h new file mode 100644 index 0000000..c83c083 --- /dev/null +++ b/7.x/src/dssi4cs/dssi4cs.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2005 Andres Cabrera + * The dssi4cs 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. + * + * The dssi4cs 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 The dssi4cs library; if not, write to the Free Software + * Foundation, Inc., 31 Milk Street, #960789, Boston, MA, 02196, USA + */ + +#ifndef DSSI4CS_H +#define DSSI4CS_H +#include "csdl.h" +#include +#include + +#define DSSI4CS_CHANNELS 9 +#define DSSI4CS_EVENTS 1024 +#define DSSI4CS_INSTANCES 256 + +typedef struct { + int64_t time; + snd_seq_event_t event; +} DSSI_EVENT; + +typedef struct DSSI_PLUGIN_ { + const LADSPA_Descriptor *ladspa; + const DSSI_Descriptor *dssi; + void *library; + LADSPA_Handle handle; + LADSPA_Data *control; + LADSPA_Data **audio; + int *mapping; + unsigned long *inputs, *outputs; + unsigned long input_count, output_count; + uint32_t capacity, render_ksmps; + cs_float sample_rate; + int active; + OPDS *owner; + int64_t last_block, rendered_until; + DSSI_EVENT queue[DSSI4CS_EVENTS]; + unsigned int queued; + snd_seq_event_t events[DSSI4CS_EVENTS + 16 * (128 + 3)]; + unsigned char notes[16][128]; + int panic; + unsigned long event_count; + unsigned int bank[16]; +} DSSI_PLUGIN; + +typedef struct { + unsigned int count; + DSSI_PLUGIN *plugins[DSSI4CS_INSTANCES]; +} DSSI_HOST; + +typedef struct { + OPDS h; + cs_float *result, *filename, *index, *verbose; +} DSSIINIT; + +typedef struct { + OPDS h; + cs_float *id, *trigger; + DSSI_PLUGIN *plugin; +} DSSIACTIVATE; + +typedef struct { + OPDS h; + cs_float *out[DSSI4CS_CHANNELS]; + cs_float *id, *in[DSSI4CS_CHANNELS]; + DSSI_PLUGIN *plugin; +} DSSIAUDIO; + +typedef struct { + OPDS h; + cs_float *id, *port, *value, *trigger; + DSSI_PLUGIN *plugin; + unsigned long index; +} DSSICTLS; + +typedef struct { + OPDS h; + cs_float *value, *id, *port; + DSSI_PLUGIN *plugin; + unsigned long index; +} DSSIGET; + +typedef struct { + OPDS h; + cs_float *trigger, *id, *note, *velocity, *duration, *channel; + DSSI_PLUGIN *plugin; +} DSSINOTE; + +typedef struct { + OPDS h; + cs_float *trigger, *id, *status, *channel, *data1, *data2, *offset; + DSSI_PLUGIN *plugin; +} DSSIEVENT; + +typedef struct { + OPDS h; + cs_float *trigger, *id, *channel, *parameter, *value, *offset; + DSSI_PLUGIN *plugin; +} DSSINRPN; + +typedef struct { + OPDS h; + cs_float *trigger, *id, *note, *velocity; + DSSI_PLUGIN *plugin; +} DSSINOTEON; + +typedef struct { + OPDS h; + cs_float *id, *bank, *program, *trigger; + DSSI_PLUGIN *plugin; +} DSSIPROGRAM; + +typedef struct { + OPDS h; + cs_float *id; + STRINGDAT *key, *value; +} DSSICONFIGURE; + +typedef struct { + OPDS h; + STRINGDAT *name; + cs_float *bank, *program, *id, *index; +} DSSIPROGRAMINFO; + +typedef struct { OPDS h; cs_float *id; } DSSIINFO; +typedef struct { OPDS h; } DSSILIST; +#endif diff --git a/7.x/src/dssi4cs/include/dssi/LICENSE b/7.x/src/dssi4cs/include/dssi/LICENSE new file mode 100644 index 0000000..6a6fbce --- /dev/null +++ b/7.x/src/dssi4cs/include/dssi/LICENSE @@ -0,0 +1,509 @@ + +*** NOTE: This license applies to the DSSI header file. See README +*** for a summary of the licensing of other files, and the individual +*** source code for more details. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/7.x/src/dssi4cs/include/dssi/README.md b/7.x/src/dssi4cs/include/dssi/README.md new file mode 100644 index 0000000..99103b8 --- /dev/null +++ b/7.x/src/dssi4cs/include/dssi/README.md @@ -0,0 +1,18 @@ +# DSSI + +Unmodified files from [release 1.1.1](https://downloads.sourceforge.net/project/dssi/dssi/1.1.1/dssi-1.1.1.tar.gz). + +Only the DSSI API header is vendored. +The header uses LGPL-2.1-or-later; see [LICENSE](LICENSE) and its license notice. + +[SOURCE.json](SOURCE.json) records the archive URL, SHA-256, and file paths. +Keep platform adapters in the plugin's own sources, outside this directory. + +Run `./update.py` for the latest stable release, `./update.py VERSION` for a +specific release, or `./update.py --pinned` to reproduce this copy and check its +archive hash. Downloads and extraction finish before checked-in files change. +The script requires curl and Python 3 and works from any directory. + +After an update, review the source and license diff, rebuild the DSSI/LADSPA +plugin on Linux, and verify that a native synth or effect still loads. Commit +the header, license, README, and SOURCE.json together. diff --git a/7.x/src/dssi4cs/include/dssi/SOURCE.json b/7.x/src/dssi4cs/include/dssi/SOURCE.json new file mode 100644 index 0000000..e91e398 --- /dev/null +++ b/7.x/src/dssi4cs/include/dssi/SOURCE.json @@ -0,0 +1,9 @@ +{ + "version": "1.1.1", + "url": "https://downloads.sourceforge.net/project/dssi/dssi/1.1.1/dssi-1.1.1.tar.gz", + "sha256": "f2c82b073a947c8255284249097667f9b14e660bf86186f3fcd3b3b3e087814e", + "files": { + "dssi.h": "dssi-1.1.1/dssi/dssi.h", + "LICENSE": "dssi-1.1.1/COPYING" + } +} diff --git a/7.x/src/dssi4cs/include/dssi/dssi.h b/7.x/src/dssi4cs/include/dssi/dssi.h new file mode 100644 index 0000000..fe4a20b --- /dev/null +++ b/7.x/src/dssi4cs/include/dssi/dssi.h @@ -0,0 +1,426 @@ +/* -*- c-basic-offset: 4 -*- */ + +/* dssi.h + + DSSI version 1.0 + Copyright (c) 2004, 2009 Chris Cannam, Steve Harris and Sean Bolton + + 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 DSSI_INCLUDED +#define DSSI_INCLUDED + +#include +#include + +#define DSSI_VERSION "1.0" +#define DSSI_VERSION_MAJOR 1 +#define DSSI_VERSION_MINOR 0 + +#ifdef __cplusplus +extern "C" { +#endif + +/* + There is a need for an API that supports hosted MIDI soft synths + with GUIs in Linux audio applications. In time the GMPI initiative + should comprehensively address this need, but the requirement for + Linux applications to be able to support simple hosted synths is + here now, and GMPI is not. This proposal (the "DSSI Soft Synth + Interface" or DSSI, pronounced "dizzy") aims to provide a simple + solution in a way that we hope will prove complete and compelling + enough to support now, yet not so compelling as to supplant GMPI or + any other comprehensive future proposal. + + For simplicity and familiarity, this API is based as far as + possible on existing work -- the LADSPA plugin API for control + values and audio processing, and the ALSA sequencer event types for + MIDI event communication. The GUI part of the proposal is quite + new, but may also be applicable retroactively to LADSPA plugins + that do not otherwise support this synth interface. +*/ + +typedef struct _DSSI_Program_Descriptor { + + /** Bank number for this program. Note that DSSI does not support + MIDI-style separation of bank LSB and MSB values. There is no + restriction on the set of available banks: the numbers do not + need to be contiguous, there does not need to be a bank 0, etc. */ + unsigned long Bank; + + /** Program number (unique within its bank) for this program. + There is no restriction on the set of available programs: the + numbers do not need to be contiguous, there does not need to + be a program 0, etc. */ + unsigned long Program; + + /** Name of the program. */ + const char * Name; + +} DSSI_Program_Descriptor; + + +typedef struct _DSSI_Descriptor { + + /** + * DSSI_API_Version + * + * This member indicates the DSSI API level used by this plugin. + * If we're lucky, this will never be needed. For now all plugins + * must set it to 1. + */ + int DSSI_API_Version; + + /** + * LADSPA_Plugin + * + * A DSSI synth plugin consists of a LADSPA plugin plus an + * additional framework for controlling program settings and + * transmitting MIDI events. A plugin must fully implement the + * LADSPA descriptor fields as well as the required LADSPA + * functions including instantiate() and (de)activate(). It + * should also implement run(), with the same behaviour as if + * run_synth() (below) were called with no synth events. + * + * In order to instantiate a synth the host calls the LADSPA + * instantiate function, passing in this LADSPA_Descriptor + * pointer. The returned LADSPA_Handle is used as the argument + * for the DSSI functions below as well as for the LADSPA ones. + */ + const LADSPA_Descriptor *LADSPA_Plugin; + + /** + * configure() + * + * This member is a function pointer that sends a piece of + * configuration data to the plugin. The key argument specifies + * some aspect of the synth's configuration that is to be changed, + * and the value argument specifies a new value for it. A plugin + * that does not require this facility at all may set this member + * to NULL. + * + * This call is intended to set some session-scoped aspect of a + * plugin's behaviour, for example to tell the plugin to load + * sample data from a particular file. The plugin should act + * immediately on the request. The call should return NULL on + * success, or an error string that may be shown to the user. The + * host will free the returned value after use if it is non-NULL. + * + * Calls to configure() are not automated as timed events. + * Instead, a host should remember the last value associated with + * each key passed to configure() during a given session for a + * given plugin instance, and should call configure() with the + * correct value for each key the next time it instantiates the + * "same" plugin instance, for example on reloading a project in + * which the plugin was used before. Plugins should note that a + * host may typically instantiate a plugin multiple times with the + * same configuration values, and should share data between + * instances where practical. + * + * Calling configure() completely invalidates the program and bank + * information last obtained from the plugin. + * + * Reserved and special key prefixes + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * The DSSI: prefix + * ---------------- + * Configure keys starting with DSSI: are reserved for particular + * purposes documented in the DSSI specification. At the moment, + * there is one such key: DSSI:PROJECT_DIRECTORY. A host may call + * configure() passing this key and a directory path value. This + * indicates to the plugin and its UI that a directory at that + * path exists and may be used for project-local data. Plugins + * may wish to use the project directory as a fallback location + * when looking for other file data, or as a base for relative + * paths in other configuration values. + * + * The GLOBAL: prefix + * ------------------ + * Configure keys starting with GLOBAL: may be used by the plugin + * and its UI for any purpose, but are treated specially by the + * host. When one of these keys is used in a configure OSC call + * from the plugin UI, the host makes the corresponding configure + * call (preserving the GLOBAL: prefix) not only to the target + * plugin but also to all other plugins in the same instance + * group, as well as their UIs. Note that if any instance + * returns non-NULL from configure to indicate error, the host + * may stop there (and the set of plugins on which configure has + * been called will thus depend on the host implementation). + * See also the configure OSC call documentation in RFC.txt. + */ + char *(*configure)(LADSPA_Handle Instance, + const char *Key, + const char *Value); + + #define DSSI_RESERVED_CONFIGURE_PREFIX "DSSI:" + #define DSSI_GLOBAL_CONFIGURE_PREFIX "GLOBAL:" + #define DSSI_PROJECT_DIRECTORY_KEY \ + DSSI_RESERVED_CONFIGURE_PREFIX "PROJECT_DIRECTORY" + + /** + * get_program() + * + * This member is a function pointer that provides a description + * of a program (named preset sound) available on this synth. A + * plugin that does not support programs at all should set this + * member to NULL. + * + * The Index argument is an index into the plugin's list of + * programs, not a program number as represented by the Program + * field of the DSSI_Program_Descriptor. (This distinction is + * needed to support synths that use non-contiguous program or + * bank numbers.) + * + * This function returns a DSSI_Program_Descriptor pointer that is + * guaranteed to be valid only until the next call to get_program, + * deactivate, or configure, on the same plugin instance. This + * function must return NULL if passed an Index argument out of + * range, so that the host can use it to query the number of + * programs as well as their properties. + */ + const DSSI_Program_Descriptor *(*get_program)(LADSPA_Handle Instance, + unsigned long Index); + + /** + * select_program() + * + * This member is a function pointer that selects a new program + * for this synth. The program change should take effect + * immediately at the start of the next run_synth() call. (This + * means that a host providing the capability of changing programs + * between any two notes on a track must vary the block size so as + * to place the program change at the right place. A host that + * wanted to avoid this would probably just instantiate a plugin + * for each program.) + * + * A plugin that does not support programs at all should set this + * member NULL. Plugins should ignore a select_program() call + * with an invalid bank or program. + * + * A plugin is not required to select any particular default + * program on activate(): it's the host's duty to set a program + * explicitly. The current program is invalidated by any call to + * configure(). + * + * A plugin is permitted to re-write the values of its input + * control ports when select_program is called. The host should + * re-read the input control port values and update its own + * records appropriately. (This is the only circumstance in + * which a DSSI plugin is allowed to modify its own input ports.) + */ + void (*select_program)(LADSPA_Handle Instance, + unsigned long Bank, + unsigned long Program); + + /** + * get_midi_controller_for_port() + * + * This member is a function pointer that returns the MIDI + * controller number or NRPN that should be mapped to the given + * input control port. If the given port should not have any MIDI + * controller mapped to it, the function should return DSSI_NONE. + * The behaviour of this function is undefined if the given port + * number does not correspond to an input control port. A plugin + * that does not want MIDI controllers mapped to ports at all may + * set this member NULL. + * + * Correct values can be got using the macros DSSI_CC(num) and + * DSSI_NRPN(num) as appropriate, and values can be combined using + * bitwise OR: e.g. DSSI_CC(23) | DSSI_NRPN(1069) means the port + * should respond to CC #23 and NRPN #1069. + * + * The host is responsible for doing proper scaling from MIDI + * controller and NRPN value ranges to port ranges according to + * the plugin's LADSPA port hints. Hosts should not deliver + * through run_synth any MIDI controller events that have already + * been mapped to control port values. + * + * A plugin should not attempt to request mappings from + * controllers 0 or 32 (MIDI Bank Select MSB and LSB). + */ + int (*get_midi_controller_for_port)(LADSPA_Handle Instance, + unsigned long Port); + + /** + * run_synth() + * + * This member is a function pointer that runs a synth for a + * block. This is identical in function to the LADSPA run() + * function, except that it also supplies events to the synth. + * + * A plugin may provide this function, run_multiple_synths() (see + * below), both, or neither (if it is not in fact a synth). A + * plugin that does not provide this function must set this member + * to NULL. Authors of synth plugins are encouraged to provide + * this function if at all possible. + * + * The Events pointer points to a block of EventCount ALSA + * sequencer events, which is used to communicate MIDI and related + * events to the synth. Each event is timestamped relative to the + * start of the block, (mis)using the ALSA "tick time" field as a + * frame count. The host is responsible for ensuring that events + * with differing timestamps are already ordered by time. + * + * See also the notes on activation, port connection etc in + * ladpsa.h, in the context of the LADSPA run() function. + * + * Note Events + * ~~~~~~~~~~~ + * There are two minor requirements aimed at making the plugin + * writer's life as simple as possible: + * + * 1. A host must never send events of type SND_SEQ_EVENT_NOTE. + * Notes should always be sent as separate SND_SEQ_EVENT_NOTE_ON + * and NOTE_OFF events. A plugin should discard any one-point + * NOTE events it sees. + * + * 2. A host must not attempt to switch notes off by sending + * zero-velocity NOTE_ON events. It should always send true + * NOTE_OFFs. It is the host's responsibility to remap events in + * cases where an external MIDI source has sent it zero-velocity + * NOTE_ONs. + * + * Bank and Program Events + * ~~~~~~~~~~~~~~~~~~~~~~~ + * Hosts must map MIDI Bank Select MSB and LSB (0 and 32) + * controllers and MIDI Program Change events onto the banks and + * programs specified by the plugin, using the DSSI select_program + * call. No host should ever deliver a program change or bank + * select controller to a plugin via run_synth. + */ + void (*run_synth)(LADSPA_Handle Instance, + unsigned long SampleCount, + snd_seq_event_t *Events, + unsigned long EventCount); + + /** + * run_synth_adding() + * + * This member is a function pointer that runs an instance of a + * synth for a block, adding its outputs to the values already + * present at the output ports. This is provided for symmetry + * with LADSPA run_adding(), and is equally optional. A plugin + * that does not provide it must set this member to NULL. + */ + void (*run_synth_adding)(LADSPA_Handle Instance, + unsigned long SampleCount, + snd_seq_event_t *Events, + unsigned long EventCount); + + /** + * run_multiple_synths() + * + * This member is a function pointer that runs multiple synth + * instances for a block. This is very similar to run_synth(), + * except that Instances, Events, and EventCounts each point to + * arrays that hold the LADSPA handles, event buffers, and + * event counts for each of InstanceCount instances. That is, + * Instances points to an array of InstanceCount pointers to + * DSSI plugin instantiations, Events points to an array of + * pointers to each instantiation's respective event list, and + * EventCounts points to an array containing each instantiation's + * respective event count. + * + * A host using this function must guarantee that ALL active + * instances of the plugin are represented in each call to the + * function -- that is, a host may not call run_multiple_synths() + * for some instances of a given plugin and then call run_synth() + * as well for others. 'All .. instances of the plugin' means + * every instance sharing the same LADSPA label and shared object + * (*.so) file (rather than every instance sharing the same *.so). + * 'Active' means any instance for which activate() has been called + * but deactivate() has not. + * + * A plugin may provide this function, run_synths() (see above), + * both, or neither (if it not in fact a synth). A plugin that + * does not provide this function must set this member to NULL. + * Plugin authors implementing run_multiple_synths are strongly + * encouraged to implement run_synth as well if at all possible, + * to aid simplistic hosts, even where it would be less efficient + * to use it. + */ + void (*run_multiple_synths)(unsigned long InstanceCount, + LADSPA_Handle *Instances, + unsigned long SampleCount, + snd_seq_event_t **Events, + unsigned long *EventCounts); + + /** + * run_multiple_synths_adding() + * + * This member is a function pointer that runs multiple synth + * instances for a block, adding each synth's outputs to the + * values already present at the output ports. This is provided + * for symmetry with both the DSSI run_multiple_synths() and LADSPA + * run_adding() functions, and is equally optional. A plugin + * that does not provide it must set this member to NULL. + */ + void (*run_multiple_synths_adding)(unsigned long InstanceCount, + LADSPA_Handle *Instances, + unsigned long SampleCount, + snd_seq_event_t **Events, + unsigned long *EventCounts); +} DSSI_Descriptor; + +/** + * DSSI supports a plugin discovery method similar to that of LADSPA: + * + * - DSSI hosts may wish to locate DSSI plugin shared object files by + * searching the paths contained in the DSSI_PATH and LADSPA_PATH + * environment variables, if they are present. Both are expected + * to be colon-separated lists of directories to be searched (in + * order), and DSSI_PATH should be searched first if both variables + * are set. + * + * - Each shared object file containing DSSI plugins must include a + * function dssi_descriptor(), with the following function prototype + * and C-style linkage. Hosts may enumerate the plugin types + * available in the shared object file by repeatedly calling + * this function with successive Index values (beginning from 0), + * until a return value of NULL indicates no more plugin types are + * available. Each non-NULL return is the DSSI_Descriptor + * of a distinct plugin type. + */ + +const DSSI_Descriptor *dssi_descriptor(unsigned long Index); + +typedef const DSSI_Descriptor *(*DSSI_Descriptor_Function)(unsigned long Index); + +/* + * Macros to specify particular MIDI controllers in return values from + * get_midi_controller_for_port() + */ + +#define DSSI_CC_BITS 0x20000000 +#define DSSI_NRPN_BITS 0x40000000 + +#define DSSI_NONE -1 +#define DSSI_CONTROLLER_IS_SET(n) (DSSI_NONE != (n)) + +#define DSSI_CC(n) (DSSI_CC_BITS | (n)) +#define DSSI_IS_CC(n) (DSSI_CC_BITS & (n)) +#define DSSI_CC_NUMBER(n) ((n) & 0x7f) + +#define DSSI_NRPN(n) (DSSI_NRPN_BITS | ((n) << 7)) +#define DSSI_IS_NRPN(n) (DSSI_NRPN_BITS & (n)) +#define DSSI_NRPN_NUMBER(n) (((n) >> 7) & 0x3fff) + +#ifdef __cplusplus +} +#endif + +#endif /* DSSI_INCLUDED */ diff --git a/7.x/src/dssi4cs/include/dssi/update.py b/7.x/src/dssi4cs/include/dssi/update.py new file mode 100755 index 0000000..6ab2551 --- /dev/null +++ b/7.x/src/dssi4cs/include/dssi/update.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +# Run from any directory. With no argument, select the latest numbered stable +# release. A version selects that release; --pinned reproduces SOURCE.json. +# Requires curl and Python 3. No upstream build scripts are run. +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +import tarfile +import tempfile + +source_dir = Path(__file__).resolve().parent +if len(sys.argv) > 2 or (len(sys.argv) == 2 and sys.argv[1] == '--help'): + print(f'Usage: {sys.argv[0]} [VERSION|--pinned]') + sys.exit(0 if len(sys.argv) == 2 else 1) +selection = sys.argv[1] if len(sys.argv) == 2 else 'latest' + +def fetch(url, destination): + subprocess.run(['curl', '--fail', '--location', '--silent', '--show-error', + '--retry', '3', '--connect-timeout', '20', '--max-time', '120', + url, '-o', str(destination)], check=True) + +with tempfile.TemporaryDirectory(prefix='csound-dssi-') as temporary: + temporary = Path(temporary) + recorded = None + if selection == '--pinned': + recorded = json.loads((source_dir / 'SOURCE.json').read_text()) + version = recorded['version'] + elif selection == 'latest': + listing = temporary / 'releases.html' + fetch('https://sourceforge.net/projects/dssi/files/dssi/', listing) + versions = re.findall('/dssi/(\\d+(?:\\.\\d+)+)/', listing.read_text()) + if not versions: + sys.exit('No stable release found; checked-in files have not changed') + version = max(versions, key=lambda value: tuple(map(int, value.split('.')))) + else: + version = selection + if not re.fullmatch(r'\d+(?:\.\d+)+', version): + sys.exit('Expected a numbered release, for example 1.2.3') + + url = 'https://downloads.sourceforge.net/project/dssi/dssi/{version}/dssi-{version}.tar.gz'.format(version=version) + archive = temporary / 'source.tar' + fetch(url, archive) + digest = hashlib.sha256(archive.read_bytes()).hexdigest() + if recorded and digest != recorded['sha256']: + sys.exit('Archive differs from SOURCE.json; checked-in files have not changed') + files = {'dssi.h': 'dssi-{version}/dssi/dssi.h', 'LICENSE': 'dssi-{version}/COPYING'} + staged = {} + # Read only named files. Preserve upstream bytes, including license notices. + with tarfile.open(archive) as package: + for destination, member in files.items(): + entry = package.getmember(member.format(version=version)) + if not entry.isfile(): + sys.exit(f'Expected a file: {entry.name}') + staged[destination] = package.extractfile(entry).read() + metadata = {'version': version, 'url': url, 'sha256': digest, + 'files': {name: member.format(version=version) for name, member in files.items()}} + staged['SOURCE.json'] = (json.dumps(metadata, indent=2) + '\n').encode() + readme = f"""# DSSI + +Unmodified files from [release {version}]({url}). + +Only the DSSI API header is vendored. +The header uses LGPL-2.1-or-later; see [LICENSE](LICENSE) and its license notice. + +[SOURCE.json](SOURCE.json) records the archive URL, SHA-256, and file paths. +Keep platform adapters in the plugin's own sources, outside this directory. + +Run `./update.py` for the latest stable release, `./update.py VERSION` for a +specific release, or `./update.py --pinned` to reproduce this copy and check its +archive hash. Downloads and extraction finish before checked-in files change. +The script requires curl and Python 3 and works from any directory. + +After an update, review the source and license diff, rebuild the DSSI/LADSPA +plugin on Linux, and verify that a native synth or effect still loads. Commit +the header, license, README, and SOURCE.json together. +""" + staged['README.md'] = readme.encode() + # A failed download or missing archive member leaves the old copy intact. + for name, data in staged.items(): + destination = source_dir / name + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(data) + print(f'Updated DSSI to {version}. Source: {url}') diff --git a/7.x/src/dssi4cs/include/ladspa/LICENSE b/7.x/src/dssi4cs/include/ladspa/LICENSE new file mode 100644 index 0000000..07d2ddf --- /dev/null +++ b/7.x/src/dssi4cs/include/ladspa/LICENSE @@ -0,0 +1,514 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. +^L + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. +^L + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. +^L + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. +^L + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. +^L + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. +^L + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. +^L + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS +^L + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper +mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/7.x/src/dssi4cs/include/ladspa/README.md b/7.x/src/dssi4cs/include/ladspa/README.md new file mode 100644 index 0000000..8722ac6 --- /dev/null +++ b/7.x/src/dssi4cs/include/ladspa/README.md @@ -0,0 +1,18 @@ +# LADSPA + +Unmodified files from [release 1.17](https://www.ladspa.org/download/ladspa_sdk_1.17.tgz). + +Only the LADSPA API header is vendored. +The header uses LGPL-2.1-or-later; see [LICENSE](LICENSE) and its license notice. + +[SOURCE.json](SOURCE.json) records the archive URL, SHA-256, and file paths. +Keep platform adapters in the plugin's own sources, outside this directory. + +Run `./update.py` for the latest stable release, `./update.py VERSION` for a +specific release, or `./update.py --pinned` to reproduce this copy and check its +archive hash. Downloads and extraction finish before checked-in files change. +The script requires curl and Python 3 and works from any directory. + +After an update, review the source and license diff, rebuild the DSSI/LADSPA +plugin on Linux, and verify that a native synth or effect still loads. Commit +the header, license, README, and SOURCE.json together. diff --git a/7.x/src/dssi4cs/include/ladspa/SOURCE.json b/7.x/src/dssi4cs/include/ladspa/SOURCE.json new file mode 100644 index 0000000..06fcef5 --- /dev/null +++ b/7.x/src/dssi4cs/include/ladspa/SOURCE.json @@ -0,0 +1,9 @@ +{ + "version": "1.17", + "url": "https://www.ladspa.org/download/ladspa_sdk_1.17.tgz", + "sha256": "27d24f279e4b81bd17ecbdcc38e4c42991bb388826c0b200067ce0eb59d3da5b", + "files": { + "ladspa.h": "ladspa_sdk_1.17/src/ladspa.h", + "LICENSE": "ladspa_sdk_1.17/doc/COPYING" + } +} diff --git a/7.x/src/dssi4cs/include/ladspa/ladspa.h b/7.x/src/dssi4cs/include/ladspa/ladspa.h new file mode 100644 index 0000000..5c30a8a --- /dev/null +++ b/7.x/src/dssi4cs/include/ladspa/ladspa.h @@ -0,0 +1,603 @@ +/* ladspa.h + + Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. + Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, + Stefan Westerfeld. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. */ + +#ifndef LADSPA_INCLUDED +#define LADSPA_INCLUDED + +#define LADSPA_VERSION "1.1" +#define LADSPA_VERSION_MAJOR 1 +#define LADSPA_VERSION_MINOR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/*****************************************************************************/ + +/* Overview: + + There is a large number of synthesis packages in use or development + on the Linux platform at this time. This API (`The Linux Audio + Developer's Simple Plugin API') attempts to give programmers the + ability to write simple `plugin' audio processors in C/C++ and link + them dynamically (`plug') into a range of these packages (`hosts'). + It should be possible for any host and any plugin to communicate + completely through this interface. + + This API is deliberately short and simple. To achieve compatibility + with a range of promising Linux sound synthesis packages it + attempts to find the `greatest common divisor' in their logical + behaviour. Having said this, certain limiting decisions are + implicit, notably the use of a fixed type (LADSPA_Data) for all + data transfer and absence of a parameterised `initialisation' + phase. See below for the LADSPA_Data typedef. + + Plugins are expected to distinguish between control and audio + data. Plugins have `ports' that are inputs or outputs for audio or + control data and each plugin is `run' for a `block' corresponding + to a short time interval measured in samples. Audio data is + communicated using arrays of LADSPA_Data, allowing a block of audio + to be processed by the plugin in a single pass. Control data is + communicated using single LADSPA_Data values. Control data has a + single value at the start of a call to the `run()' or `run_adding()' + function, and may be considered to remain this value for its + duration. The plugin may assume that all its input and output ports + have been connected to the relevant data location (see the + `connect_port()' function below) before it is asked to run. + + Plugins will reside in shared object files suitable for dynamic + linking by dlopen() and family. The file will provide a number of + `plugin types' that can be used to instantiate actual plugins + (sometimes known as `plugin instances') that can be connected + together to perform tasks. + + This API contains very limited error-handling. */ + +/*****************************************************************************/ + +/* Fundamental data type passed in and out of plugin. This data type + is used to communicate audio samples and control values. It is + assumed that the plugin will work sensibly given any numeric input + value although it may have a preferred range (see hints below). + + For audio it is generally assumed that 1.0f is the `0dB' reference + amplitude and is a `normal' signal level. */ + +typedef float LADSPA_Data; + +/*****************************************************************************/ + +/* Special Plugin Properties: + + Optional features of the plugin type are encapsulated in the + LADSPA_Properties type. This is assembled by ORing individual + properties together. */ + +typedef int LADSPA_Properties; + +/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a + real-time dependency (e.g. listens to a MIDI device) and so its + output must not be cached or subject to significant latency. */ +#define LADSPA_PROPERTY_REALTIME 0x1 + +/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin + may cease to work correctly if the host elects to use the same data + location for both input and output (see connect_port()). This + should be avoided as enabling this flag makes it impossible for + hosts to use the plugin to process audio `in-place.' */ +#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2 + +/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin + is capable of running not only in a conventional host but also in a + `hard real-time' environment. To qualify for this the plugin must + satisfy all of the following: + + (1) The plugin must not use malloc(), free() or other heap memory + management within its run() or run_adding() functions. All new + memory used in run() must be managed via the stack. These + restrictions only apply to the run() function. + + (2) The plugin will not attempt to make use of any library + functions with the exceptions of functions in the ANSI standard C + and C maths libraries, which the host is expected to provide. + + (3) The plugin will not access files, devices, pipes, sockets, IPC + or any other mechanism that might result in process or thread + blocking. + + (4) The plugin will take an amount of time to execute a run() or + run_adding() call approximately of form (A+B*SampleCount) where A + and B depend on the machine and host in use. This amount of time + may not depend on input signals or plugin state. The host is left + the responsibility to perform timings to estimate upper bounds for + A and B. */ +#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4 + +#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME) +#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN) +#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE) + +/*****************************************************************************/ + +/* Plugin Ports: + + Plugins have `ports' that are inputs or outputs for audio or + data. Ports can communicate arrays of LADSPA_Data (for audio + inputs/outputs) or single LADSPA_Data values (for control + input/outputs). This information is encapsulated in the + LADSPA_PortDescriptor type which is assembled by ORing individual + properties together. + + Note that a port must be an input or an output port but not both + and that a port must be a control or audio port but not both. */ + +typedef int LADSPA_PortDescriptor; + +/* Property LADSPA_PORT_INPUT indicates that the port is an input. */ +#define LADSPA_PORT_INPUT 0x1 + +/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */ +#define LADSPA_PORT_OUTPUT 0x2 + +/* Property LADSPA_PORT_CONTROL indicates that the port is a control + port. */ +#define LADSPA_PORT_CONTROL 0x4 + +/* Property LADSPA_PORT_AUDIO indicates that the port is a audio + port. */ +#define LADSPA_PORT_AUDIO 0x8 + +#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT) +#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT) +#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL) +#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO) + +/*****************************************************************************/ + +/* Plugin Port Range Hints: + + The host may wish to provide a representation of data entering or + leaving a plugin (e.g. to generate a GUI automatically). To make + this more meaningful, the plugin should provide `hints' to the host + describing the usual values taken by the data. + + Note that these are only hints. The host may ignore them and the + plugin must not assume that data supplied to it is meaningful. If + the plugin receives invalid input data it is expected to continue + to run without failure and, where possible, produce a sensible + output (e.g. a high-pass filter given a negative cutoff frequency + might switch to an all-pass mode). + + Hints are meaningful for all input and output ports but hints for + input control ports are expected to be particularly useful. + + More hint information is encapsulated in the + LADSPA_PortRangeHintDescriptor type which is assembled by ORing + individual hint types together. Hints may require further + LowerBound and UpperBound information. + + All the hint information for a particular port is aggregated in the + LADSPA_PortRangeHint structure. */ + +typedef int LADSPA_PortRangeHintDescriptor; + +/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) lower + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of LowerBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_BELOW 0x1 + +/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) upper + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of UpperBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_ABOVE 0x2 + +/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be + considered a Boolean toggle. Data less than or equal to zero should + be considered `off' or `false,' and data above zero should be + considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in + conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or + LADSPA_HINT_DEFAULT_1. */ +#define LADSPA_HINT_TOGGLED 0x4 + +/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified + should be interpreted as multiples of the sample rate. For + instance, a frequency range from 0Hz to the Nyquist frequency (half + the sample rate) could be requested by this hint in conjunction + with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds + at all must support this hint to retain meaning. */ +#define LADSPA_HINT_SAMPLE_RATE 0x8 + +/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the + user will find it more intuitive to view values using a logarithmic + scale. This is particularly useful for frequencies and gains. */ +#define LADSPA_HINT_LOGARITHMIC 0x10 + +/* Hint LADSPA_HINT_INTEGER indicates that a user interface would + probably wish to provide a stepped control taking only integer + values. Any bounds set should be slightly wider than the actual + integer range required to avoid floating point rounding errors. For + instance, the integer set {0,1,2,3} might be described as [-0.1, + 3.1]. */ +#define LADSPA_HINT_INTEGER 0x20 + +/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal' + value for the port that is sensible as a default. For instance, + this value is suitable for use as an initial value in a user + interface or as a value the host might assign to a control port + when the user has not provided one. Defaults are encoded using a + mask so only one default may be specified for a port. Some of the + hints make use of lower and upper bounds, in which case the + relevant bound or bounds must be available and + LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting + default must be rounded if LADSPA_HINT_INTEGER is present. Default + values were introduced in LADSPA v1.1. */ +#define LADSPA_HINT_DEFAULT_MASK 0x3C0 + +/* This default values indicates that no default is provided. */ +#define LADSPA_HINT_DEFAULT_NONE 0x0 + +/* This default hint indicates that the suggested lower bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MINIMUM 0x40 + +/* This default hint indicates that a low value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 + + log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper + * 0.25). */ +#define LADSPA_HINT_DEFAULT_LOW 0x80 + +/* This default hint indicates that a middle value between the + suggested lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 + + log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper * + 0.5). */ +#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0 + +/* This default hint indicates that a high value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 + + log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper + * 0.75). */ +#define LADSPA_HINT_DEFAULT_HIGH 0x100 + +/* This default hint indicates that the suggested upper bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140 + +/* This default hint indicates that the number 0 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_0 0x200 + +/* This default hint indicates that the number 1 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_1 0x240 + +/* This default hint indicates that the number 100 should be used. */ +#define LADSPA_HINT_DEFAULT_100 0x280 + +/* This default hint indicates that the Hz frequency of `concert A' + should be used. This will be 440 unless the host uses an unusual + tuning convention, in which case it may be within a few Hz. */ +#define LADSPA_HINT_DEFAULT_440 0x2C0 + +#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) +#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) +#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) +#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) +#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) +#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) + +#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK) +#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MINIMUM) +#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_LOW) +#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MIDDLE) +#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_HIGH) +#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MAXIMUM) +#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_0) +#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_1) +#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_100) +#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_440) + +typedef struct _LADSPA_PortRangeHint { + + /* Hints about the port. */ + LADSPA_PortRangeHintDescriptor HintDescriptor; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data LowerBound; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data UpperBound; + +} LADSPA_PortRangeHint; + +/*****************************************************************************/ + +/* Plugin Handles: + + This plugin handle indicates a particular instance of the plugin + concerned. It is valid to compare this to NULL (0 for C++) but + otherwise the host should not attempt to interpret it. The plugin + may use it to reference internal instance data. */ + +typedef void * LADSPA_Handle; + +/*****************************************************************************/ + +/* Descriptor for a Type of Plugin: + + This structure is used to describe a plugin type. It provides a + number of functions to examine the type, instantiate it, link it to + buffers and workspaces and to run it. */ + +typedef struct _LADSPA_Descriptor { + + /* This numeric identifier indicates the plugin type + uniquely. Plugin programmers may reserve ranges of IDs from a + central body to avoid clashes. Hosts may assume that IDs are + below 0x1000000. */ + unsigned long UniqueID; + + /* This identifier can be used as a unique, case-sensitive + identifier for the plugin type within the plugin file. Plugin + types should be identified by file and label rather than by index + or plugin name, which may be changed in new plugin + versions. Labels must not contain white-space characters. */ + const char * Label; + + /* This indicates a number of properties of the plugin. */ + LADSPA_Properties Properties; + + /* This member points to the null-terminated name of the plugin + (e.g. "Sine Oscillator"). */ + const char * Name; + + /* This member points to the null-terminated string indicating the + maker of the plugin. This can be an empty string but not NULL. */ + const char * Maker; + + /* This member points to the null-terminated string indicating any + copyright applying to the plugin. If no Copyright applies the + string "None" should be used. */ + const char * Copyright; + + /* This indicates the number of ports (input AND output) present on + the plugin. */ + unsigned long PortCount; + + /* This member indicates an array of port descriptors. Valid indices + vary from 0 to PortCount-1. */ + const LADSPA_PortDescriptor * PortDescriptors; + + /* This member indicates an array of null-terminated strings + describing ports (e.g. "Frequency (Hz)"). Valid indices vary from + 0 to PortCount-1. */ + const char * const * PortNames; + + /* This member indicates an array of range hints for each port (see + above). Valid indices vary from 0 to PortCount-1. */ + const LADSPA_PortRangeHint * PortRangeHints; + + /* This may be used by the plugin developer to pass any custom + implementation data into an instantiate call. It must not be used + or interpreted by the host. It is expected that most plugin + writers will not use this facility as LADSPA_Handle should be + used to hold instance data. */ + void * ImplementationData; + + /* This member is a function pointer that instantiates a plugin. A + handle is returned indicating the new plugin instance. The + instantiation function accepts a sample rate as a parameter. The + plugin descriptor from which this instantiate function was found + must also be passed. This function must return NULL if + instantiation fails. + + Note that instance initialisation should generally occur in + activate() rather than here. */ + LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor, + unsigned long SampleRate); + + /* This member is a function pointer that connects a port on an + instantiated plugin to a memory location at which a block of data + for the port will be read/written. The data location is expected + to be an array of LADSPA_Data for audio ports or a single + LADSPA_Data value for control ports. Memory issues will be + managed by the host. The plugin must read/write the data at these + locations every time run() or run_adding() is called and the data + present at the time of this connection call should not be + considered meaningful. + + connect_port() may be called more than once for a plugin instance + to allow the host to change the buffers that the plugin is + reading or writing. These calls may be made before or after + activate() or deactivate() calls. + + connect_port() must be called at least once for each port before + run() or run_adding() is called. When working with blocks of + LADSPA_Data the plugin should pay careful attention to the block + size passed to the run function as the block allocated may only + just be large enough to contain the block of samples. + + Plugin writers should be aware that the host may elect to use the + same buffer for more than one port and even use the same buffer + for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN). + However, overlapped buffers or use of a single buffer for both + audio and control data may result in unexpected behaviour. */ + void (*connect_port)(LADSPA_Handle Instance, + unsigned long Port, + LADSPA_Data * DataLocation); + + /* This member is a function pointer that initialises a plugin + instance and activates it for use. This is separated from + instantiate() to aid real-time support and so that hosts can + reinitialise a plugin instance by calling deactivate() and then + activate(). In this case the plugin instance must reset all state + information dependent on the history of the plugin instance + except for any data locations provided by connect_port() and any + gain set by set_run_adding_gain(). If there is nothing for + activate() to do then the plugin writer may provide a NULL rather + than an empty function. + + When present, hosts must call this function once before run() (or + run_adding()) is called for the first time. This call should be + made as close to the run() call as possible and indicates to + real-time plugins that they are now live. Plugins should not rely + on a prompt call to run() after activate(). activate() may not be + called again unless deactivate() is called first. Note that + connect_port() may be called before or after a call to + activate(). */ + void (*activate)(LADSPA_Handle Instance); + + /* This method is a function pointer that runs an instance of a + plugin for a block. Two parameters are required: the first is a + handle to the particular instance to be run and the second + indicates the block size (in samples) for which the plugin + instance may run. + + Note that if an activate() function exists then it must be called + before run() or run_adding(). If deactivate() is called for a + plugin instance then the plugin instance may not be reused until + activate() has been called again. + + If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE + then there are various things that the plugin should not do + within the run() or run_adding() functions (see above). */ + void (*run)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that runs an instance of a + plugin for a block. This has identical behaviour to run() except + in the way data is output from the plugin. When run() is used, + values are written directly to the memory areas associated with + the output ports. However when run_adding() is called, values + must be added to the values already present in the memory + areas. Furthermore, output values written must be scaled by the + current gain set by set_run_adding_gain() (see below) before + addition. + + run_adding() is optional. When it is not provided by a plugin, + this function pointer must be set to NULL. When it is provided, + the function set_run_adding_gain() must be provided also. */ + void (*run_adding)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that sets the output gain for + use when run_adding() is called (see above). If this function is + never called the gain is assumed to default to 1. Gain + information should be retained when activate() or deactivate() + are called. + + This function should be provided by the plugin if and only if the + run_adding() function is provided. When it is absent this + function pointer must be set to NULL. */ + void (*set_run_adding_gain)(LADSPA_Handle Instance, + LADSPA_Data Gain); + + /* This is the counterpart to activate() (see above). If there is + nothing for deactivate() to do then the plugin writer may provide + a NULL rather than an empty function. + + Hosts must deactivate all activated units after they have been + run() (or run_adding()) for the last time. This call should be + made as close to the last run() call as possible and indicates to + real-time plugins that they are no longer live. Plugins should + not rely on prompt deactivation. Note that connect_port() may be + called before or after a call to deactivate(). + + Deactivation is not similar to pausing as the plugin instance + will be reinitialised when activate() is called to reuse it. */ + void (*deactivate)(LADSPA_Handle Instance); + + /* Once an instance of a plugin has been finished with it can be + deleted using the following function. The instance handle passed + ceases to be valid after this call. + + If activate() was called for a plugin instance then a + corresponding call to deactivate() must be made before cleanup() + is called. */ + void (*cleanup)(LADSPA_Handle Instance); + +} LADSPA_Descriptor; + +/**********************************************************************/ + +/* Accessing a Plugin: */ + +/* The exact mechanism by which plugins are loaded is host-dependent, + however all most hosts will need to know is the name of shared + object file containing the plugin types. To allow multiple hosts to + share plugin types, hosts may wish to check for environment + variable LADSPA_PATH. If present, this should contain a + colon-separated path indicating directories that should be searched + (in order) when loading plugin types. + + A plugin programmer must include a function called + "ladspa_descriptor" with the following function prototype within + the shared object file. This function will have C-style linkage (if + you are using C++ this is taken care of by the `extern "C"' clause + at the top of the file). + + A host will find the plugin shared object file by one means or + another, find the ladspa_descriptor() function, call it, and + proceed from there. + + Plugin types are accessed by index (not ID) using values from 0 + upwards. Out of range indexes must result in this function + returning NULL, so the plugin count can be determined by checking + for the least index that results in NULL being returned. */ + +const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); + +/* Datatype corresponding to the ladspa_descriptor() function. */ +typedef const LADSPA_Descriptor * +(*LADSPA_Descriptor_Function)(unsigned long Index); + +/**********************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* LADSPA_INCLUDED */ + +/* EOF */ diff --git a/7.x/src/dssi4cs/include/ladspa/update.py b/7.x/src/dssi4cs/include/ladspa/update.py new file mode 100755 index 0000000..30fc025 --- /dev/null +++ b/7.x/src/dssi4cs/include/ladspa/update.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +# Run from any directory. With no argument, select the latest numbered stable +# release. A version selects that release; --pinned reproduces SOURCE.json. +# Requires curl and Python 3. No upstream build scripts are run. +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +import tarfile +import tempfile + +source_dir = Path(__file__).resolve().parent +if len(sys.argv) > 2 or (len(sys.argv) == 2 and sys.argv[1] == '--help'): + print(f'Usage: {sys.argv[0]} [VERSION|--pinned]') + sys.exit(0 if len(sys.argv) == 2 else 1) +selection = sys.argv[1] if len(sys.argv) == 2 else 'latest' + +def fetch(url, destination): + subprocess.run(['curl', '--fail', '--location', '--silent', '--show-error', + '--retry', '3', '--connect-timeout', '20', '--max-time', '120', + url, '-o', str(destination)], check=True) + +with tempfile.TemporaryDirectory(prefix='csound-ladspa-') as temporary: + temporary = Path(temporary) + recorded = None + if selection == '--pinned': + recorded = json.loads((source_dir / 'SOURCE.json').read_text()) + version = recorded['version'] + elif selection == 'latest': + listing = temporary / 'releases.html' + fetch('https://www.ladspa.org/ladspa_sdk/download.html', listing) + versions = re.findall('ladspa_sdk_(\\d+(?:\\.\\d+)+)\\.tgz', listing.read_text()) + if not versions: + sys.exit('No stable release found; checked-in files have not changed') + version = max(versions, key=lambda value: tuple(map(int, value.split('.')))) + else: + version = selection + if not re.fullmatch(r'\d+(?:\.\d+)+', version): + sys.exit('Expected a numbered release, for example 1.2.3') + + url = 'https://www.ladspa.org/download/ladspa_sdk_{version}.tgz'.format(version=version) + archive = temporary / 'source.tar' + fetch(url, archive) + digest = hashlib.sha256(archive.read_bytes()).hexdigest() + if recorded and digest != recorded['sha256']: + sys.exit('Archive differs from SOURCE.json; checked-in files have not changed') + files = {'ladspa.h': 'ladspa_sdk_{version}/src/ladspa.h', 'LICENSE': 'ladspa_sdk_{version}/doc/COPYING'} + staged = {} + # Read only named files. Preserve upstream bytes, including license notices. + with tarfile.open(archive) as package: + for destination, member in files.items(): + entry = package.getmember(member.format(version=version)) + if not entry.isfile(): + sys.exit(f'Expected a file: {entry.name}') + staged[destination] = package.extractfile(entry).read() + metadata = {'version': version, 'url': url, 'sha256': digest, + 'files': {name: member.format(version=version) for name, member in files.items()}} + staged['SOURCE.json'] = (json.dumps(metadata, indent=2) + '\n').encode() + readme = f"""# LADSPA + +Unmodified files from [release {version}]({url}). + +Only the LADSPA API header is vendored. +The header uses LGPL-2.1-or-later; see [LICENSE](LICENSE) and its license notice. + +[SOURCE.json](SOURCE.json) records the archive URL, SHA-256, and file paths. +Keep platform adapters in the plugin's own sources, outside this directory. + +Run `./update.py` for the latest stable release, `./update.py VERSION` for a +specific release, or `./update.py --pinned` to reproduce this copy and check its +archive hash. Downloads and extraction finish before checked-in files change. +The script requires curl and Python 3 and works from any directory. + +After an update, review the source and license diff, rebuild the DSSI/LADSPA +plugin on Linux, and verify that a native synth or effect still loads. Commit +the header, license, README, and SOURCE.json together. +""" + staged['README.md'] = readme.encode() + # A failed download or missing archive member leaves the old copy intact. + for name, data in staged.items(): + destination = source_dir / name + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(data) + print(f'Updated LADSPA to {version}. Source: {url}') diff --git a/7.x/src/dssi4cs/load.c b/7.x/src/dssi4cs/load.c new file mode 100644 index 0000000..8456999 --- /dev/null +++ b/7.x/src/dssi4cs/load.c @@ -0,0 +1,49 @@ +/* load.c + + Copyright 2005 Richard W.E. Furse + with Csound adjustments by Andres Cabrera, Istvan Varga, John ffitch + + Free software by Richard W.E. Furse. Do with as you will. No + warranty. */ + +#include "utils.h" +#include + +static void *try_library(CSOUND *csound, const char *path, int flags) +{ + const char *suffixes[] = {"", ".so", NULL}; + for (int i = 0; suffixes[i]; ++i) { + size_t size = strlen(path) + strlen(suffixes[i]) + 1; + char *name = csound->Malloc(csound, size); + snprintf(name, size, "%s%s", path, suffixes[i]); + void *library = dlopen(name, flags | RTLD_LOCAL); + csound->Free(csound, name); + if (library) return library; + } + return NULL; +} + +void *dlopenLADSPA(CSOUND *csound, const char *name, int32_t flags) +{ + if (strchr(name, '/')) return try_library(csound, name, flags); + const char *paths[] = {csound->GetEnv(csound, "DSSI_PATH"), + csound->GetEnv(csound, "LADSPA_PATH"), + DSSI4CS_DEFAULT_PATH}; + for (unsigned i = 0; i < sizeof(paths)/sizeof(paths[0]); ++i) { + const char *start = paths[i]; + while (start && *start) { + const char *end = strchr(start, ':'); + size_t len = end ? (size_t)(end - start) : strlen(start); + if (len) { + size_t size = len + strlen(name) + 2; + char *path = csound->Malloc(csound, size); + snprintf(path, size, "%.*s/%s", (int)len, start, name); + void *library = try_library(csound, path, flags); + csound->Free(csound, path); + if (library) return library; + } + start = end ? end + 1 : NULL; + } + } + return NULL; +} diff --git a/7.x/src/dssi4cs/utils.h b/7.x/src/dssi4cs/utils.h new file mode 100644 index 0000000..e882912 --- /dev/null +++ b/7.x/src/dssi4cs/utils.h @@ -0,0 +1,14 @@ +/* Native DSSI/LADSPA library lookup. */ +#ifndef DSSI4CS_UTILS_H +#define DSSI4CS_UTILS_H +#include "csdl.h" +/* Search path used when neither DSSI_PATH nor LADSPA_PATH is set. The build + may override this (see CMakeLists.txt) to prepend the multiarch library + directory detected on the build host, e.g. /usr/lib/x86_64-linux-gnu. */ +#ifndef DSSI4CS_DEFAULT_PATH +#define DSSI4CS_DEFAULT_PATH \ + "/usr/local/lib/dssi:/usr/lib/dssi:/usr/lib64/dssi:" \ + "/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/lib64/ladspa" +#endif +void *dlopenLADSPA(CSOUND *, const char *, int32_t); +#endif diff --git a/7.x/src/py/src/pycall-gen.py b/7.x/src/py/src/pycall-gen.py index 2246c5d..9a6b283 100644 --- a/7.x/src/py/src/pycall-gen.py +++ b/7.x/src/py/src/pycall-gen.py @@ -164,20 +164,20 @@ def generate_pycall_opcode_struct(f, n, triggered=0): s = 'typedef struct {\n' s += ' OPDS h;\n' if n == 1: - s += ' MYFLT *result;\n' + s += ' cs_float *result;\n' else: for i in range(n): - s += ' MYFLT *result%d;\n' % (i+1) + s += ' cs_float *result%d;\n' % (i+1) if triggered: - s += ' MYFLT *trigger;\n' + s += ' cs_float *trigger;\n' s += ' STRINGDAT *function;\n' - s += ' MYFLT *args[VARGMAX-3];\n' + s += ' cs_float *args[VARGMAX-3];\n' if triggered: if n == 1: - s += ' MYFLT oresult\n;' + s += ' cs_float oresult;\n' else: for i in range(n): - s += ' MYFLT oresult%d;\n' % (i+1) + s += ' cs_float oresult%d;\n' % (i+1) s += '} PYCALL%d%s;\n' % (n, T) print(s, file=f) diff --git a/7.x/src/py/src/pycall.auto.h b/7.x/src/py/src/pycall.auto.h index 81935c6..aa59ce1 100644 --- a/7.x/src/py/src/pycall.auto.h +++ b/7.x/src/py/src/pycall.auto.h @@ -23,225 +23,225 @@ typedef struct { OPDS h; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL0; typedef struct { OPDS h; - MYFLT *trigger; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL0T; typedef struct { OPDS h; - MYFLT *result; + cs_float *result; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL1; typedef struct { OPDS h; - MYFLT *result; - MYFLT *trigger; + cs_float *result; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult -;} PYCALL1T; + cs_float *args[VARGMAX-3]; + cs_float oresult; +} PYCALL1T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; + cs_float *result1; + cs_float *result2; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL2; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *trigger; + cs_float *result1; + cs_float *result2; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; } PYCALL2T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; + cs_float *result1; + cs_float *result2; + cs_float *result3; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL3; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *trigger; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; - MYFLT oresult3; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; + cs_float oresult3; } PYCALL3T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL4; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *trigger; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; - MYFLT oresult3; - MYFLT oresult4; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; + cs_float oresult3; + cs_float oresult4; } PYCALL4T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL5; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *trigger; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; - MYFLT oresult3; - MYFLT oresult4; - MYFLT oresult5; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; + cs_float oresult3; + cs_float oresult4; + cs_float oresult5; } PYCALL5T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *result6; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *result6; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL6; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *result6; - MYFLT *trigger; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *result6; + cs_float *trigger; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; - MYFLT oresult3; - MYFLT oresult4; - MYFLT oresult5; - MYFLT oresult6; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; + cs_float oresult3; + cs_float oresult4; + cs_float oresult5; + cs_float oresult6; } PYCALL6T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *result6; - MYFLT *result7; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *result6; + cs_float *result7; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL7; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *result6; - MYFLT *result7; - MYFLT *trigger; - STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; - MYFLT oresult3; - MYFLT oresult4; - MYFLT oresult5; - MYFLT oresult6; - MYFLT oresult7; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *result6; + cs_float *result7; + cs_float *trigger; + STRINGDAT *function; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; + cs_float oresult3; + cs_float oresult4; + cs_float oresult5; + cs_float oresult6; + cs_float oresult7; } PYCALL7T; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *result6; - MYFLT *result7; - MYFLT *result8; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *result6; + cs_float *result7; + cs_float *result8; STRINGDAT *function; - MYFLT *args[VARGMAX-3]; + cs_float *args[VARGMAX-3]; } PYCALL8; typedef struct { OPDS h; - MYFLT *result1; - MYFLT *result2; - MYFLT *result3; - MYFLT *result4; - MYFLT *result5; - MYFLT *result6; - MYFLT *result7; - MYFLT *result8; - MYFLT *trigger; - STRINGDAT *function; - MYFLT *args[VARGMAX-3]; - MYFLT oresult1; - MYFLT oresult2; - MYFLT oresult3; - MYFLT oresult4; - MYFLT oresult5; - MYFLT oresult6; - MYFLT oresult7; - MYFLT oresult8; + cs_float *result1; + cs_float *result2; + cs_float *result3; + cs_float *result4; + cs_float *result5; + cs_float *result6; + cs_float *result7; + cs_float *result8; + cs_float *trigger; + STRINGDAT *function; + cs_float *args[VARGMAX-3]; + cs_float oresult1; + cs_float oresult2; + cs_float oresult3; + cs_float oresult4; + cs_float oresult5; + cs_float oresult6; + cs_float oresult7; + cs_float oresult8; } PYCALL8T; diff --git a/7.x/src/py/src/pythonopcodes.c b/7.x/src/py/src/pythonopcodes.c index 71689c0..b4cc4fe 100644 --- a/7.x/src/py/src/pythonopcodes.c +++ b/7.x/src/py/src/pythonopcodes.c @@ -55,7 +55,7 @@ static CS_NOINLINE void create_private_namespace_if_needed(OPDS *o) } static void format_call_statement2(char *statement, char *callable, - int argc, MYFLT *argv[], long skip) + int argc, cs_float *argv[], long skip) { int i; @@ -77,7 +77,7 @@ static void format_call_statement2(char *statement, char *callable, } static void format_call_statement(char *statement, char *callable, - int argc, MYFLT *argv[], int skip) + int argc, cs_float *argv[], int skip) { int i; diff --git a/7.x/src/py/src/pythonopcodes.h b/7.x/src/py/src/pythonopcodes.h index 82d7d21..84d17c1 100644 --- a/7.x/src/py/src/pythonopcodes.h +++ b/7.x/src/py/src/pythonopcodes.h @@ -39,8 +39,8 @@ typedef struct { typedef struct { OPDS h; STRINGDAT *function; - MYFLT *nresult; - MYFLT *args[VARGMAX]; + cs_float *nresult; + cs_float *args[VARGMAX]; } PYCALLN; #endif /* _pycsound_pythonopcodes_h_ */ diff --git a/7.x/src/py/src/pyx-gen.py b/7.x/src/py/src/pyx-gen.py index d1e54ce..bbccee8 100644 --- a/7.x/src/py/src/pyx-gen.py +++ b/7.x/src/py/src/pyx-gen.py @@ -154,14 +154,14 @@ def generate_pycall_opcode_struct(f, action, triggered): s = 'typedef struct {\n' s += ' OPDS h;\n' if action == 'eval': - s += ' MYFLT *result;\n' + s += ' cs_float *result;\n' if triggered: - s += ' MYFLT *trigger;\n' + s += ' cs_float *trigger;\n' s += ' STRINGDAT *string;\n' if action == 'assign': - s += ' MYFLT *value;\n' + s += ' cs_float *value;\n' if action == 'eval' and triggered: - s += ' MYFLT oresult;\n' + s += ' cs_float oresult;\n' s += '} PY%(ACTION)s%(T)s;\n' % locals() print(s, file=f) diff --git a/7.x/src/py/src/pyx.auto.h b/7.x/src/py/src/pyx.auto.h index 4744dcf..21703ef 100644 --- a/7.x/src/py/src/pyx.auto.h +++ b/7.x/src/py/src/pyx.auto.h @@ -27,7 +27,7 @@ typedef struct { typedef struct { OPDS h; - MYFLT *trigger; + cs_float *trigger; STRINGDAT *string; } PYEXECT; @@ -38,34 +38,34 @@ typedef struct { typedef struct { OPDS h; - MYFLT *trigger; + cs_float *trigger; STRINGDAT *string; } PYRUNT; typedef struct { OPDS h; - MYFLT *result; + cs_float *result; STRINGDAT *string; } PYEVAL; typedef struct { OPDS h; - MYFLT *result; - MYFLT *trigger; + cs_float *result; + cs_float *trigger; STRINGDAT *string; - MYFLT oresult; + cs_float oresult; } PYEVALT; typedef struct { OPDS h; STRINGDAT *string; - MYFLT *value; + cs_float *value; } PYASSIGN; typedef struct { OPDS h; - MYFLT *trigger; + cs_float *trigger; STRINGDAT *string; - MYFLT *value; + cs_float *value; } PYASSIGNT;