diff options
Diffstat (limited to 'plugins/eg-sampler.lv2')
| -rw-r--r-- | plugins/eg-sampler.lv2/README.txt | 14 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/atom_sink.h | 34 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/click.wav | bin | 644 -> 0 bytes | |||
| -rw-r--r-- | plugins/eg-sampler.lv2/manifest.ttl.in | 19 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/meson.build | 82 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/peaks.h | 266 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler.c | 695 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler.ttl | 73 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/sampler_ui.c | 458 | ||||
| -rw-r--r-- | plugins/eg-sampler.lv2/uris.h | 170 |
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 Binary files differdeleted file mode 100644 index 520a18c..0000000 --- a/plugins/eg-sampler.lv2/click.wav +++ /dev/null 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, - ¬e_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 */ |