mirror of https://github.com/dcoredump/dexed.git
parent
28fb4417b0
commit
8a765b347a
@ -1,3 +1,4 @@ |
||||
*.o |
||||
*.so |
||||
*.gch |
||||
*.swp |
||||
|
@ -1,3 +1,5 @@ |
||||
// from: http://ll-plugins.nongnu.org/lv2pftci/#A_synth
|
||||
|
||||
#include <lv2synth.hpp> |
||||
#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