From 77f72ce2fb0342c755eac18a6cc0f94c2e82ae32 Mon Sep 17 00:00:00 2001 From: Alexandros Theodotou Date: Fri, 20 Mar 2020 14:21:55 +0000 Subject: [PATCH] use a worker to calculate new values --- .gitignore | 3 + ext/Soundpipe/meson.build | 3 + meson.build | 2 +- plugins/meson.build | 29 +- plugins/{supersaw => saw}/common.h | 36 +- plugins/{supersaw => saw}/manifest.ttl.in | 2 +- plugins/{supersaw/supersaw.c => saw/saw.c} | 459 ++++++++++++++------- plugins/{supersaw => saw}/ttl_gen.c | 9 +- scripts/lv2lint_wrap.sh | 22 + 9 files changed, 397 insertions(+), 168 deletions(-) rename plugins/{supersaw => saw}/common.h (87%) rename plugins/{supersaw => saw}/manifest.ttl.in (97%) rename plugins/{supersaw/supersaw.c => saw/saw.c} (57%) rename plugins/{supersaw => saw}/ttl_gen.c (93%) create mode 100755 scripts/lv2lint_wrap.sh diff --git a/.gitignore b/.gitignore index 628e411..80eded7 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ callgrind.* # subprojects subprojects/**/* !subprojects/*.wrap + +# gprof +results diff --git a/ext/Soundpipe/meson.build b/ext/Soundpipe/meson.build index 333518e..dd79401 100644 --- a/ext/Soundpipe/meson.build +++ b/ext/Soundpipe/meson.build @@ -37,4 +37,7 @@ soundpipe_lib = static_library ( dependency('sndfile'), cc.find_library('m'), ], + c_args: [ + '-fvisibility=hidden', + ], ) diff --git a/meson.build b/meson.build index 80e33c7..4b797fa 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,7 @@ project ( 'ZPlugins', ['c'], version: '0.0.1', license: 'AGPLv3+', - meson_version: '>= 0.43.0', + meson_version: '>= 0.46.0', default_options: [ 'warning_level=2', 'buildtype=debug', diff --git a/plugins/meson.build b/plugins/meson.build index c47c139..9f9296a 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -17,7 +17,7 @@ # name, version plugins = [ - ['SuperSaw', '0.0.1'], + ['Saw', '0.0.1'], ] foreach pl : plugins @@ -105,18 +105,22 @@ foreach pl : plugins link_with: soundpipe_lib, install: true, install_dir: pl_dir, - c_args: common_cflags, + c_args: [ + common_cflags, + '-DPLUGIN_CONFIG="../' + pl_lowercase + '_config.h"', + ], ) # create and install manifest ttl manifest_ttl = configure_file ( input: join_paths (pl_lowercase, 'manifest.ttl.in'), - output: 'manifest.ttl', + output: pl_str + '_manifest.ttl', configuration: pl_cdata, ) install_data ( manifest_ttl, install_dir: pl_dir, + rename: 'manifest.ttl', ) # create and install ttl @@ -128,7 +132,10 @@ foreach pl : plugins ], include_directories: pl_inc_dirs, dependencies: lv2_dep, - c_args: common_cflags, + c_args: [ + common_cflags, + '-DPLUGIN_CONFIG="../' + pl_lowercase + '_config.h"', + ], install: false, ) pl_ttl = custom_target ( @@ -148,13 +155,17 @@ foreach pl : plugins 'lv2_validate', required: false) sord_validate = find_program ( 'sord_validate', required: false) - if (lv2lint.found()) + if lv2lint.found() and (os_linux or os_freebsd) + lv2lint_wrap = find_program ( + join_paths ('..', 'scripts', 'lv2lint_wrap.sh')) test ( - 'LV2 lint', lv2lint, - env: ['LV2_PATH=' + pl_build_dir + '/'], + 'LV2 lint', lv2lint_wrap, args: [ - '-I', pl_build_dir + '/', - pl_cdata.get ('PLUGIN_URI')]) + lv2lint.path(), + pl_build_dir, + pl_str, + pl_cdata.get('PLUGIN_URI'), + ]) endif if lv2_validate.found() and sord_validate.found() test ( diff --git a/plugins/supersaw/common.h b/plugins/saw/common.h similarity index 87% rename from plugins/supersaw/common.h rename to plugins/saw/common.h index 49971ab..0ff992b 100644 --- a/plugins/supersaw/common.h +++ b/plugins/saw/common.h @@ -26,7 +26,7 @@ #ifndef __Z_SUPERSAW_COMMON_H__ #define __Z_SUPERSAW_COMMON_H__ -#include "../supersaw_config.h" +#include PLUGIN_CONFIG #include @@ -34,11 +34,13 @@ #include "lv2/atom/forge.h" #include "lv2/core/lv2.h" #include "lv2/log/log.h" +#include "lv2/log/logger.h" #include "lv2/midi/midi.h" #include "lv2/urid/urid.h" #include "lv2/time/time.h" +#include "lv2/worker/worker.h" -typedef struct SuperSawUris +typedef struct SawUris { LV2_URID atom_eventTransfer; LV2_URID atom_Blank; @@ -62,8 +64,10 @@ typedef struct SuperSawUris LV2_URID time_speed; /* custom URIs for communication */ + LV2_URID saw_calcValues; + LV2_URID saw_freeValues; -} SuperSawUris; +} SawUris; typedef enum PortIndex { @@ -103,29 +107,35 @@ typedef enum PortIndex * Group of variables needed by both the DSP and * the UI. */ -typedef struct SuperSawCommon +typedef struct SawCommon { /** Log feature. */ - LV2_Log_Log * log; + LV2_Log_Log * log; /** Map feature. */ - LV2_URID_Map * map; + LV2_URID_Map * map; + + /** Logger convenience API. */ + LV2_Log_Logger logger; + + /** Worker schedule feature. */ + LV2_Worker_Schedule* schedule; /** Atom forge. */ - LV2_Atom_Forge forge; + LV2_Atom_Forge forge; /** URIs. */ - SuperSawUris uris; + SawUris uris; /** Plugin samplerate. */ - double samplerate; + double samplerate; -} SuperSawCommon; +} SawCommon; static inline void map_uris ( LV2_URID_Map* map, - SuperSawUris* uris) + SawUris* uris) { #define MAP(x,uri) \ uris->x = map->map (map->handle, uri) @@ -154,6 +164,8 @@ map_uris ( MAP (time_speed, LV2_TIME__speed); /* custom URIs */ + MAP (saw_freeValues, PLUGIN_URI "#freeValues"); + MAP (saw_calcValues, PLUGIN_URI "#calcValues"); } /** @@ -162,7 +174,7 @@ map_uris ( static inline void log_error ( LV2_Log_Log * log, - SuperSawUris * uris, + SawUris * uris, const char * _fmt, ...) { diff --git a/plugins/supersaw/manifest.ttl.in b/plugins/saw/manifest.ttl.in similarity index 97% rename from plugins/supersaw/manifest.ttl.in rename to plugins/saw/manifest.ttl.in index 4a95cb3..1145f73 100644 --- a/plugins/supersaw/manifest.ttl.in +++ b/plugins/saw/manifest.ttl.in @@ -22,7 +22,7 @@ <@PLUGIN_URI@> a lv2:Plugin, - lv2:OscillatorPlugin ; + lv2:InstrumentPlugin ; lv2:binary <@PLUGIN_DSP_BINARY@> ; lv2:minorVersion 0; lv2:microVersion 1; diff --git a/plugins/supersaw/supersaw.c b/plugins/saw/saw.c similarity index 57% rename from plugins/supersaw/supersaw.c rename to plugins/saw/saw.c index 1b5343b..881516b 100644 --- a/plugins/supersaw/supersaw.c +++ b/plugins/saw/saw.c @@ -1,20 +1,20 @@ /* * Copyright (C) 2019-2020 Alexandros Theodotou * - * This file is part of ZSuperSaw + * This file is part of ZSaw * - * ZSuperSaw is free software: you can redistribute it and/or modify + * ZSaw is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * - * ZSuperSaw is distributed in the hope that it will be useful, + * ZSaw is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU General Affero Public License - * along with ZSuperSaw. If not, see . + * along with ZSaw. If not, see . */ #include @@ -53,13 +53,33 @@ typedef struct MidiKey } MidiKey; /** - * One voice per active key. + * Worker data. */ -/*typedef struct Voice*/ -/*{*/ -/*} Voice;*/ +typedef struct SawValues +{ + float attack; + float decay; + float sustain; + float release; + + float saturator_drive; + float saturator_dcoffset; + float distortion_shape1; + float distortion_shape2; + float reverb_mix; + float keyfreqs[128][7]; +} SawValues; + +/** + * Atom message. + */ +typedef struct SawValuesMessage +{ + LV2_Atom atom; + SawValues * values; +} SawValuesMessage; -typedef struct SuperSaw +typedef struct Saw { /** Plugin ports. */ const LV2_Atom_Sequence* control; @@ -73,11 +93,11 @@ typedef struct SuperSaw /** Events in the queue. */ MidiKey keys[128]; - /** Current values based on \ref SuperSaw.amount. */ - float attack; - float decay; - float sustain; - float release; + /** Current values based on \ref Saw.amount. */ + /*float attack;*/ + /*float decay;*/ + /*float sustain;*/ + /*float release;*/ /*int num_voices;*/ sp_saturator * saturator; @@ -85,11 +105,170 @@ typedef struct SuperSaw sp_zitarev * reverb; sp_data * sp; - SuperSawCommon common; + SawCommon common; + + SawValues * values; /* cache */ float last_amount; -} SuperSaw; +} Saw; + +/** + * To be called by the worker function. + */ +static SawValues * +calc_values ( + Saw * self) +{ + lv2_log_note ( + &self->common.logger, "calculating values\n"); + SawValues * values = calloc (1, sizeof (SawValues)); + + values->attack = 0.02f; + values->decay = 0.04f + *self->amount * 0.4f; + values->sustain = 0.5f; + values->release = 0.04f + *self->amount * 0.4f; + values->saturator_drive = *self->amount * 0.3f; + values->saturator_dcoffset = *self->amount * 0.3f; + values->distortion_shape1 = *self->amount * 0.2f; + values->distortion_shape2 = *self->amount * 0.2f; + values->reverb_mix = *self->amount * 0.5f; + + float freq_delta = + (*self->amount + 0.4f * (1.f - *self->amount)) * + 2.4f; + + for (int i = 0; i < 128; i++) + { + for (int j = 0; j < 7; j++) + { + /* voice spread */ + int is_even = (j % 2) == 0; + float freq_apart; + if (is_even) + { + freq_apart = (float) (j / 2) * freq_delta; + } + else + { + freq_apart = (float) (j / 2 + 1) * - freq_delta; + } + values->keyfreqs[i][j] = + self->keys[i].base_freq + + math_round_float_to_int (freq_apart); + } + } + + return values; +} + +/** + * Cleanup function after work is finished. + */ +static void +free_values ( + Saw * self, + SawValues * values) +{ + lv2_log_note ( + &self->common.logger, "freeing values\n"); + free (values); +} + +/** + * Sets the values from the SawValues struct on the + * actual plugin. + */ +static void +set_values ( + Saw * self, + SawValues * values) +{ + lv2_log_note ( + &self->common.logger, "setting values\n"); + self->saturator->drive = values->saturator_drive; + self->saturator->dcoffset = values->saturator_dcoffset; + self->distortion->shape1 = values->distortion_shape1; + self->distortion->shape2 = values->distortion_shape2; + *self->reverb->mix = values->reverb_mix; + + for (int i = 0; i < 128; i++) + { + MidiKey * key = &self->keys[i]; + + /* adsr */ + key->adsr->atk = values->attack; + key->adsr->dec = values->decay; + key->adsr->sus = values->sustain; + key->adsr->rel = values->release; + + for (int j = 0; j < 7; j++) + { + /* spread voices */ + *key->blsaws[j]->freq = values->keyfreqs[i][j]; + } + } + +} + +static LV2_Worker_Status +work ( + LV2_Handle instance, + LV2_Worker_Respond_Function respond, + LV2_Worker_Respond_Handle handle, + uint32_t size, + const void * data) +{ + Saw * self = (Saw *) instance; + + lv2_log_note ( + &self->common.logger, "working\n"); + const LV2_Atom * atom = + (const LV2_Atom *) data; + if (atom->type == self->common.uris.saw_freeValues) + { + /* free old values */ + const SawValuesMessage * msg = + (const SawValuesMessage *) data; + free_values (self, msg->values); + } + else if (atom->type == self->common.uris.saw_calcValues) + { + /* recalc values */ + SawValues * values = calc_values (self); + respond (handle, sizeof (values), &values); + } + + return LV2_WORKER_SUCCESS; +} + +static LV2_Worker_Status +work_response ( + LV2_Handle instance, + uint32_t size, + const void* data) +{ + Saw * self = (Saw *) instance; + + /* install the new values */ + SawValues * values = * (SawValues * const *) data; + set_values (self, values); + + /* send a message to the worker to free the + * values */ + SawValuesMessage msg = { + { sizeof (SawValues *), + self->common.uris.saw_freeValues }, + values }; + + self->common.schedule->schedule_work ( + self->common.schedule->handle, + sizeof (msg), &msg); + lv2_log_note ( + &self->common.logger, "inside work response\n"); + + return LV2_WORKER_SUCCESS; +} static LV2_Handle instantiate ( @@ -98,7 +277,7 @@ instantiate ( const char* bundle_path, const LV2_Feature* const* features) { - SuperSaw * self = calloc (1, sizeof (SuperSaw)); + Saw * self = calloc (1, sizeof (Saw)); self->common.samplerate = rate; @@ -112,6 +291,11 @@ instantiate ( self->common.map = (LV2_URID_Map*) features[i]->data; } + else if (HAVE_FEATURE (LV2_WORKER__schedule)) + { + self->common.schedule = + (LV2_Worker_Schedule *) features[i]->data; + } else if (HAVE_FEATURE (LV2_LOG__log)) { self->common.log = @@ -122,8 +306,16 @@ instantiate ( if (!self->common.map) { - fprintf (stderr, "Missing feature urid:map\n"); - return NULL; + lv2_log_error ( + &self->common.logger, "Missing feature urid:map\n"); + goto fail; + } + else if (!self->common.schedule) + { + lv2_log_error ( + &self->common.logger, + "Missing feature work:schedule\n"); + goto fail; } /* map uris */ @@ -133,97 +325,11 @@ instantiate ( lv2_atom_forge_init ( &self->common.forge, self->common.map); - return (LV2_Handle) self; -} - -static void -connect_port ( - LV2_Handle instance, - uint32_t port, - void * data) -{ - SuperSaw * self = (SuperSaw*) instance; - - switch ((PortIndex) port) - { - case SUPERSAW_CONTROL: - self->control = - (const LV2_Atom_Sequence *) data; - break; - case SUPERSAW_NOTIFY: - self->notify = - (LV2_Atom_Sequence *) data; - break; - case SUPERSAW_AMOUNT: - self->amount = (const float *) data; - break; - case SUPERSAW_STEREO_OUT_L: - self->stereo_out_l = (float *) data; - break; - case SUPERSAW_STEREO_OUT_R: - self->stereo_out_r = (float *) data; - break; - default: - break; - } -} - -/** - * To be called when the amount changes. - */ -static void -recalc_values ( - SuperSaw * self) -{ - self->attack = 0.02f; - self->decay = 0.04f + *self->amount * 0.4f; - self->sustain = 0.5f; - self->release = 0.04f + *self->amount * 0.4f; - self->saturator->drive = *self->amount * 0.3f; - self->saturator->dcoffset = *self->amount * 0.3f; - self->distortion->shape1 = *self->amount * 0.2f; - self->distortion->shape2 = *self->amount * 0.2f; - *self->reverb->mix = *self->amount * 0.5f; - - for (int i = 0; i < 128; i++) - { - MidiKey * key = &self->keys[i]; - - /* adsr */ - key->adsr->atk = self->attack; - key->adsr->dec = self->decay; - key->adsr->sus = self->sustain; - key->adsr->rel = self->release; - - for (int j = 0; j < 7; j++) - { - /* voice spread */ - int is_even = (j % 2) == 0; - float freq_apart; - float freq_delta = - (*self->amount + 0.4f * (1.f - *self->amount)) * - 2.4f; - if (is_even) - { - freq_apart = (float) (j / 2) * freq_delta; - } - else - { - freq_apart = (float) (j / 2 + 1) * - freq_delta; - } - *key->blsaws[j]->freq = - key->base_freq + - math_round_float_to_int (freq_apart); - } - } -} - -static void -activate ( - LV2_Handle instance) -{ - SuperSaw * self = (SuperSaw*) instance; + /* init logger */ + lv2_log_logger_init ( + &self->common.logger, self->common.map, self->common.log); + /* create synth */ srand (time (NULL)); for (int i = 0; i < 128; i++) { @@ -284,7 +390,55 @@ activate ( sp_zitarev_init (self->sp, self->reverb); *self->reverb->level = 0.f; - recalc_values (self); + return (LV2_Handle) self; + +fail: + free (self); + return NULL; +} + +static void +connect_port ( + LV2_Handle instance, + uint32_t port, + void * data) +{ + Saw * self = (Saw*) instance; + + switch ((PortIndex) port) + { + case SUPERSAW_CONTROL: + self->control = + (const LV2_Atom_Sequence *) data; + break; + case SUPERSAW_NOTIFY: + self->notify = + (LV2_Atom_Sequence *) data; + break; + case SUPERSAW_AMOUNT: + self->amount = (const float *) data; + break; + case SUPERSAW_STEREO_OUT_L: + self->stereo_out_l = (float *) data; + break; + case SUPERSAW_STEREO_OUT_R: + self->stereo_out_r = (float *) data; + break; + default: + break; + } +} + +static void +activate ( + LV2_Handle instance) +{ + Saw * self = (Saw*) instance; + + /* load the default values */ + SawValues * values = calc_values (self); + set_values (self, values); + free_values (self, values); } /** @@ -292,11 +446,13 @@ activate ( */ static void process ( - SuperSaw * self, + Saw * self, uint32_t * offset) { - self->stereo_out_l[*offset] = 0.f; - self->stereo_out_r[*offset] = 0.f; + float * current_l = &self->stereo_out_l[*offset]; + float * current_r = &self->stereo_out_r[*offset]; + *current_l = 0.f; + *current_r = 0.f; for (int i = 0; i < 128; i++) { @@ -333,13 +489,13 @@ process ( if (j % 2 == 0) { - self->stereo_out_l[*offset] += val * 0.8f; - self->stereo_out_r[*offset] += val * 0.2f; + *current_l += val * 0.8f; + *current_r += val * 0.2f; } else { - self->stereo_out_l[*offset] += val * 0.2f; - self->stereo_out_r[*offset] += val * 0.8f; + *current_l += val * 0.2f; + *current_r += val * 0.8f; } } @@ -347,37 +503,38 @@ process ( } } -#if 0 - /* saturate */ - float saturated = 0; - sp_saturator_compute ( - self->sp, self->saturator, &self->stereo_out_l[*offset], - &saturated); - self->stereo_out_l[*offset] += saturated; - sp_saturator_compute ( - self->sp, self->saturator, &self->stereo_out_r[*offset], - &saturated); - self->stereo_out_r[*offset] += saturated; -#endif + /* saturate - for some reason it makes noise when it's + * silent */ + if (fabsf (*current_l) > 0.001f || + fabsf (*current_r) > 0.001f) + { + float saturated = 0; + sp_saturator_compute ( + self->sp, self->saturator, current_l, + &saturated); + *current_l += saturated; + sp_saturator_compute ( + self->sp, self->saturator, current_r, + &saturated); + *current_r += saturated; + } /* distort */ float distortion = 0; sp_dist_compute ( - self->sp, self->distortion, &self->stereo_out_l[*offset], + self->sp, self->distortion, current_l, &distortion); - self->stereo_out_l[*offset] += distortion; + *current_l += distortion; sp_dist_compute ( - self->sp, self->distortion, &self->stereo_out_r[*offset], + self->sp, self->distortion, current_r, &distortion); - self->stereo_out_r[*offset] += distortion; + *current_r += distortion; /* reverb */ sp_zitarev_compute ( self->sp, self->reverb, - &self->stereo_out_l[*offset], - &self->stereo_out_r[*offset], - &self->stereo_out_l[*offset], - &self->stereo_out_r[*offset]); + current_l, current_r, + current_l, current_r); (*offset)++; } @@ -387,13 +544,24 @@ run ( LV2_Handle instance, uint32_t n_samples) { - SuperSaw * self = (SuperSaw *) instance; + Saw * self = (Saw *) instance; uint32_t processed = 0; if (!math_floats_equal (self->last_amount, *self->amount)) { - recalc_values (self); + /* send a message to the worker to calculate new + * values */ + SawValuesMessage msg = { + { 0, + self->common.uris.saw_calcValues } + }; + + self->common.schedule->schedule_work ( + self->common.schedule->handle, + sizeof (msg), &msg); + lv2_log_note ( + &self->common.logger, "scheduled to recalculate\n"); } /* read incoming events from host and UI */ @@ -450,7 +618,7 @@ static void deactivate ( LV2_Handle instance) { - SuperSaw * self = (SuperSaw *) instance; + Saw * self = (Saw *) instance; for (int i = 0; i < 128; i++) { @@ -467,13 +635,20 @@ static void cleanup ( LV2_Handle instance) { - free (instance); + Saw * self = (Saw *) instance; + free (self); } static const void* extension_data ( - const char* uri) + const char * uri) { + static const LV2_Worker_Interface worker = + { work, work_response, NULL }; + if (!strcmp(uri, LV2_WORKER__interface)) + { + return &worker; + } return NULL; } diff --git a/plugins/supersaw/ttl_gen.c b/plugins/saw/ttl_gen.c similarity index 93% rename from plugins/supersaw/ttl_gen.c rename to plugins/saw/ttl_gen.c index 42632b2..faae8e5 100644 --- a/plugins/supersaw/ttl_gen.c +++ b/plugins/saw/ttl_gen.c @@ -17,7 +17,7 @@ * along with ZPlugins. If not, see . */ -#include "../supersaw_config.h" +#include PLUGIN_CONFIG #include @@ -54,7 +54,8 @@ int main ( @prefix rsz: .\n\ @prefix time: .\n\ @prefix urid: .\n\ -@prefix ui: .\n\n"); +@prefix ui: .\n\ +@prefix work: .\n\n"); fprintf (f, "<" PROJECT_URI ">\n\ @@ -73,9 +74,11 @@ int main ( ] ;\n\ doap:license ;\n\ lv2:project <" PROJECT_URI "> ;\n\ - lv2:requiredFeature urid:map ;\n\ + lv2:requiredFeature urid:map ,\n\ + work:schedule ;\n\ lv2:optionalFeature log:log ,\n\ lv2:hardRTCapable ;\n\ + lv2:extensionData work:interface ;\n\ lv2:port [\n\ a lv2:InputPort ,\n\ atom:AtomPort ;\n\ diff --git a/scripts/lv2lint_wrap.sh b/scripts/lv2lint_wrap.sh new file mode 100755 index 0000000..6f17710 --- /dev/null +++ b/scripts/lv2lint_wrap.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# $1: lv2lint +# $2: plugin build dir +# $3: plugin name +# $4: plugin URI + +set -o xtrace + +LV2_LINT_BIN=$1 +PL_BUILD_DIR=$2 +PL_NAME=$3 +PL_URI=$4 + +tmpdir=$(mktemp -d /tmp/lv2lint_wrap.XXXXXXXXX) +tmp_plugin_dir="$tmpdir/$PL_NAME" +mkdir -p "$tmp_plugin_dir" +cp $PL_BUILD_DIR/$PL_NAME.ttl \ + $PL_BUILD_DIR/${PL_NAME}_dsp.so $tmp_plugin_dir/ +cp $PL_BUILD_DIR/$3_manifest.ttl $tmp_plugin_dir/manifest.ttl + +LV2_PATH="$tmpdir" env $LV2_LINT_BIN -d \ + -I $tmp_plugin_dir/ $PL_URI