aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/eg-sampler.lv2
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/eg-sampler.lv2')
-rw-r--r--plugins/eg-sampler.lv2/README.txt14
-rw-r--r--plugins/eg-sampler.lv2/atom_sink.h34
-rw-r--r--plugins/eg-sampler.lv2/click.wavbin644 -> 0 bytes
-rw-r--r--plugins/eg-sampler.lv2/manifest.ttl.in19
-rw-r--r--plugins/eg-sampler.lv2/meson.build82
-rw-r--r--plugins/eg-sampler.lv2/peaks.h266
-rw-r--r--plugins/eg-sampler.lv2/sampler.c695
-rw-r--r--plugins/eg-sampler.lv2/sampler.ttl73
-rw-r--r--plugins/eg-sampler.lv2/sampler_ui.c458
-rw-r--r--plugins/eg-sampler.lv2/uris.h170
10 files changed, 0 insertions, 1811 deletions
diff --git a/plugins/eg-sampler.lv2/README.txt b/plugins/eg-sampler.lv2/README.txt
deleted file mode 100644
index 8d136fa..0000000
--- a/plugins/eg-sampler.lv2/README.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-== Sampler ==
-
-This plugin loads a single sample from a .wav file and plays it back when a MIDI
-note on is received. Any sample on the system can be loaded via another event.
-A Gtk UI is included which does this, but the host can as well.
-
-This plugin illustrates:
-
-- UI <==> Plugin communication via events
-- Use of the worker extension for non-realtime tasks (sample loading)
-- Use of the log extension to print log messages via the host
-- Saving plugin state via the state extension
-- Dynamic plugin control via the same properties saved to state
-- Network-transparent waveform display with incremental peak transmission
diff --git a/plugins/eg-sampler.lv2/atom_sink.h b/plugins/eg-sampler.lv2/atom_sink.h
deleted file mode 100644
index 3319767..0000000
--- a/plugins/eg-sampler.lv2/atom_sink.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2016 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#include <lv2/atom/atom.h>
-#include <lv2/atom/forge.h>
-#include <lv2/atom/util.h>
-
-#include <stdint.h>
-#include <string.h>
-
-/**
- A forge sink that writes to an atom buffer.
-
- It is assumed that the handle points to an LV2_Atom large enough to store
- the forge output. The forged result is in the body of the buffer atom.
-*/
-static LV2_Atom_Forge_Ref
-atom_sink(LV2_Atom_Forge_Sink_Handle handle, const void* buf, uint32_t size)
-{
- LV2_Atom* atom = (LV2_Atom*)handle;
- const uint32_t offset = lv2_atom_total_size(atom);
- memcpy((char*)atom + offset, buf, size);
- atom->size += size;
- return offset;
-}
-
-/**
- Dereference counterpart to atom_sink().
-*/
-static LV2_Atom*
-atom_sink_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
-{
- return (LV2_Atom*)((char*)handle + ref);
-}
diff --git a/plugins/eg-sampler.lv2/click.wav b/plugins/eg-sampler.lv2/click.wav
deleted file mode 100644
index 520a18c..0000000
--- a/plugins/eg-sampler.lv2/click.wav
+++ /dev/null
Binary files differ
diff --git a/plugins/eg-sampler.lv2/manifest.ttl.in b/plugins/eg-sampler.lv2/manifest.ttl.in
deleted file mode 100644
index e688256..0000000
--- a/plugins/eg-sampler.lv2/manifest.ttl.in
+++ /dev/null
@@ -1,19 +0,0 @@
-# Unlike the previous examples, this manifest lists more than one resource: the
-# plugin as usual, and the UI. The descriptions are similar, but have
-# different types, so the host can decide from this file alone whether or not
-# it is interested, and avoid following the `rdfs:seeAlso` link if not (though
-# in this case both are described in the same file).
-
-@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
-
-<http://lv2plug.in/plugins/eg-sampler>
- a lv2:Plugin ;
- lv2:binary <sampler@LIB_EXT@> ;
- rdfs:seeAlso <sampler.ttl> .
-
-<http://lv2plug.in/plugins/eg-sampler#ui>
- a ui:GtkUI ;
- lv2:binary <sampler_ui@LIB_EXT@> ;
- rdfs:seeAlso <sampler.ttl> .
diff --git a/plugins/eg-sampler.lv2/meson.build b/plugins/eg-sampler.lv2/meson.build
deleted file mode 100644
index a0f8799..0000000
--- a/plugins/eg-sampler.lv2/meson.build
+++ /dev/null
@@ -1,82 +0,0 @@
-# Copyright 2022 David Robillard <d@drobilla.net>
-# SPDX-License-Identifier: 0BSD OR ISC
-
-plugin_sources = files('sampler.c')
-ui_sources = files('sampler_ui.c')
-bundle_name = 'eg-sampler.lv2'
-data_filenames = ['manifest.ttl.in', 'sampler.ttl', 'click.wav']
-
-samplerate_dep = dependency(
- 'samplerate',
- include_type: 'system',
- required: get_option('plugins'),
- version: '>= 0.1.0',
-)
-
-sndfile_dep = dependency(
- 'sndfile',
- include_type: 'system',
- required: get_option('plugins'),
- version: '>= 1.0.0',
-)
-
-gtk2_dep = dependency(
- 'gtk+-2.0',
- include_type: 'system',
- required: get_option('plugins'),
- version: '>= 2.18.0',
-)
-
-if samplerate_dep.found() and sndfile_dep.found()
- module = shared_library(
- 'sampler',
- plugin_sources,
- c_args: c_suppressions,
- dependencies: [lv2_dep, m_dep, samplerate_dep, sndfile_dep],
- gnu_symbol_visibility: 'hidden',
- implicit_include_directories: false,
- install: true,
- install_dir: lv2dir / bundle_name,
- name_prefix: '',
- )
-
- extension = '.' + module.full_path().split('.')[-1]
- config = configuration_data({'LIB_EXT': extension})
-
- foreach filename : data_filenames
- if filename.endswith('.in')
- configure_file(
- configuration: config,
- input: files(filename),
- install_dir: lv2dir / bundle_name,
- output: filename.substring(0, -3),
- )
- else
- configure_file(
- copy: true,
- input: files(filename),
- install_dir: lv2dir / bundle_name,
- output: filename,
- )
- endif
- endforeach
-
- if gtk2_dep.found()
- ui_suppressions = c_suppressions
- if cc.get_id() == 'gcc'
- ui_suppressions += ['-Wno-strict-overflow']
- endif
-
- shared_library(
- 'sampler_ui',
- ui_sources,
- c_args: ui_suppressions,
- dependencies: [lv2_dep, gtk2_dep],
- gnu_symbol_visibility: 'hidden',
- implicit_include_directories: false,
- install: true,
- install_dir: lv2dir / bundle_name,
- name_prefix: '',
- )
- endif
-endif
diff --git a/plugins/eg-sampler.lv2/peaks.h b/plugins/eg-sampler.lv2/peaks.h
deleted file mode 100644
index ff91546..0000000
--- a/plugins/eg-sampler.lv2/peaks.h
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2016 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#ifndef PEAKS_H_INCLUDED
-#define PEAKS_H_INCLUDED
-
-/**
- This file defines utilities for sending and receiving audio peaks for
- waveform display. The functionality is divided into two objects:
- PeaksSender, for sending peaks updates from the plugin, and PeaksReceiver,
- for receiving such updates and caching the peaks.
-
- This allows peaks for a waveform of any size at any resolution to be
- requested, with reasonably sized incremental updates sent over plugin ports.
-*/
-
-#include <lv2/atom/atom.h>
-#include <lv2/atom/forge.h>
-#include <lv2/atom/util.h>
-#include <lv2/urid/urid.h>
-
-#include <math.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define PEAKS_URI "http://lv2plug.in/ns/peaks#"
-#define PEAKS__PeakUpdate PEAKS_URI "PeakUpdate"
-#define PEAKS__magnitudes PEAKS_URI "magnitudes"
-#define PEAKS__offset PEAKS_URI "offset"
-#define PEAKS__total PEAKS_URI "total"
-
-#ifndef MIN
-# define MIN(a, b) (((a) < (b)) ? (a) : (b))
-#endif
-#ifndef MAX
-# define MAX(a, b) (((a) > (b)) ? (a) : (b))
-#endif
-
-typedef struct {
- LV2_URID atom_Float;
- LV2_URID atom_Int;
- LV2_URID atom_Vector;
- LV2_URID peaks_PeakUpdate;
- LV2_URID peaks_magnitudes;
- LV2_URID peaks_offset;
- LV2_URID peaks_total;
-} PeaksURIs;
-
-typedef struct {
- PeaksURIs uris; ///< URIDs used in protocol
- const float* samples; ///< Sample data
- uint32_t n_samples; ///< Total number of samples
- uint32_t n_peaks; ///< Total number of peaks
- uint32_t current_offset; ///< Current peak offset
- bool sending; ///< True iff currently sending
-} PeaksSender;
-
-typedef struct {
- PeaksURIs uris; ///< URIDs used in protocol
- float* peaks; ///< Received peaks, or zeroes
- uint32_t n_peaks; ///< Total number of peaks
-} PeaksReceiver;
-
-/**
- Map URIs used in the peaks protocol.
-*/
-static inline void
-peaks_map_uris(PeaksURIs* uris, LV2_URID_Map* map)
-{
- uris->atom_Float = map->map(map->handle, LV2_ATOM__Float);
- uris->atom_Int = map->map(map->handle, LV2_ATOM__Int);
- uris->atom_Vector = map->map(map->handle, LV2_ATOM__Vector);
- uris->peaks_PeakUpdate = map->map(map->handle, PEAKS__PeakUpdate);
- uris->peaks_magnitudes = map->map(map->handle, PEAKS__magnitudes);
- uris->peaks_offset = map->map(map->handle, PEAKS__offset);
- uris->peaks_total = map->map(map->handle, PEAKS__total);
-}
-
-/**
- Initialise peaks sender. The new sender is inactive and will do nothing
- when `peaks_sender_send()` is called, until a transmission is started with
- `peaks_sender_start()`.
-*/
-static inline PeaksSender*
-peaks_sender_init(PeaksSender* sender, LV2_URID_Map* map)
-{
- memset(sender, 0, sizeof(*sender));
- peaks_map_uris(&sender->uris, map);
- return sender;
-}
-
-/**
- Prepare to start a new peaks transmission. After this is called, the peaks
- can be sent with successive calls to `peaks_sender_send()`.
-*/
-static inline void
-peaks_sender_start(PeaksSender* sender,
- const float* samples,
- uint32_t n_samples,
- uint32_t n_peaks)
-{
- sender->samples = samples;
- sender->n_samples = n_samples;
- sender->n_peaks = n_peaks;
- sender->current_offset = 0;
- sender->sending = true;
-}
-
-/**
- Forge a message which sends a range of peaks. Writes a peaks:PeakUpdate
- object to `forge`, like:
-
- [source,turtle]
- ----
- []
- a peaks:PeakUpdate ;
- peaks:offset 256 ;
- peaks:total 1024 ;
- peaks:magnitudes [ 0.2f, 0.3f, ... ] .
- ----
-*/
-static inline bool
-peaks_sender_send(PeaksSender* sender,
- LV2_Atom_Forge* forge,
- uint32_t n_frames,
- uint32_t offset)
-{
- const PeaksURIs* uris = &sender->uris;
- if (!sender->sending || sender->current_offset >= sender->n_peaks) {
- return sender->sending = false;
- }
-
- // Start PeakUpdate object
- lv2_atom_forge_frame_time(forge, offset);
- LV2_Atom_Forge_Frame frame;
- lv2_atom_forge_object(forge, &frame, 0, uris->peaks_PeakUpdate);
-
- // eg:offset = OFFSET
- lv2_atom_forge_key(forge, uris->peaks_offset);
- lv2_atom_forge_int(forge, (int32_t)sender->current_offset);
-
- // eg:total = TOTAL
- lv2_atom_forge_key(forge, uris->peaks_total);
- lv2_atom_forge_int(forge, (int32_t)sender->n_peaks);
-
- // eg:magnitudes = Vector<Float>(PEAK, PEAK, ...)
- lv2_atom_forge_key(forge, uris->peaks_magnitudes);
- LV2_Atom_Forge_Frame vec_frame;
- lv2_atom_forge_vector_head(
- forge, &vec_frame, sizeof(float), uris->atom_Float);
-
- // Calculate how many peaks to send this update
- const uint32_t chunk_size = MAX(1U, sender->n_samples / sender->n_peaks);
- const uint32_t space = forge->size - forge->offset;
- const uint32_t remaining = sender->n_peaks - sender->current_offset;
- const uint32_t n_update =
- MIN(remaining, MIN(n_frames / 4U, space / sizeof(float)));
-
- // Calculate peak (maximum magnitude) for each chunk
- for (uint32_t i = 0; i < n_update; ++i) {
- const uint32_t start = (sender->current_offset + i) * chunk_size;
- float peak = 0.0f;
- for (uint32_t j = 0; j < chunk_size; ++j) {
- peak = fmaxf(peak, fabsf(sender->samples[start + j]));
- }
- lv2_atom_forge_float(forge, peak);
- }
-
- // Finish message
- lv2_atom_forge_pop(forge, &vec_frame);
- lv2_atom_forge_pop(forge, &frame);
-
- sender->current_offset += n_update;
- return true;
-}
-
-/**
- Initialise a peaks receiver. The receiver stores an array of all peaks,
- which is updated incrementally with peaks_receiver_receive().
-*/
-static inline PeaksReceiver*
-peaks_receiver_init(PeaksReceiver* receiver, LV2_URID_Map* map)
-{
- memset(receiver, 0, sizeof(*receiver));
- peaks_map_uris(&receiver->uris, map);
- return receiver;
-}
-
-/**
- Clear stored peaks and free all memory. This should be called when the
- peaks are to be updated with a different audio source.
-*/
-static inline void
-peaks_receiver_clear(PeaksReceiver* receiver)
-{
- free(receiver->peaks);
- receiver->peaks = NULL;
- receiver->n_peaks = 0;
-}
-
-/**
- Handle PeakUpdate message.
-
- The stored peaks array is updated with the slice of peaks in `update`,
- resizing if necessary while preserving contents.
-
- Returns 0 if peaks have been updated, negative on error.
-*/
-static inline int
-peaks_receiver_receive(PeaksReceiver* receiver, const LV2_Atom_Object* update)
-{
- const PeaksURIs* uris = &receiver->uris;
-
- // Get properties of interest from update
- const LV2_Atom_Int* offset = NULL;
- const LV2_Atom_Int* total = NULL;
- const LV2_Atom_Vector* peaks = NULL;
-
- // clang-format off
- lv2_atom_object_get_typed(update,
- uris->peaks_offset, &offset, uris->atom_Int,
- uris->peaks_total, &total, uris->atom_Int,
- uris->peaks_magnitudes, &peaks, uris->atom_Vector,
- 0);
- // clang-format on
-
- if (!offset || !total || !peaks ||
- peaks->body.child_type != uris->atom_Float) {
- return -1; // Invalid update
- }
-
- const uint32_t n = (uint32_t)total->body;
- if (receiver->n_peaks != n) {
- // Update is for a different total number of peaks, resize
- receiver->peaks = (float*)realloc(receiver->peaks, n * sizeof(float));
- if (receiver->n_peaks > 0 && receiver->n_peaks < n) {
- /* The peaks array is being expanded. Copy the old peaks,
- duplicating each as necessary to fill the new peaks buffer.
- This preserves the current peaks so that the peaks array can be
- reasonably drawn at any time, but the resolution will increase
- as new updates arrive. */
- const int64_t n_per = n / receiver->n_peaks;
- for (int64_t i = n - 1; i >= 0; --i) {
- receiver->peaks[i] = receiver->peaks[i / n_per];
- }
- } else if (receiver->n_peaks > 0) {
- /* The peak array is being shrunk. Similar to the above. */
- const int64_t n_per = receiver->n_peaks / n;
- for (int64_t i = n - 1; i >= 0; --i) {
- receiver->peaks[i] = receiver->peaks[i * n_per];
- }
- }
- receiver->n_peaks = n;
- }
-
- // Copy vector contents to corresponding range in peaks array
- memcpy(receiver->peaks + offset->body,
- peaks + 1,
- peaks->atom.size - sizeof(LV2_Atom_Vector_Body));
-
- return 0;
-}
-
-#endif // PEAKS_H_INCLUDED
diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c
deleted file mode 100644
index 6fc04c5..0000000
--- a/plugins/eg-sampler.lv2/sampler.c
+++ /dev/null
@@ -1,695 +0,0 @@
-// Copyright 2011-2016 David Robillard <d@drobilla.net>
-// Copyright 2011 Gabriel M. Beddingfield <gabriel@teuton.org>
-// Copyright 2011 James Morris <jwm.art.net@gmail.com>
-// SPDX-License-Identifier: ISC
-
-#include "atom_sink.h"
-#include "peaks.h"
-#include "uris.h"
-
-#include <lv2/atom/atom.h>
-#include <lv2/atom/forge.h>
-#include <lv2/atom/util.h>
-#include <lv2/core/lv2.h>
-#include <lv2/core/lv2_util.h>
-#include <lv2/log/log.h>
-#include <lv2/log/logger.h>
-#include <lv2/midi/midi.h>
-#include <lv2/state/state.h>
-#include <lv2/urid/urid.h>
-#include <lv2/worker/worker.h>
-
-#include <samplerate.h>
-#include <sndfile.h>
-
-#include <math.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-enum { SAMPLER_CONTROL = 0, SAMPLER_NOTIFY = 1, SAMPLER_OUT = 2 };
-
-typedef struct {
- SF_INFO info; // Info about sample from sndfile
- float* data; // Sample data in float
- char* path; // Path of file
- uint32_t path_len; // Length of path
-} Sample;
-
-typedef struct {
- // Features
- LV2_URID_Map* map;
- LV2_Worker_Schedule* schedule;
- LV2_Log_Logger logger;
-
- // Ports
- const LV2_Atom_Sequence* control_port;
- LV2_Atom_Sequence* notify_port;
- float* output_port;
-
- // Communication utilities
- LV2_Atom_Forge_Frame notify_frame; ///< Cached for worker replies
- LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread
- PeaksSender psend; ///< Audio peaks sender
-
- // URIs
- SamplerURIs uris;
-
- // Playback state
- Sample* sample;
- uint32_t frame_offset;
- float gain;
- float gain_dB;
- sf_count_t frame;
- bool play;
- bool activated;
- bool gain_changed;
- bool sample_changed;
- int sample_rate;
-} Sampler;
-
-/**
- An atom-like message used internally to apply/free samples.
-
- This is only used internally to communicate with the worker, it is never
- sent to the outside world via a port since it is not POD. It is convenient
- to use an Atom header so actual atoms can be easily sent through the same
- ringbuffer.
-*/
-typedef struct {
- LV2_Atom atom;
- Sample* sample;
-} SampleMessage;
-
-/**
- Convert an interleaved audio buffer to mono.
-
- This simply ignores the data on all channels but the first.
-*/
-static sf_count_t
-convert_to_mono(float* data, sf_count_t num_input_frames, uint32_t channels)
-{
- sf_count_t num_output_frames = 0;
-
- for (sf_count_t i = 0; i < num_input_frames * channels; i += channels) {
- data[num_output_frames++] = data[i];
- }
-
- return num_output_frames;
-}
-
-/**
- Load a new sample and return it.
-
- Since this is of course not a real-time safe action, this is called in the
- worker thread only. The sample is loaded and returned only, plugin state is
- not modified.
-*/
-static Sample*
-load_sample(LV2_Log_Logger* logger, const char* path, const int sample_rate)
-{
- lv2_log_trace(logger, "Loading %s\n", path);
-
- const size_t path_len = strlen(path);
- Sample* const sample = (Sample*)calloc(1, sizeof(Sample));
- SF_INFO* const info = &sample->info;
- SNDFILE* const sndfile = sf_open(path, SFM_READ, info);
- float* data = NULL;
- bool error = true;
- if (!sndfile || !info->frames) {
- lv2_log_error(logger, "Failed to open %s\n", path);
- } else if (!(data = (float*)malloc(sizeof(float) * info->frames *
- info->channels))) {
- lv2_log_error(logger, "Failed to allocate memory for sample\n");
- } else {
- error = false;
- }
-
- if (error) {
- free(sample);
- free(data);
- sf_close(sndfile);
- return NULL;
- }
-
- sf_seek(sndfile, 0UL, SEEK_SET);
- sf_read_float(sndfile, data, info->frames * info->channels);
- sf_close(sndfile);
-
- if (info->channels != 1) {
- info->frames = convert_to_mono(data, info->frames, info->channels);
- info->channels = 1;
- }
-
- if (info->samplerate != sample_rate) {
- lv2_log_trace(logger,
- "Converting from %d Hz to %d Hz\n",
- info->samplerate,
- sample_rate);
-
- const double src_ratio = (double)sample_rate / (double)info->samplerate;
- const double output_length = ceil((double)info->frames * src_ratio);
- const long output_frames = (long)output_length;
- float* const output_buffer = (float*)malloc(sizeof(float) * output_frames);
-
- SRC_DATA src_data = {
- data,
- output_buffer,
- info->frames,
- output_frames,
- 0,
- 0,
- 0,
- src_ratio,
- };
-
- if (src_simple(&src_data, SRC_SINC_BEST_QUALITY, 1) != 0) {
- lv2_log_error(logger, "Sample rate conversion failed\n");
- free(output_buffer);
- } else {
- // Replace original data with converted buffer
- free(data);
- data = output_buffer;
- info->frames = src_data.output_frames_gen;
- }
- } else {
- lv2_log_trace(
- logger, "Sample matches the current rate of %d Hz\n", sample_rate);
- }
-
- // Fill sample struct and return it
- sample->data = data;
- sample->path = (char*)malloc(path_len + 1);
- sample->path_len = (uint32_t)path_len;
- memcpy(sample->path, path, path_len + 1);
-
- return sample;
-}
-
-static void
-free_sample(Sampler* self, Sample* sample)
-{
- if (sample) {
- lv2_log_trace(&self->logger, "Freeing %s\n", sample->path);
- free(sample->path);
- free(sample->data);
- free(sample);
- }
-}
-
-/**
- Do work in a non-realtime thread.
-
- This is called for every piece of work scheduled in the audio thread using
- self->schedule->schedule_work(). A reply can be sent back to the audio
- thread using the provided `respond` function.
-*/
-static LV2_Worker_Status
-work(LV2_Handle instance,
- LV2_Worker_Respond_Function respond,
- LV2_Worker_Respond_Handle handle,
- uint32_t size,
- const void* data)
-{
- Sampler* self = (Sampler*)instance;
- const LV2_Atom* atom = (const LV2_Atom*)data;
- if (atom->type == self->uris.eg_freeSample) {
- // Free old sample
- const SampleMessage* msg = (const SampleMessage*)data;
- free_sample(self, msg->sample);
- } else if (atom->type == self->forge.Object) {
- // Handle set message (load sample).
- const LV2_Atom_Object* obj = (const LV2_Atom_Object*)data;
- const char* path = read_set_file(&self->uris, obj);
- if (!path) {
- lv2_log_error(&self->logger, "Malformed set file request\n");
- return LV2_WORKER_ERR_UNKNOWN;
- }
-
- // Load sample.
- Sample* sample = load_sample(&self->logger, path, self->sample_rate);
- if (sample) {
- // Send new sample to run() to be applied
- respond(handle, sizeof(Sample*), &sample);
- }
- }
-
- return LV2_WORKER_SUCCESS;
-}
-
-/**
- Handle a response from work() in the audio thread.
-
- When running normally, this will be called by the host after run(). When
- freewheeling, this will be called immediately at the point the work was
- scheduled.
-*/
-static LV2_Worker_Status
-work_response(LV2_Handle instance, uint32_t size, const void* data)
-{
- Sampler* self = (Sampler*)instance;
- Sample* old_sample = self->sample;
- const Sample* new_sample = *(Sample* const*)data;
-
- // Install the new sample
- self->sample = *(Sample* const*)data;
-
- // Stop playing previous sample, which can be larger than new one
- self->frame = 0;
- self->play = false;
-
- // Schedule work to free the old sample
- SampleMessage msg = {{sizeof(Sample*), self->uris.eg_freeSample}, old_sample};
- self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg);
-
- // Send a notification that we're using a new sample
- lv2_atom_forge_frame_time(&self->forge, self->frame_offset);
- write_set_file(
- &self->forge, &self->uris, new_sample->path, new_sample->path_len);
-
- return LV2_WORKER_SUCCESS;
-}
-
-static void
-connect_port(LV2_Handle instance, uint32_t port, void* data)
-{
- Sampler* self = (Sampler*)instance;
- switch (port) {
- case SAMPLER_CONTROL:
- self->control_port = (const LV2_Atom_Sequence*)data;
- break;
- case SAMPLER_NOTIFY:
- self->notify_port = (LV2_Atom_Sequence*)data;
- break;
- case SAMPLER_OUT:
- self->output_port = (float*)data;
- break;
- default:
- break;
- }
-}
-
-static LV2_Handle
-instantiate(const LV2_Descriptor* descriptor,
- double rate,
- const char* path,
- const LV2_Feature* const* features)
-{
- // Allocate and initialise instance structure.
- Sampler* self = (Sampler*)calloc(1, sizeof(Sampler));
- if (!self) {
- return NULL;
- }
-
- // Get host features
- // clang-format off
- const char* missing = lv2_features_query(
- features,
- LV2_LOG__log, &self->logger.log, false,
- LV2_URID__map, &self->map, true,
- LV2_WORKER__schedule, &self->schedule, true,
- NULL);
- // clang-format on
-
- lv2_log_logger_set_map(&self->logger, self->map);
- if (missing) {
- lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
- free(self);
- return NULL;
- }
-
- // Map URIs and initialise forge
- map_sampler_uris(self->map, &self->uris);
- lv2_atom_forge_init(&self->forge, self->map);
- peaks_sender_init(&self->psend, self->map);
-
- self->gain = 1.0f;
- self->gain_dB = 0.0f;
- self->sample_rate = (int)rate;
-
- return (LV2_Handle)self;
-}
-
-static void
-cleanup(LV2_Handle instance)
-{
- Sampler* self = (Sampler*)instance;
- free_sample(self, self->sample);
- free(self);
-}
-
-static void
-activate(LV2_Handle instance)
-{
- ((Sampler*)instance)->activated = true;
-}
-
-static void
-deactivate(LV2_Handle instance)
-{
- ((Sampler*)instance)->activated = false;
-}
-
-/** Define a macro for converting a gain in dB to a coefficient. */
-#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f)
-
-/**
- Handle an incoming event in the audio thread.
-
- This performs any actions triggered by an event, such as the start of sample
- playback, a sample change, or responding to requests from the UI.
-*/
-static void
-handle_event(Sampler* self, LV2_Atom_Event* ev)
-{
- SamplerURIs* uris = &self->uris;
- PeaksURIs* peaks_uris = &self->psend.uris;
-
- if (ev->body.type == uris->midi_Event) {
- const uint8_t* const msg = (const uint8_t*)(ev + 1);
- switch (lv2_midi_message_type(msg)) {
- case LV2_MIDI_MSG_NOTE_ON:
- self->frame = 0;
- self->play = true;
- break;
- default:
- break;
- }
- } else if (lv2_atom_forge_is_object_type(&self->forge, ev->body.type)) {
- const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body;
- if (obj->body.otype == uris->patch_Set) {
- // Get the property and value of the set message
- const LV2_Atom* property = NULL;
- const LV2_Atom* value = NULL;
-
- // clang-format off
- lv2_atom_object_get(obj,
- uris->patch_property, &property,
- uris->patch_value, &value,
- 0);
- // clang-format on
-
- if (!property) {
- lv2_log_error(&self->logger, "Set message with no property\n");
- return;
- }
-
- if (property->type != uris->atom_URID) {
- lv2_log_error(&self->logger, "Set property is not a URID\n");
- return;
- }
-
- const uint32_t key = ((const LV2_Atom_URID*)property)->body;
- if (key == uris->eg_sample) {
- // Sample change, send it to the worker.
- lv2_log_trace(&self->logger, "Scheduling sample change\n");
- self->schedule->schedule_work(
- self->schedule->handle, lv2_atom_total_size(&ev->body), &ev->body);
- } else if (key == uris->param_gain) {
- // Gain change
- if (value->type == uris->atom_Float) {
- self->gain_dB = ((LV2_Atom_Float*)value)->body;
- self->gain = DB_CO(self->gain_dB);
- }
- }
- } else if (obj->body.otype == uris->patch_Get && self->sample) {
- const LV2_Atom_URID* accept = NULL;
- const LV2_Atom_Int* n_peaks = NULL;
-
- // clang-format off
- lv2_atom_object_get_typed(
- obj,
- uris->patch_accept, &accept, uris->atom_URID,
- peaks_uris->peaks_total, &n_peaks, peaks_uris->atom_Int,
- 0);
- // clang-format on
-
- if (accept && accept->body == peaks_uris->peaks_PeakUpdate) {
- // Received a request for peaks, prepare for transmission
- peaks_sender_start(&self->psend,
- self->sample->data,
- self->sample->info.frames,
- n_peaks->body);
- } else {
- // Received a get message, emit our state (probably to UI)
- lv2_atom_forge_frame_time(&self->forge, self->frame_offset);
- write_set_file(&self->forge,
- &self->uris,
- self->sample->path,
- self->sample->path_len);
- }
- } else {
- lv2_log_trace(&self->logger, "Unknown object type %u\n", obj->body.otype);
- }
- } else {
- lv2_log_trace(&self->logger, "Unknown event type %u\n", ev->body.type);
- }
-}
-
-/**
- Output audio for a slice of the current cycle.
-*/
-static void
-render(Sampler* self, uint32_t start, uint32_t end)
-{
- float* output = self->output_port;
-
- if (self->play && self->sample) {
- // Start/continue writing sample to output
- for (; start < end; ++start) {
- output[start] = self->sample->data[self->frame] * self->gain;
- if (++self->frame == self->sample->info.frames) {
- self->play = false; // Reached end of sample
- break;
- }
- }
- }
-
- // Write silence to remaining buffer
- for (; start < end; ++start) {
- output[start] = 0.0f;
- }
-}
-
-static void
-run(LV2_Handle instance, uint32_t sample_count)
-{
- Sampler* self = (Sampler*)instance;
-
- // Set up forge to write directly to notify output port.
- const uint32_t notify_capacity = self->notify_port->atom.size;
- lv2_atom_forge_set_buffer(
- &self->forge, (uint8_t*)self->notify_port, notify_capacity);
-
- // Start a sequence in the notify output port.
- lv2_atom_forge_sequence_head(&self->forge, &self->notify_frame, 0);
-
- // Send update to UI if gain has changed due to state restore
- if (self->gain_changed) {
- lv2_atom_forge_frame_time(&self->forge, 0);
- write_set_gain(&self->forge, &self->uris, self->gain_dB);
- self->gain_changed = false;
- }
-
- // Send update to UI if sample has changed due to state restore
- if (self->sample_changed) {
- lv2_atom_forge_frame_time(&self->forge, 0);
- write_set_file(
- &self->forge, &self->uris, self->sample->path, self->sample->path_len);
- self->sample_changed = false;
- }
-
- // Iterate over incoming events, emitting audio along the way
- self->frame_offset = 0;
- LV2_ATOM_SEQUENCE_FOREACH (self->control_port, ev) {
- // Render output up to the time of this event
- render(self, self->frame_offset, ev->time.frames);
-
- /* Update current frame offset to this event's time. This is stored in
- the instance because it is used for synchronous worker event
- execution. This allows a sample load event to be executed with
- sample accuracy when running in a non-realtime context (such as
- exporting a session). */
- self->frame_offset = ev->time.frames;
-
- // Process this event
- handle_event(self, ev);
- }
-
- // Use available space after any emitted events to send peaks
- peaks_sender_send(
- &self->psend, &self->forge, sample_count, self->frame_offset);
-
- // Render output for the rest of the cycle past the last event
- render(self, self->frame_offset, sample_count);
-}
-
-static LV2_State_Status
-save(LV2_Handle instance,
- LV2_State_Store_Function store,
- LV2_State_Handle handle,
- uint32_t flags,
- const LV2_Feature* const* features)
-{
- Sampler* self = (Sampler*)instance;
- if (!self->sample) {
- return LV2_STATE_SUCCESS;
- }
-
- LV2_State_Map_Path* map_path =
- (LV2_State_Map_Path*)lv2_features_data(features, LV2_STATE__mapPath);
- if (!map_path) {
- return LV2_STATE_ERR_NO_FEATURE;
- }
-
- // Map absolute sample path to an abstract state path
- char* apath = map_path->abstract_path(map_path->handle, self->sample->path);
-
- // Store eg:sample = abstract path
- store(handle,
- self->uris.eg_sample,
- apath,
- strlen(apath) + 1,
- self->uris.atom_Path,
- LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
-
- free(apath);
-
- // Store the gain value
- store(handle,
- self->uris.param_gain,
- &self->gain_dB,
- sizeof(self->gain_dB),
- self->uris.atom_Float,
- LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
-
- return LV2_STATE_SUCCESS;
-}
-
-static LV2_State_Status
-restore(LV2_Handle instance,
- LV2_State_Retrieve_Function retrieve,
- LV2_State_Handle handle,
- uint32_t flags,
- const LV2_Feature* const* features)
-{
- Sampler* self = (Sampler*)instance;
-
- // Get host features
- LV2_Worker_Schedule* schedule = NULL;
- LV2_State_Map_Path* paths = NULL;
-
- // clang-format off
- const char* missing = lv2_features_query(
- features,
- LV2_STATE__mapPath, &paths, true,
- LV2_WORKER__schedule, &schedule, false,
- NULL);
- // clang-format on
-
- if (missing) {
- lv2_log_error(&self->logger, "Missing feature <%s>\n", missing);
- return LV2_STATE_ERR_NO_FEATURE;
- }
-
- // Get eg:sample from state
- size_t size = 0;
- uint32_t type = 0;
- uint32_t valflags = 0;
- const void* value =
- retrieve(handle, self->uris.eg_sample, &size, &type, &valflags);
-
- if (!value) {
- lv2_log_error(&self->logger, "Missing eg:sample\n");
- return LV2_STATE_ERR_NO_PROPERTY;
- }
-
- if (type != self->uris.atom_Path) {
- lv2_log_error(&self->logger, "Non-path eg:sample\n");
- return LV2_STATE_ERR_BAD_TYPE;
- }
-
- // Map abstract state path to absolute path
- const char* apath = (const char*)value;
- char* path = paths->absolute_path(paths->handle, apath);
-
- // Replace current sample with the new one
- if (!self->activated || !schedule) {
- // No scheduling available, load sample immediately
- lv2_log_trace(&self->logger, "Synchronous restore\n");
- Sample* sample = load_sample(&self->logger, path, self->sample_rate);
- if (sample) {
- free_sample(self, self->sample);
- self->sample = sample;
- self->sample_changed = true;
- }
- } else {
- // Schedule sample to be loaded by the provided worker
- lv2_log_trace(&self->logger, "Scheduling restore\n");
- LV2_Atom_Forge forge;
- LV2_Atom* buf = (LV2_Atom*)calloc(1, strlen(path) + 128);
- lv2_atom_forge_init(&forge, self->map);
- lv2_atom_forge_set_sink(&forge, atom_sink, atom_sink_deref, buf);
- write_set_file(&forge, &self->uris, path, strlen(path));
-
- const uint32_t msg_size = lv2_atom_pad_size(buf->size);
- schedule->schedule_work(self->schedule->handle, msg_size, buf + 1);
- free(buf);
- }
-
- free(path);
-
- // Get param:gain from state
- value = retrieve(handle, self->uris.param_gain, &size, &type, &valflags);
-
- if (!value) {
- // Not an error, since older versions did not save this property
- lv2_log_note(&self->logger, "Missing param:gain\n");
- return LV2_STATE_SUCCESS;
- }
-
- if (type != self->uris.atom_Float) {
- lv2_log_error(&self->logger, "Non-float param:gain\n");
- return LV2_STATE_ERR_BAD_TYPE;
- }
-
- self->gain_dB = *(const float*)value;
- self->gain = DB_CO(self->gain_dB);
- self->gain_changed = true;
-
- return LV2_STATE_SUCCESS;
-}
-
-static const void*
-extension_data(const char* uri)
-{
- static const LV2_State_Interface state = {save, restore};
- static const LV2_Worker_Interface worker = {work, work_response, NULL};
-
- if (!strcmp(uri, LV2_STATE__interface)) {
- return &state;
- }
-
- if (!strcmp(uri, LV2_WORKER__interface)) {
- return &worker;
- }
-
- return NULL;
-}
-
-static const LV2_Descriptor descriptor = {EG_SAMPLER_URI,
- instantiate,
- connect_port,
- activate,
- run,
- deactivate,
- cleanup,
- extension_data};
-
-LV2_SYMBOL_EXPORT const LV2_Descriptor*
-lv2_descriptor(uint32_t index)
-{
- return index == 0 ? &descriptor : NULL;
-}
diff --git a/plugins/eg-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl
deleted file mode 100644
index 4a3c24c..0000000
--- a/plugins/eg-sampler.lv2/sampler.ttl
+++ /dev/null
@@ -1,73 +0,0 @@
-@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
-@prefix doap: <http://usefulinc.com/ns/doap#> .
-@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
-@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
-@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix state: <http://lv2plug.in/ns/ext/state#> .
-@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
-@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
-@prefix work: <http://lv2plug.in/ns/ext/worker#> .
-@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
-
-<http://lv2plug.in/plugins/eg-sampler#sample>
- a lv2:Parameter ;
- rdfs:label "sample" ;
- rdfs:range atom:Path .
-
-<http://lv2plug.in/plugins/eg-sampler>
- a lv2:Plugin ;
- doap:name "Exampler" ;
- doap:license <http://opensource.org/licenses/isc> ;
- lv2:project <http://lv2plug.in/ns/lv2> ;
- lv2:requiredFeature state:loadDefaultState ,
- urid:map ,
- work:schedule ;
- lv2:optionalFeature lv2:hardRTCapable ,
- state:threadSafeRestore ;
- lv2:extensionData state:interface ,
- work:interface ;
- ui:ui <http://lv2plug.in/plugins/eg-sampler#ui> ;
- patch:writable <http://lv2plug.in/plugins/eg-sampler#sample> ,
- param:gain ;
- lv2:port [
- a lv2:InputPort ,
- atom:AtomPort ;
- atom:bufferType atom:Sequence ;
- atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ,
- patch:Message ;
- lv2:designation lv2:control ;
- lv2:index 0 ;
- lv2:symbol "control" ;
- lv2:name "Control"
- ] , [
- a lv2:OutputPort ,
- atom:AtomPort ;
- atom:bufferType atom:Sequence ;
- atom:supports patch:Message ;
- lv2:designation lv2:control ;
- lv2:index 1 ;
- lv2:symbol "notify" ;
- lv2:name "Notify"
- ] , [
- a lv2:AudioPort ,
- lv2:OutputPort ;
- lv2:index 2 ;
- lv2:symbol "out" ;
- lv2:name "Out"
- ] ;
- state:state [
- <http://lv2plug.in/plugins/eg-sampler#sample> <click.wav> ;
- param:gain "0.0"^^xsd:float
- ] .
-
-<http://lv2plug.in/plugins/eg-sampler#ui>
- a ui:GtkUI ;
- lv2:requiredFeature urid:map ;
- lv2:optionalFeature ui:requestValue ;
- lv2:extensionData ui:showInterface ;
- ui:portNotification [
- ui:plugin <http://lv2plug.in/plugins/eg-sampler> ;
- lv2:symbol "notify" ;
- ui:notifyType atom:Blank
- ] .
diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c
deleted file mode 100644
index 769dde0..0000000
--- a/plugins/eg-sampler.lv2/sampler_ui.c
+++ /dev/null
@@ -1,458 +0,0 @@
-// Copyright 2011-2016 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#include "peaks.h"
-#include "uris.h"
-
-#include <lv2/atom/atom.h>
-#include <lv2/atom/forge.h>
-#include <lv2/atom/util.h>
-#include <lv2/core/lv2.h>
-#include <lv2/core/lv2_util.h>
-#include <lv2/log/log.h>
-#include <lv2/log/logger.h>
-#include <lv2/midi/midi.h>
-#include <lv2/ui/ui.h>
-#include <lv2/urid/urid.h>
-
-#include <cairo.h>
-#include <gdk/gdk.h>
-#include <glib-object.h>
-#include <glib.h>
-#include <gobject/gclosure.h>
-#include <gtk/gtk.h>
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui"
-
-#define MIN_CANVAS_W 128
-#define MIN_CANVAS_H 80
-
-typedef struct {
- LV2_Atom_Forge forge;
- LV2_URID_Map* map;
- LV2UI_Request_Value* request_value;
- LV2_Log_Logger logger;
- SamplerURIs uris;
- PeaksReceiver precv;
-
- LV2UI_Write_Function write;
- LV2UI_Controller controller;
-
- GtkWidget* box;
- GtkWidget* play_button;
- GtkWidget* file_button;
- GtkWidget* request_file_button;
- GtkWidget* button_box;
- GtkWidget* canvas;
-
- uint32_t width;
- uint32_t requested_n_peaks;
- char* filename;
-
- uint8_t forge_buf[1024];
-
- // Optional show/hide interface
- GtkWidget* window;
- bool did_init;
-} SamplerUI;
-
-static void
-on_file_set(GtkFileChooserButton* widget, void* handle)
-{
- SamplerUI* ui = (SamplerUI*)handle;
-
- // Get the filename from the file chooser
- char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
-
- // Write a set message to the plugin to load new file
- lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
- LV2_Atom* msg = (LV2_Atom*)write_set_file(
- &ui->forge, &ui->uris, filename, strlen(filename));
-
- assert(msg);
-
- ui->write(ui->controller,
- 0,
- lv2_atom_total_size(msg),
- ui->uris.atom_eventTransfer,
- msg);
-
- g_free(filename);
-}
-
-static void
-on_request_file(GtkButton* widget, void* handle)
-{
- SamplerUI* ui = (SamplerUI*)handle;
-
- ui->request_value->request(
- ui->request_value->handle, ui->uris.eg_sample, 0, NULL);
-}
-
-static void
-on_play_clicked(GtkFileChooserButton* widget, void* handle)
-{
- SamplerUI* ui = (SamplerUI*)handle;
- struct {
- LV2_Atom atom;
- uint8_t msg[3];
- } note_on;
-
- note_on.atom.type = ui->uris.midi_Event;
- note_on.atom.size = 3;
- note_on.msg[0] = LV2_MIDI_MSG_NOTE_ON;
- note_on.msg[1] = 60;
- note_on.msg[2] = 60;
- ui->write(ui->controller,
- 0,
- sizeof(LV2_Atom) + 3,
- ui->uris.atom_eventTransfer,
- &note_on);
-}
-
-static void
-request_peaks(SamplerUI* ui, uint32_t n_peaks)
-{
- if (n_peaks == ui->requested_n_peaks) {
- return;
- }
-
- lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
-
- LV2_Atom_Forge_Frame frame;
- lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.patch_Get);
- lv2_atom_forge_key(&ui->forge, ui->uris.patch_accept);
- lv2_atom_forge_urid(&ui->forge, ui->precv.uris.peaks_PeakUpdate);
- lv2_atom_forge_key(&ui->forge, ui->precv.uris.peaks_total);
- lv2_atom_forge_int(&ui->forge, n_peaks);
- lv2_atom_forge_pop(&ui->forge, &frame);
-
- LV2_Atom* msg = lv2_atom_forge_deref(&ui->forge, frame.ref);
- ui->write(ui->controller,
- 0,
- lv2_atom_total_size(msg),
- ui->uris.atom_eventTransfer,
- msg);
-
- ui->requested_n_peaks = n_peaks;
-}
-
-/** Set Cairo color to a GDK color (to follow Gtk theme). */
-static void
-cairo_set_source_gdk(cairo_t* cr, const GdkColor* color)
-{
- cairo_set_source_rgb(
- cr, color->red / 65535.0, color->green / 65535.0, color->blue / 65535.0);
-}
-
-static gboolean
-on_canvas_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
-{
- SamplerUI* ui = (SamplerUI*)data;
-
- GtkAllocation size;
- gtk_widget_get_allocation(widget, &size);
-
- ui->width = size.width;
- if (ui->width > 2 * ui->requested_n_peaks) {
- request_peaks(ui, 2 * ui->requested_n_peaks);
- }
-
- cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
-
- cairo_set_line_width(cr, 1.0);
- cairo_translate(cr, 0.5, 0.5);
-
- const double mid_y = size.height / 2.0;
-
- const float* const peaks = ui->precv.peaks;
- const int32_t n_peaks = ui->precv.n_peaks;
- if (peaks) {
- // Draw waveform
- const double scale = size.width / ((double)n_peaks - 1.0f);
-
- // Start at left origin
- cairo_move_to(cr, 0, mid_y);
-
- // Draw line through top peaks
- for (int i = 0; i < n_peaks; ++i) {
- const float peak = peaks[i];
- cairo_line_to(cr, i * scale, mid_y + ((peak / 2.0f) * size.height));
- }
-
- // Continue through bottom peaks
- for (int i = n_peaks - 1; i >= 0; --i) {
- const float peak = peaks[i];
- cairo_line_to(cr, i * scale, mid_y - ((peak / 2.0f) * size.height));
- }
-
- // Close shape
- cairo_line_to(cr, 0, mid_y);
-
- cairo_set_source_gdk(cr, widget->style->mid);
- cairo_fill_preserve(cr);
-
- cairo_set_source_gdk(cr, widget->style->fg);
- cairo_stroke(cr);
- }
-
- cairo_destroy(cr);
- return TRUE;
-}
-
-static void
-destroy_window(SamplerUI* ui)
-{
- if (ui->window) {
- gtk_container_remove(GTK_CONTAINER(ui->window), ui->box);
- gtk_widget_destroy(ui->window);
- ui->window = NULL;
- }
-}
-
-static gboolean
-on_window_closed(GtkWidget* widget, GdkEvent* event, gpointer data)
-{
- SamplerUI* ui = (SamplerUI*)data;
-
- // Remove widget so Gtk doesn't delete it when the window is closed
- gtk_container_remove(GTK_CONTAINER(ui->window), ui->box);
- ui->window = NULL;
-
- return FALSE;
-}
-
-static LV2UI_Handle
-instantiate(const LV2UI_Descriptor* descriptor,
- const char* plugin_uri,
- const char* bundle_path,
- LV2UI_Write_Function write_function,
- LV2UI_Controller controller,
- LV2UI_Widget* widget,
- const LV2_Feature* const* features)
-{
- SamplerUI* ui = (SamplerUI*)calloc(1, sizeof(SamplerUI));
- if (!ui) {
- return NULL;
- }
-
- ui->write = write_function;
- ui->controller = controller;
- ui->width = MIN_CANVAS_W;
- *widget = NULL;
- ui->window = NULL;
- ui->did_init = false;
-
- // Get host features
- // clang-format off
- const char* missing = lv2_features_query(
- features,
- LV2_LOG__log, &ui->logger.log, false,
- LV2_URID__map, &ui->map, true,
- LV2_UI__requestValue, &ui->request_value, false,
- NULL);
- // clang-format on
-
- lv2_log_logger_set_map(&ui->logger, ui->map);
- if (missing) {
- lv2_log_error(&ui->logger, "Missing feature <%s>\n", missing);
- free(ui);
- return NULL;
- }
-
- // Map URIs and initialise forge
- map_sampler_uris(ui->map, &ui->uris);
- lv2_atom_forge_init(&ui->forge, ui->map);
- peaks_receiver_init(&ui->precv, ui->map);
-
- // Construct Gtk UI
- ui->box = gtk_vbox_new(FALSE, 4);
- ui->play_button = gtk_button_new_with_label("▶");
- ui->canvas = gtk_drawing_area_new();
- ui->button_box = gtk_hbox_new(FALSE, 4);
- ui->file_button =
- gtk_file_chooser_button_new("Load Sample", GTK_FILE_CHOOSER_ACTION_OPEN);
- ui->request_file_button = gtk_button_new_with_label("Request Sample");
- gtk_widget_set_size_request(ui->canvas, MIN_CANVAS_W, MIN_CANVAS_H);
- gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4);
- gtk_box_pack_start(GTK_BOX(ui->box), ui->canvas, TRUE, TRUE, 0);
- gtk_box_pack_start(GTK_BOX(ui->box), ui->button_box, FALSE, TRUE, 0);
- gtk_box_pack_start(GTK_BOX(ui->button_box), ui->play_button, FALSE, FALSE, 0);
- gtk_box_pack_start(
- GTK_BOX(ui->button_box), ui->request_file_button, FALSE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(ui->button_box), ui->file_button, TRUE, TRUE, 0);
-
- g_signal_connect(ui->file_button, "file-set", G_CALLBACK(on_file_set), ui);
-
- g_signal_connect(
- ui->request_file_button, "clicked", G_CALLBACK(on_request_file), ui);
-
- g_signal_connect(ui->play_button, "clicked", G_CALLBACK(on_play_clicked), ui);
-
- g_signal_connect(
- G_OBJECT(ui->canvas), "expose_event", G_CALLBACK(on_canvas_expose), ui);
-
- // Request state (filename) from plugin
- lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
- LV2_Atom_Forge_Frame frame;
- LV2_Atom* msg =
- (LV2_Atom*)lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.patch_Get);
-
- assert(msg);
-
- lv2_atom_forge_pop(&ui->forge, &frame);
-
- ui->write(ui->controller,
- 0,
- lv2_atom_total_size(msg),
- ui->uris.atom_eventTransfer,
- msg);
-
- *widget = ui->box;
-
- return ui;
-}
-
-static void
-cleanup(LV2UI_Handle handle)
-{
- SamplerUI* ui = (SamplerUI*)handle;
-
- if (ui->window) {
- destroy_window(ui);
- }
-
- gtk_widget_destroy(ui->canvas);
- gtk_widget_destroy(ui->play_button);
- gtk_widget_destroy(ui->file_button);
- gtk_widget_destroy(ui->request_file_button);
- gtk_widget_destroy(ui->button_box);
- gtk_widget_destroy(ui->box);
- free(ui);
-}
-
-static void
-port_event(LV2UI_Handle handle,
- uint32_t port_index,
- uint32_t buffer_size,
- uint32_t format,
- const void* buffer)
-{
- SamplerUI* ui = (SamplerUI*)handle;
- if (format == ui->uris.atom_eventTransfer) {
- const LV2_Atom* atom = (const LV2_Atom*)buffer;
- if (lv2_atom_forge_is_object_type(&ui->forge, atom->type)) {
- const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
- if (obj->body.otype == ui->uris.patch_Set) {
- const char* path = read_set_file(&ui->uris, obj);
- if (path && (!ui->filename || !!strcmp(path, ui->filename))) {
- g_free(ui->filename);
- ui->filename = g_strdup(path);
- gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(ui->file_button),
- path);
- peaks_receiver_clear(&ui->precv);
- ui->requested_n_peaks = 0;
- request_peaks(ui, ui->width / 2 * 2);
- } else if (!path) {
- lv2_log_warning(&ui->logger, "Set message has no path\n");
- }
- } else if (obj->body.otype == ui->precv.uris.peaks_PeakUpdate) {
- if (!peaks_receiver_receive(&ui->precv, obj)) {
- gtk_widget_queue_draw(ui->canvas);
- }
- }
- } else {
- lv2_log_error(&ui->logger, "Unknown message type\n");
- }
- } else {
- lv2_log_warning(&ui->logger, "Unknown port event format\n");
- }
-}
-
-/* Optional non-embedded UI show interface. */
-static int
-ui_show(LV2UI_Handle handle)
-{
- SamplerUI* ui = (SamplerUI*)handle;
-
- if (ui->window) {
- return 0;
- }
-
- if (!ui->did_init) {
- int argc = 0;
- gtk_init_check(&argc, NULL);
- g_object_ref(ui->box);
- ui->did_init = true;
- }
-
- ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_container_add(GTK_CONTAINER(ui->window), ui->box);
-
- g_signal_connect(
- G_OBJECT(ui->window), "delete-event", G_CALLBACK(on_window_closed), handle);
-
- gtk_widget_show_all(ui->window);
- gtk_window_present(GTK_WINDOW(ui->window));
-
- return 0;
-}
-
-/* Optional non-embedded UI hide interface. */
-static int
-ui_hide(LV2UI_Handle handle)
-{
- SamplerUI* ui = (SamplerUI*)handle;
-
- if (ui->window) {
- destroy_window(ui);
- }
-
- return 0;
-}
-
-/* Idle interface for optional non-embedded UI. */
-static int
-ui_idle(LV2UI_Handle handle)
-{
- const SamplerUI* ui = (const SamplerUI*)handle;
- if (ui->window) {
- gtk_main_iteration_do(false);
- }
- return 0;
-}
-
-static const void*
-extension_data(const char* uri)
-{
- static const LV2UI_Show_Interface show = {ui_show, ui_hide};
- static const LV2UI_Idle_Interface idle = {ui_idle};
-
- if (!strcmp(uri, LV2_UI__showInterface)) {
- return &show;
- }
-
- if (!strcmp(uri, LV2_UI__idleInterface)) {
- return &idle;
- }
-
- return NULL;
-}
-
-static const LV2UI_Descriptor descriptor = {SAMPLER_UI_URI,
- instantiate,
- cleanup,
- port_event,
- extension_data};
-
-LV2_SYMBOL_EXPORT const LV2UI_Descriptor*
-lv2ui_descriptor(uint32_t index)
-{
- return index == 0 ? &descriptor : NULL;
-}
diff --git a/plugins/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h
deleted file mode 100644
index 9922f51..0000000
--- a/plugins/eg-sampler.lv2/uris.h
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2011-2016 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#ifndef SAMPLER_URIS_H
-#define SAMPLER_URIS_H
-
-#include <lv2/atom/atom.h>
-#include <lv2/atom/forge.h>
-#include <lv2/atom/util.h>
-#include <lv2/midi/midi.h>
-#include <lv2/parameters/parameters.h>
-#include <lv2/patch/patch.h>
-#include <lv2/urid/urid.h>
-
-#include <stdint.h>
-#include <stdio.h>
-
-#define EG_SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler"
-#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample"
-#define EG_SAMPLER__freeSample EG_SAMPLER_URI "#freeSample"
-#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample"
-
-typedef struct {
- LV2_URID atom_Float;
- LV2_URID atom_Path;
- LV2_URID atom_Resource;
- LV2_URID atom_Sequence;
- LV2_URID atom_URID;
- LV2_URID atom_eventTransfer;
- LV2_URID eg_applySample;
- LV2_URID eg_freeSample;
- LV2_URID eg_sample;
- LV2_URID midi_Event;
- LV2_URID param_gain;
- LV2_URID patch_Get;
- LV2_URID patch_Set;
- LV2_URID patch_accept;
- LV2_URID patch_property;
- LV2_URID patch_value;
-} SamplerURIs;
-
-static inline void
-map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris)
-{
- uris->atom_Float = map->map(map->handle, LV2_ATOM__Float);
- uris->atom_Path = map->map(map->handle, LV2_ATOM__Path);
- uris->atom_Resource = map->map(map->handle, LV2_ATOM__Resource);
- uris->atom_Sequence = map->map(map->handle, LV2_ATOM__Sequence);
- uris->atom_URID = map->map(map->handle, LV2_ATOM__URID);
- uris->atom_eventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer);
- uris->eg_applySample = map->map(map->handle, EG_SAMPLER__applySample);
- uris->eg_freeSample = map->map(map->handle, EG_SAMPLER__freeSample);
- uris->eg_sample = map->map(map->handle, EG_SAMPLER__sample);
- uris->midi_Event = map->map(map->handle, LV2_MIDI__MidiEvent);
- uris->param_gain = map->map(map->handle, LV2_PARAMETERS__gain);
- uris->patch_Get = map->map(map->handle, LV2_PATCH__Get);
- uris->patch_Set = map->map(map->handle, LV2_PATCH__Set);
- uris->patch_accept = map->map(map->handle, LV2_PATCH__accept);
- uris->patch_property = map->map(map->handle, LV2_PATCH__property);
- uris->patch_value = map->map(map->handle, LV2_PATCH__value);
-}
-
-/**
- Write a message like the following to `forge`:
- [source,turtle]
- ----
- []
- a patch:Set ;
- patch:property param:gain ;
- patch:value 0.0f .
- ----
-*/
-static inline LV2_Atom_Forge_Ref
-write_set_gain(LV2_Atom_Forge* forge, const SamplerURIs* uris, const float gain)
-{
- LV2_Atom_Forge_Frame frame;
- LV2_Atom_Forge_Ref set =
- lv2_atom_forge_object(forge, &frame, 0, uris->patch_Set);
-
- lv2_atom_forge_key(forge, uris->patch_property);
- lv2_atom_forge_urid(forge, uris->param_gain);
- lv2_atom_forge_key(forge, uris->patch_value);
- lv2_atom_forge_float(forge, gain);
-
- lv2_atom_forge_pop(forge, &frame);
- return set;
-}
-
-/**
- Write a message like the following to `forge`:
- [source,turtle]
- ----
- []
- a patch:Set ;
- patch:property eg:sample ;
- patch:value </home/me/foo.wav> .
- ----
-*/
-static inline LV2_Atom_Forge_Ref
-write_set_file(LV2_Atom_Forge* forge,
- const SamplerURIs* uris,
- const char* filename,
- const uint32_t filename_len)
-{
- LV2_Atom_Forge_Frame frame;
- LV2_Atom_Forge_Ref set =
- lv2_atom_forge_object(forge, &frame, 0, uris->patch_Set);
-
- lv2_atom_forge_key(forge, uris->patch_property);
- lv2_atom_forge_urid(forge, uris->eg_sample);
- lv2_atom_forge_key(forge, uris->patch_value);
- lv2_atom_forge_path(forge, filename, filename_len);
-
- lv2_atom_forge_pop(forge, &frame);
- return set;
-}
-
-/**
- Get the file path from `obj` which is a message like:
- [source,turtle]
- ----
- []
- a patch:Set ;
- patch:property eg:sample ;
- patch:value </home/me/foo.wav> .
- ----
-*/
-static inline const char*
-read_set_file(const SamplerURIs* uris, const LV2_Atom_Object* obj)
-{
- if (obj->body.otype != uris->patch_Set) {
- fprintf(stderr, "Ignoring unknown message type %u\n", obj->body.otype);
- return NULL;
- }
-
- /* Get property URI. */
- const LV2_Atom* property = NULL;
- lv2_atom_object_get(obj, uris->patch_property, &property, 0);
- if (!property) {
- fprintf(stderr, "Malformed set message has no body.\n");
- return NULL;
- }
-
- if (property->type != uris->atom_URID) {
- fprintf(stderr, "Malformed set message has non-URID property.\n");
- return NULL;
- }
-
- if (((const LV2_Atom_URID*)property)->body != uris->eg_sample) {
- fprintf(stderr, "Set message for unknown property.\n");
- return NULL;
- }
-
- /* Get value. */
- const LV2_Atom* value = NULL;
- lv2_atom_object_get(obj, uris->patch_value, &value, 0);
- if (!value) {
- fprintf(stderr, "Malformed set message has no value.\n");
- return NULL;
- }
-
- if (value->type != uris->atom_Path) {
- fprintf(stderr, "Set message value is not a Path.\n");
- return NULL;
- }
-
- return (const char*)&value[1];
-}
-
-#endif /* SAMPLER_URIS_H */