mirror of https://github.com/dcoredump/dexed.git
parent
28fb4417b0
commit
8a765b347a
@ -1,3 +1,4 @@ |
|||||||
*.o |
*.o |
||||||
*.so |
*.so |
||||||
*.gch |
*.gch |
||||||
|
*.swp |
||||||
|
@ -1,3 +1,5 @@ |
|||||||
|
// from: http://ll-plugins.nongnu.org/lv2pftci/#A_synth
|
||||||
|
|
||||||
#include <lv2synth.hpp> |
#include <lv2synth.hpp> |
||||||
#include "beep.peg" |
#include "beep.peg" |
||||||
|
|
@ -0,0 +1,13 @@ |
|||||||
|
== 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 |
@ -0,0 +1,40 @@ |
|||||||
|
/*
|
||||||
|
Copyright 2016 David Robillard <d@drobilla.net> |
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any |
||||||
|
purpose with or without fee is hereby granted, provided that the above |
||||||
|
copyright notice and this permission notice appear in all copies. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
/**
|
||||||
|
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); |
||||||
|
} |
Binary file not shown.
@ -0,0 +1,19 @@ |
|||||||
|
# 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 ; |
||||||
|
ui:binary <sampler_ui@LIB_EXT@> ; |
||||||
|
rdfs:seeAlso <sampler.ttl> . |
@ -0,0 +1,552 @@ |
|||||||
|
/*
|
||||||
|
LV2 Sampler Example Plugin |
||||||
|
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> |
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any |
||||||
|
purpose with or without fee is hereby granted, provided that the above |
||||||
|
copyright notice and this permission notice appear in all copies. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <math.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#ifndef __cplusplus |
||||||
|
# include <stdbool.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <sndfile.h> |
||||||
|
|
||||||
|
#include "lv2/lv2plug.in/ns/ext/atom/forge.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/atom/util.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/log/log.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/log/logger.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/midi/midi.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/patch/patch.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/state/state.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/urid/urid.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/worker/worker.h" |
||||||
|
#include "lv2/lv2plug.in/ns/lv2core/lv2.h" |
||||||
|
#include "lv2/lv2plug.in/ns/lv2core/lv2_util.h" |
||||||
|
|
||||||
|
#include "uris.h" |
||||||
|
#include "atom_sink.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; |
||||||
|
|
||||||
|
// Forge for creating atoms
|
||||||
|
LV2_Atom_Forge forge; |
||||||
|
|
||||||
|
// Ports
|
||||||
|
const LV2_Atom_Sequence* control_port; |
||||||
|
LV2_Atom_Sequence* notify_port; |
||||||
|
float* output_port; |
||||||
|
|
||||||
|
// Forge frame for notify port (for writing worker replies)
|
||||||
|
LV2_Atom_Forge_Frame notify_frame; |
||||||
|
|
||||||
|
// URIs
|
||||||
|
SamplerURIs uris; |
||||||
|
|
||||||
|
// Playback state
|
||||||
|
Sample* sample; |
||||||
|
uint32_t frame_offset; |
||||||
|
float gain; |
||||||
|
sf_count_t frame; |
||||||
|
bool play; |
||||||
|
bool activated; |
||||||
|
bool sample_changed; |
||||||
|
} 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; |
||||||
|
|
||||||
|
/**
|
||||||
|
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) |
||||||
|
{ |
||||||
|
lv2_log_trace(logger, "Loading %s\n", path); |
||||||
|
|
||||||
|
const size_t path_len = strlen(path); |
||||||
|
Sample* const sample = (Sample*)malloc(sizeof(Sample)); |
||||||
|
SF_INFO* const info = &sample->info; |
||||||
|
SNDFILE* const sndfile = sf_open(path, SFM_READ, info); |
||||||
|
|
||||||
|
if (!sndfile || !info->frames || (info->channels != 1)) { |
||||||
|
lv2_log_error(logger, "Failed to open sample '%s'\n", path); |
||||||
|
free(sample); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
// Read data
|
||||||
|
float* const data = (float*)malloc(sizeof(float) * info->frames); |
||||||
|
if (!data) { |
||||||
|
lv2_log_error(logger, "Failed to allocate memory for sample\n"); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
sf_seek(sndfile, 0ul, SEEK_SET); |
||||||
|
sf_read_float(sndfile, data, info->frames); |
||||||
|
sf_close(sndfile); |
||||||
|
|
||||||
|
// 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); |
||||||
|
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; |
||||||
|
Sample* new_sample = *(Sample*const*)data; |
||||||
|
|
||||||
|
// Install the new sample
|
||||||
|
self->sample = *(Sample*const*)data; |
||||||
|
|
||||||
|
// 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); |
||||||
|
|
||||||
|
if (strcmp(old_sample->path, new_sample->path)) { |
||||||
|
// 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
|
||||||
|
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); |
||||||
|
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); |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
static void |
||||||
|
run(LV2_Handle instance, |
||||||
|
uint32_t sample_count) |
||||||
|
{ |
||||||
|
Sampler* self = (Sampler*)instance; |
||||||
|
SamplerURIs* uris = &self->uris; |
||||||
|
sf_count_t start_frame = 0; |
||||||
|
sf_count_t pos = 0; |
||||||
|
float* output = self->output_port; |
||||||
|
|
||||||
|
// 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 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; |
||||||
|
} |
||||||
|
|
||||||
|
// Read incoming events
|
||||||
|
LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) { |
||||||
|
self->frame_offset = ev->time.frames; |
||||||
|
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: |
||||||
|
start_frame = ev->time.frames; |
||||||
|
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; |
||||||
|
lv2_atom_object_get(obj, |
||||||
|
uris->patch_property, &property, |
||||||
|
uris->patch_value, &value, |
||||||
|
0); |
||||||
|
if (!property) { |
||||||
|
lv2_log_error(&self->logger, |
||||||
|
"patch:Set message with no property\n"); |
||||||
|
continue; |
||||||
|
} else if (property->type != uris->atom_URID) { |
||||||
|
lv2_log_error(&self->logger, |
||||||
|
"patch:Set property is not a URID\n"); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
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_CO(((LV2_Atom_Float*)value)->body); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (obj->body.otype == uris->patch_Get) { |
||||||
|
// 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 %d\n", obj->body.otype); |
||||||
|
} |
||||||
|
} else { |
||||||
|
lv2_log_trace(&self->logger, |
||||||
|
"Unknown event type %d\n", ev->body.type); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Render the sample (possibly already in progress)
|
||||||
|
if (self->play) { |
||||||
|
uint32_t f = self->frame; |
||||||
|
const uint32_t lf = self->sample->info.frames; |
||||||
|
|
||||||
|
for (pos = 0; pos < start_frame; ++pos) { |
||||||
|
output[pos] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
for (; pos < sample_count && f < lf; ++pos, ++f) { |
||||||
|
output[pos] = self->sample->data[f] * self->gain; |
||||||
|
} |
||||||
|
|
||||||
|
self->frame = f; |
||||||
|
|
||||||
|
if (f == lf) { |
||||||
|
self->play = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Add zeros to end if sample not long enough (or not playing)
|
||||||
|
for (; pos < sample_count; ++pos) { |
||||||
|
output[pos] = 0.0f; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
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; |
||||||
|
const char* missing = lv2_features_query( |
||||||
|
features, |
||||||
|
LV2_STATE__mapPath, &paths, true, |
||||||
|
LV2_WORKER__schedule, &schedule, false, |
||||||
|
NULL); |
||||||
|
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; |
||||||
|
uint32_t type; |
||||||
|
uint32_t valflags; |
||||||
|
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; |
||||||
|
} else 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"); |
||||||
|
free_sample(self, self->sample); |
||||||
|
self->sample = load_sample(&self->logger, path); |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} else 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) |
||||||
|
{ |
||||||
|
switch (index) { |
||||||
|
case 0: |
||||||
|
return &descriptor; |
||||||
|
default: |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
@prefix atom: <http://lv2plug.in/ns/ext/atom#> . |
||||||
|
@prefix doap: <http://usefulinc.com/ns/doap#> . |
||||||
|
@prefix lv2: <http://lv2plug.in/ns/lv2core#> . |
||||||
|
@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 param: <http://lv2plug.in/ns/ext/parameters#> . |
||||||
|
|
||||||
|
<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 "Example Sampler" ; |
||||||
|
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> |
||||||
|
] . |
||||||
|
|
||||||
|
<http://lv2plug.in/plugins/eg-sampler#ui> |
||||||
|
a ui:GtkUI ; |
||||||
|
lv2:requiredFeature urid:map ; |
||||||
|
lv2:extensionData ui:showInterface ; |
||||||
|
ui:portNotification [ |
||||||
|
ui:plugin <http://lv2plug.in/plugins/eg-sampler> ; |
||||||
|
lv2:symbol "notify" ; |
||||||
|
ui:notifyType atom:Blank |
||||||
|
] . |
@ -0,0 +1,254 @@ |
|||||||
|
/*
|
||||||
|
LV2 Sampler Example Plugin UI |
||||||
|
Copyright 2011-2016 David Robillard <d@drobilla.net> |
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any |
||||||
|
purpose with or without fee is hereby granted, provided that the above |
||||||
|
copyright notice and this permission notice appear in all copies. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
#include <gtk/gtk.h> |
||||||
|
|
||||||
|
#include "lv2/lv2plug.in/ns/ext/atom/atom.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/atom/forge.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/atom/util.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/log/logger.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/patch/patch.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/urid/urid.h" |
||||||
|
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" |
||||||
|
#include "lv2/lv2plug.in/ns/lv2core/lv2_util.h" |
||||||
|
|
||||||
|
#include "./uris.h" |
||||||
|
|
||||||
|
#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui"
|
||||||
|
|
||||||
|
typedef struct { |
||||||
|
LV2_Atom_Forge forge; |
||||||
|
LV2_URID_Map* map; |
||||||
|
LV2_Log_Logger logger; |
||||||
|
SamplerURIs uris; |
||||||
|
|
||||||
|
LV2UI_Write_Function write; |
||||||
|
LV2UI_Controller controller; |
||||||
|
|
||||||
|
GtkWidget* box; |
||||||
|
GtkWidget* button; |
||||||
|
GtkWidget* label; |
||||||
|
GtkWidget* window; /* For optional show interface. */ |
||||||
|
} SamplerUI; |
||||||
|
|
||||||
|
static void |
||||||
|
on_load_clicked(GtkWidget* widget, |
||||||
|
void* handle) |
||||||
|
{ |
||||||
|
SamplerUI* ui = (SamplerUI*)handle; |
||||||
|
|
||||||
|
/* Create a dialog to select a sample file. */ |
||||||
|
GtkWidget* dialog = gtk_file_chooser_dialog_new( |
||||||
|
"Load Sample", |
||||||
|
NULL, |
||||||
|
GTK_FILE_CHOOSER_ACTION_OPEN, |
||||||
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, |
||||||
|
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, |
||||||
|
NULL); |
||||||
|
|
||||||
|
/* Run the dialog, and return if it is cancelled. */ |
||||||
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { |
||||||
|
gtk_widget_destroy(dialog); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
/* Get the file path from the dialog. */ |
||||||
|
char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); |
||||||
|
|
||||||
|
/* Got what we need, destroy the dialog. */ |
||||||
|
gtk_widget_destroy(dialog); |
||||||
|
|
||||||
|
#define OBJ_BUF_SIZE 1024 |
||||||
|
uint8_t obj_buf[OBJ_BUF_SIZE]; |
||||||
|
lv2_atom_forge_set_buffer(&ui->forge, obj_buf, OBJ_BUF_SIZE); |
||||||
|
|
||||||
|
LV2_Atom* msg = (LV2_Atom*)write_set_file(&ui->forge, &ui->uris, |
||||||
|
filename, strlen(filename)); |
||||||
|
|
||||||
|
ui->write(ui->controller, 0, lv2_atom_total_size(msg), |
||||||
|
ui->uris.atom_eventTransfer, |
||||||
|
msg); |
||||||
|
|
||||||
|
g_free(filename); |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
*widget = NULL; |
||||||
|
|
||||||
|
// Get host features
|
||||||
|
const char* missing = lv2_features_query( |
||||||
|
features, |
||||||
|
LV2_LOG__log, &ui->logger.log, false, |
||||||
|
LV2_URID__map, &ui->map, true, |
||||||
|
NULL); |
||||||
|
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); |
||||||
|
|
||||||
|
// Construct Gtk UI
|
||||||
|
ui->box = gtk_vbox_new(FALSE, 4); |
||||||
|
ui->label = gtk_label_new("?"); |
||||||
|
ui->button = gtk_button_new_with_label("Load Sample"); |
||||||
|
gtk_box_pack_start(GTK_BOX(ui->box), ui->label, TRUE, TRUE, 4); |
||||||
|
gtk_box_pack_start(GTK_BOX(ui->box), ui->button, FALSE, FALSE, 4); |
||||||
|
g_signal_connect(ui->button, "clicked", |
||||||
|
G_CALLBACK(on_load_clicked), |
||||||
|
ui); |
||||||
|
|
||||||
|
// Request state (filename) from plugin
|
||||||
|
uint8_t get_buf[512]; |
||||||
|
lv2_atom_forge_set_buffer(&ui->forge, get_buf, sizeof(get_buf)); |
||||||
|
|
||||||
|
LV2_Atom_Forge_Frame frame; |
||||||
|
LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( |
||||||
|
&ui->forge, &frame, 0, ui->uris.patch_Get); |
||||||
|
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; |
||||||
|
gtk_widget_destroy(ui->button); |
||||||
|
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; |
||||||
|
const char* uri = read_set_file(&ui->uris, obj); |
||||||
|
if (uri) { |
||||||
|
gtk_label_set_text(GTK_LABEL(ui->label), uri); |
||||||
|
} else { |
||||||
|
lv2_log_warning(&ui->logger, "Malformed message\n"); |
||||||
|
} |
||||||
|
} 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; |
||||||
|
|
||||||
|
int argc = 0; |
||||||
|
gtk_init(&argc, NULL); |
||||||
|
|
||||||
|
ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
||||||
|
gtk_container_add(GTK_CONTAINER(ui->window), ui->box); |
||||||
|
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) |
||||||
|
{ |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* Idle interface for optional non-embedded UI. */ |
||||||
|
static int |
||||||
|
ui_idle(LV2UI_Handle handle) |
||||||
|
{ |
||||||
|
SamplerUI* ui = (SamplerUI*)handle; |
||||||
|
if (ui->window) { |
||||||
|
gtk_main_iteration(); |
||||||
|
} |
||||||
|
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; |
||||||
|
} else 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) |
||||||
|
{ |
||||||
|
switch (index) { |
||||||
|
case 0: |
||||||
|
return &descriptor; |
||||||
|
default: |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
/*
|
||||||
|
LV2 Sampler Example Plugin |
||||||
|
Copyright 2011-2016 David Robillard <d@drobilla.net> |
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any |
||||||
|
purpose with or without fee is hereby granted, provided that the above |
||||||
|
copyright notice and this permission notice appear in all copies. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef SAMPLER_URIS_H |
||||||
|
#define SAMPLER_URIS_H |
||||||
|
|
||||||
|
#include "lv2/lv2plug.in/ns/ext/log/log.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/midi/midi.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/state/state.h" |
||||||
|
#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h" |
||||||
|
|
||||||
|
#define EG_SAMPLER_URI "http://lv2plug.in/plugins/eg-sampler"
|
||||||
|
#define EG_SAMPLER__sample EG_SAMPLER_URI "#sample" |
||||||
|
#define EG_SAMPLER__applySample EG_SAMPLER_URI "#applySample" |
||||||
|
#define EG_SAMPLER__freeSample EG_SAMPLER_URI "#freeSample" |
||||||
|
|
||||||
|
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_sample; |
||||||
|
LV2_URID eg_freeSample; |
||||||
|
LV2_URID midi_Event; |
||||||
|
LV2_URID param_gain; |
||||||
|
LV2_URID patch_Get; |
||||||
|
LV2_URID patch_Set; |
||||||
|
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_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 @p forge: |
||||||
|
* [] |
||||||
|
* 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 a message like: |
||||||
|
* [] |
||||||
|
* 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 %d\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; |
||||||
|
} else if (property->type != uris->atom_URID) { |
||||||
|
fprintf(stderr, "Malformed set message has non-URID property.\n"); |
||||||
|
return NULL; |
||||||
|
} else 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; |
||||||
|
} else if (value->type != uris->atom_Path) { |
||||||
|
fprintf(stderr, "Set message value is not a Path.\n"); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
return (const char*)LV2_ATOM_BODY_CONST(value); |
||||||
|
} |
||||||
|
|
||||||
|
#endif /* SAMPLER_URIS_H */ |
@ -0,0 +1 @@ |
|||||||
|
../../waf |
@ -0,0 +1,83 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
from waflib.extras import autowaf as autowaf |
||||||
|
import re |
||||||
|
|
||||||
|
# Variables for 'waf dist' |
||||||
|
APPNAME = 'eg-sampler.lv2' |
||||||
|
VERSION = '1.0.0' |
||||||
|
|
||||||
|
# Mandatory variables |
||||||
|
top = '.' |
||||||
|
out = 'build' |
||||||
|
|
||||||
|
def options(opt): |
||||||
|
opt.load('compiler_c') |
||||||
|
opt.load('lv2') |
||||||
|
autowaf.set_options(opt) |
||||||
|
|
||||||
|
def configure(conf): |
||||||
|
conf.load('compiler_c') |
||||||
|
conf.load('lv2') |
||||||
|
autowaf.configure(conf) |
||||||
|
autowaf.set_c99_mode(conf) |
||||||
|
autowaf.display_header('Sampler Configuration') |
||||||
|
|
||||||
|
if not autowaf.is_child(): |
||||||
|
autowaf.check_pkg(conf, 'lv2', atleast_version='1.2.1', uselib_store='LV2') |
||||||
|
|
||||||
|
autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', |
||||||
|
atleast_version='1.0.0', mandatory=True) |
||||||
|
autowaf.check_pkg(conf, 'gtk+-2.0', uselib_store='GTK2', |
||||||
|
atleast_version='2.18.0', mandatory=False) |
||||||
|
conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) |
||||||
|
|
||||||
|
autowaf.display_msg(conf, 'LV2 bundle directory', conf.env.LV2DIR) |
||||||
|
print('') |
||||||
|
|
||||||
|
def build(bld): |
||||||
|
bundle = 'eg-sampler.lv2' |
||||||
|
|
||||||
|
# Make a pattern for shared objects without the 'lib' prefix |
||||||
|
module_pat = re.sub('^lib', '', bld.env.cshlib_PATTERN) |
||||||
|
module_ext = module_pat[module_pat.rfind('.'):] |
||||||
|
|
||||||
|
# Build manifest.ttl by substitution (for portable lib extension) |
||||||
|
bld(features = 'subst', |
||||||
|
source = 'manifest.ttl.in', |
||||||
|
target = '%s/%s' % (bundle, 'manifest.ttl'), |
||||||
|
install_path = '${LV2DIR}/%s' % bundle, |
||||||
|
LIB_EXT = module_ext) |
||||||
|
|
||||||
|
# Copy other data files to build bundle (build/eg-sampler.lv2) |
||||||
|
for i in ['sampler.ttl', 'click.wav']: |
||||||
|
bld(features = 'subst', |
||||||
|
is_copy = True, |
||||||
|
source = i, |
||||||
|
target = '%s/%s' % (bundle, i), |
||||||
|
install_path = '${LV2DIR}/%s' % bundle) |
||||||
|
|
||||||
|
# Use LV2 headers from parent directory if building as a sub-project |
||||||
|
includes = ['.'] |
||||||
|
if autowaf.is_child: |
||||||
|
includes += ['../..'] |
||||||
|
|
||||||
|
# Build plugin library |
||||||
|
obj = bld(features = 'c cshlib', |
||||||
|
source = 'sampler.c', |
||||||
|
name = 'sampler', |
||||||
|
target = '%s/sampler' % bundle, |
||||||
|
install_path = '${LV2DIR}/%s' % bundle, |
||||||
|
use = ['M', 'SNDFILE', 'LV2'], |
||||||
|
includes = includes) |
||||||
|
obj.env.cshlib_PATTERN = module_pat |
||||||
|
|
||||||
|
# Build UI library |
||||||
|
if bld.env.HAVE_GTK2: |
||||||
|
obj = bld(features = 'c cshlib', |
||||||
|
source = 'sampler_ui.c', |
||||||
|
name = 'sampler_ui', |
||||||
|
target = '%s/sampler_ui' % bundle, |
||||||
|
install_path = '${LV2DIR}/%s' % bundle, |
||||||
|
use = ['GTK2', 'LV2'], |
||||||
|
includes = includes) |
||||||
|
obj.env.cshlib_PATTERN = module_pat |
Binary file not shown.
Loading…
Reference in new issue