From 4ef382420916f9c10f5c27307d04579a14b23d86 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 29 Apr 2021 19:28:29 +0200 Subject: [PATCH] External for puredata --- CMakeLists.txt | 1 + cmake/PuredataConfig.cmake | 15 ++ plugins/CMakeLists.txt | 4 + plugins/puredata/CMakeLists.txt | 38 +++++ plugins/puredata/example.sfz | 22 +++ plugins/puredata/sfizz_puredata.c | 269 ++++++++++++++++++++++++++++++ plugins/puredata/sfizz~-help.pd | 46 +++++ 7 files changed, 395 insertions(+) create mode 100644 cmake/PuredataConfig.cmake create mode 100644 plugins/puredata/CMakeLists.txt create mode 100644 plugins/puredata/example.sfz create mode 100644 plugins/puredata/sfizz_puredata.c create mode 100644 plugins/puredata/sfizz~-help.pd diff --git a/CMakeLists.txt b/CMakeLists.txt index d3954343..6805f67d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ option_ex (SFIZZ_LV2_UI "Enable LV2 plug-in user interface" ON) option_ex (SFIZZ_VST "Enable VST plug-in build" ON) option_ex (SFIZZ_AU "Enable AU plug-in build" APPLE) option_ex (SFIZZ_VST2 "Enable VST2 plug-in build (unsupported)" OFF) +option_ex (SFIZZ_PUREDATA "Enable Puredata plug-in build" OFF) option_ex (SFIZZ_BENCHMARKS "Enable benchmarks build" OFF) option_ex (SFIZZ_TESTS "Enable tests build" OFF) option_ex (SFIZZ_DEMOS "Enable feature demos build" OFF) diff --git a/cmake/PuredataConfig.cmake b/cmake/PuredataConfig.cmake new file mode 100644 index 00000000..2cb764ff --- /dev/null +++ b/cmake/PuredataConfig.cmake @@ -0,0 +1,15 @@ +find_path(PUREDATA_INCLUDE_DIR "m_pd.h" PATH_SUFFIXES "pd") + +if(NOT PUREDATA_INCLUDE_DIR) + message(FATAL_ERROR "Cannot find Puredata headers") +endif() + +message(STATUS "Puredata headers: ${PUREDATA_INCLUDE_DIR}") + +if(WIN32) + set(PUREDATA_SUFFIX ".dll") +elseif(APPLE) + set(PUREDATA_SUFFIX ".pd_darwin") +else() + set(PUREDATA_SUFFIX ".pd_linux") +endif() diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3201b27d..77f13374 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -58,3 +58,7 @@ endif() if(SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) add_subdirectory(vst) endif() + +if(SFIZZ_PUREDATA) + add_subdirectory(puredata) +endif() diff --git a/plugins/puredata/CMakeLists.txt b/plugins/puredata/CMakeLists.txt new file mode 100644 index 00000000..7372c863 --- /dev/null +++ b/plugins/puredata/CMakeLists.txt @@ -0,0 +1,38 @@ +# Puredata plugin specific settings +include(PuredataConfig) + +set(PUREDATA_BINARY_DIR "${PROJECT_BINARY_DIR}/pd/sfizz") + +set(PUREDATA_RESOURCES + "sfizz~-help.pd" + "example.sfz") + +function(copy_puredata_resources TARGET SOURCE_DIR DESTINATION_DIR) + set(_deps) + foreach(res ${PUREDATA_RESOURCES}) + get_filename_component(_dir "${res}" DIRECTORY) + file(MAKE_DIRECTORY "${DESTINATION_DIR}/${_dir}") + add_custom_command( + OUTPUT "${DESTINATION_DIR}/${res}" + COMMAND "${CMAKE_COMMAND}" "-E" "copy" + "${SOURCE_DIR}/${res}" "${DESTINATION_DIR}/${res}" + DEPENDS "${SOURCE_DIR}/${res}") + list(APPEND _deps "${DESTINATION_DIR}/${res}") + endforeach() + add_custom_target("${TARGET}_puredata_resources" DEPENDS ${_deps}) + add_dependencies("${TARGET}" "${TARGET}_puredata_resources") +endfunction() + +add_library(sfizz_puredata MODULE sfizz_puredata.c) +target_include_directories(sfizz_puredata PRIVATE "${PUREDATA_INCLUDE_DIR}") +target_link_libraries(sfizz_puredata PRIVATE sfizz::sfizz sfizz::spin_mutex) + +set_target_properties(sfizz_puredata PROPERTIES + PREFIX "" + SUFFIX "${PUREDATA_SUFFIX}" + OUTPUT_NAME "sfizz" + LIBRARY_OUTPUT_DIRECTORY "${PUREDATA_BINARY_DIR}/$<0:>") + +copy_puredata_resources(sfizz_puredata + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PUREDATA_BINARY_DIR}") diff --git a/plugins/puredata/example.sfz b/plugins/puredata/example.sfz new file mode 100644 index 00000000..1ab72699 --- /dev/null +++ b/plugins/puredata/example.sfz @@ -0,0 +1,22 @@ + +ampeg_attack=0.25 +ampeg_decay=5 +ampeg_sustain=50 +ampeg_release=2 +fil_type=lpf_4p +resonance=3 +cutoff=2000 +oscillator_phase=-1 +cutoff_oncc300=3600 +cutoff_curvecc300=1 + + +sample=*saw +oscillator_multi=3 +oscillator_detune=10 +lfo1_freq=5 +lfo1_pitch=30 + + +sample=*triangle +transpose=-12 diff --git a/plugins/puredata/sfizz_puredata.c b/plugins/puredata/sfizz_puredata.c new file mode 100644 index 00000000..f1aafe69 --- /dev/null +++ b/plugins/puredata/sfizz_puredata.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include +#include +#include +#include +#include +#include +#include + +static t_class* cls_sfizz_tilde; + +typedef struct _sfizz_tilde { + t_object obj; + t_outlet* outputs[2]; + sfizz_synth_t* synth; + spin_mutex_t* mutex; + int midi[3]; + int midinum; + t_symbol* dir; + char* filepath; +} t_sfizz_tilde; + +static void sfizz_tilde_set_file(t_sfizz_tilde* self, const char* file) +{ + const char* dir = self->dir->s_name; + char* filepath; + if (file[0] != '\0') { + filepath = malloc(strlen(dir) + 1 + strlen(file) + 1); + sprintf(filepath, "%s/%s", dir, file); + } + else { + filepath = malloc(1); + *filepath = '\0'; + } + free(self->filepath); + self->filepath = filepath; +} + +static bool sfizz_tilde_do_load(t_sfizz_tilde* self) +{ + bool loaded; + spin_mutex_lock(self->mutex); + if (self->filepath[0] != '\0') + loaded = sfizz_load_file(self->synth, self->filepath); + else + loaded = sfizz_load_string(self->synth, "default.sfz", "sample=*sine"); + spin_mutex_unlock(self->mutex); + return loaded; +} + +static void* sfizz_tilde_new(t_symbol* sym, int argc, t_atom argv[]) +{ + (void)sym; + t_sfizz_tilde* self = (t_sfizz_tilde*)pd_new(cls_sfizz_tilde); + + const char* file; + if (argc == 0) + file = ""; + else if (argc == 1 && argv[0].a_type == A_SYMBOL) + file = argv[0].a_w.w_symbol->s_name; + else { + pd_free((t_pd*)self); + return NULL; + } + + self->dir = canvas_getcurrentdir(); + + self->outputs[0] = outlet_new(&self->obj, &s_signal); + self->outputs[1] = outlet_new(&self->obj, &s_signal); + + sfizz_synth_t* synth = sfizz_create_synth(); + self->synth = synth; + + self->mutex = spin_mutex_create(); + + sfizz_set_sample_rate(synth, sys_getsr()); + sfizz_set_samples_per_block(synth, sys_getblksize()); + + sfizz_tilde_set_file(self, file); + if (!sfizz_tilde_do_load(self)) { + pd_free((t_pd*)self); + return NULL; + } + + return self; +} + +static void sfizz_tilde_free(t_sfizz_tilde* self) +{ + if (self->filepath) + free(self->filepath); + if (self->synth) + sfizz_free(self->synth); + if (self->mutex) + spin_mutex_destroy(self->mutex); + if (self->outputs[0]) + outlet_free(self->outputs[0]); + if (self->outputs[1]) + outlet_free(self->outputs[1]); +} + +static t_int* sfizz_tilde_perform(t_int* w) +{ + t_sfizz_tilde* self; + t_sample* outputs[2]; + t_int nframes; + + w++; + self = (t_sfizz_tilde*)*w++; + outputs[0] = (t_sample*)*w++; + outputs[1] = (t_sample*)*w++; + nframes = (t_int)*w++; + + if (spin_mutex_trylock(self->mutex)) { + sfizz_render_block(self->synth, outputs, 2, nframes); + spin_mutex_unlock(self->mutex); + } + else { + memset(outputs[0], 0, nframes * sizeof(t_sample)); + memset(outputs[1], 0, nframes * sizeof(t_sample)); + } + + return w; +} + +static void sfizz_tilde_dsp(t_sfizz_tilde* self, t_signal** sp) +{ + dsp_add( + &sfizz_tilde_perform, 4, (t_int)self, + (t_int)sp[0]->s_vec, (t_int)sp[1]->s_vec, (t_int)sp[0]->s_n); +} + +static void sfizz_tilde_midiin(t_sfizz_tilde* self, t_float f) +{ + int byte = (int)f; + bool isstatus = (byte & 0x80) != 0; + + int* midi = self->midi; + int midinum = self->midinum; + + // + if (isstatus) { + midi[0] = byte; + midinum = 1; + } + else if (midinum != -1 && midinum < 3) + midi[midinum++] = byte; + else + midinum = -1; + + // + switch (midinum) { + case 2: + switch (midi[0] & 0xf0) { + case 0xd0: // channel aftertouch + if (spin_mutex_trylock(self->mutex)) { + sfizz_send_channel_aftertouch(self->synth, 0, midi[1]); + spin_mutex_unlock(self->mutex); + } + break; + } + break; + case 3: + switch (midi[0] & 0xf0) { + case 0x90: // note on + if (midi[2] == 0) + goto noteoff; + if (spin_mutex_trylock(self->mutex)) { + sfizz_send_note_on(self->synth, 0, midi[1], midi[2]); + spin_mutex_unlock(self->mutex); + } + break; + case 0x80: // note off + noteoff: + if (spin_mutex_trylock(self->mutex)) { + sfizz_send_note_off(self->synth, 0, midi[1], midi[2]); + spin_mutex_unlock(self->mutex); + } + break; + case 0xb0: // controller + if (spin_mutex_trylock(self->mutex)) { + sfizz_send_cc(self->synth, 0, midi[1], midi[2]); + spin_mutex_unlock(self->mutex); + } + break; + case 0xa0: // key aftertouch + if (spin_mutex_trylock(self->mutex)) { + sfizz_send_poly_aftertouch(self->synth, 0, midi[1], midi[2]); + spin_mutex_unlock(self->mutex); + } + break; + case 0xe0: // pitch bend + if (spin_mutex_trylock(self->mutex)) { + sfizz_send_pitch_wheel(self->synth, 0, (midi[1] + (midi[2] << 7)) - 8192); + spin_mutex_unlock(self->mutex); + } + break; + + } + break; + } + + self->midinum = midinum; +} + +static void sfizz_tilde_load(t_sfizz_tilde* self, t_symbol* sym) +{ + sfizz_tilde_set_file(self, sym->s_name); + sfizz_tilde_do_load(self); +} + +static void sfizz_tilde_reload(t_sfizz_tilde* self, t_float value) +{ + (void)value; + sfizz_tilde_do_load(self); +} + +static void sfizz_tilde_hdcc(t_sfizz_tilde* self, t_float cc, t_float value) +{ + if (spin_mutex_trylock(self->mutex)) { + sfizz_automate_hdcc(self->synth, 0, (int)cc, value); + spin_mutex_unlock(self->mutex); + } +} + +static void sfizz_tilde_voices(t_sfizz_tilde* self, t_float value) +{ + if (spin_mutex_trylock(self->mutex)) { + int numvoices = (int)value; + if (numvoices < 1) + numvoices = 1; + sfizz_set_num_voices(self->synth, numvoices); + spin_mutex_unlock(self->mutex); + } +} + +#if defined(_WIN32) +__declspec(dllexport) +#else +__attribute__((visibility("default"))) +#endif +void sfizz_setup() +{ + post("sfizz external for Puredata"); + + cls_sfizz_tilde = class_new( + gensym("sfizz~"), + (t_newmethod)&sfizz_tilde_new, + (t_method)&sfizz_tilde_free, + sizeof(t_sfizz_tilde), + CLASS_DEFAULT, A_GIMME, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_dsp, gensym("dsp"), A_CANT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_midiin, &s_float, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_load, gensym("load"), A_DEFSYM, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_reload, gensym("reload"), A_DEFFLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_hdcc, gensym("hdcc"), A_FLOAT, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_voices, gensym("voices"), A_FLOAT, A_NULL); +} diff --git a/plugins/puredata/sfizz~-help.pd b/plugins/puredata/sfizz~-help.pd new file mode 100644 index 00000000..96600cba --- /dev/null +++ b/plugins/puredata/sfizz~-help.pd @@ -0,0 +1,46 @@ +#N canvas 932 395 659 405 12; +#X declare -lib sfizz; +#X obj 4 3 cnv 15 650 80 empty empty sfizz 20 30 0 40 -261682 -66577 +0; +#X text 45 54 A synthesizer for instruments in SFZ format; +#X obj 241 165 r sfizz-ctl; +#X text 33 167 create a SFZ instrument; +#X obj 29 324 midiin; +#X obj 29 349 s sfizz-ctl; +#X text 26 301 connect MIDI-in; +#X obj 162 223 dac~; +#X msg 180 346 voices \$1; +#X obj 180 371 s sfizz-ctl; +#X text 178 302 modify the polyphony; +#X obj 183 326 hsl 128 15 1 256 0 1 empty empty empty -2 -8 0 10 -262144 +-1 -1 3138 1; +#X floatatom 273 346 5 0 0 0 - - -; +#X obj 378 372 s sfizz-ctl; +#X obj 381 327 hsl 128 15 0 1 0 1 empty empty empty -2 -8 0 10 -262144 +-1 -1 6350 1; +#X floatatom 471 347 5 0 0 0 - - -; +#X text 377 303 modulate a parameter; +#X msg 378 347 hdcc 300 \$1; +#X obj 27 115 declare -lib sfizz; +#X obj 163 192 sfizz~ example.sfz; +#X text 18 93 load the sfizz library; +#X msg 221 115 \; pd dsp 1; +#X text 218 94 click to turn on DSP; +#X text 153 247 output stereo audio; +#X msg 366 223 reload; +#X obj 366 248 s sfizz-ctl; +#X obj 466 248 s sfizz-ctl; +#X msg 466 223 load example.sfz; +#X text 364 200 reload the instrument or load another; +#X connect 2 0 19 0; +#X connect 4 0 5 0; +#X connect 8 0 9 0; +#X connect 11 0 8 0; +#X connect 11 0 12 0; +#X connect 14 0 17 0; +#X connect 14 0 15 0; +#X connect 17 0 13 0; +#X connect 19 0 7 0; +#X connect 19 1 7 1; +#X connect 24 0 25 0; +#X connect 27 0 26 0;