/*
* Copyright (c) 2010 Peter Brinkmann ([email protected])
* Copyright (c) 2012-2021 libpd team
*
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.
*
* See https://github.com/libpd/libpd/wiki for documentation
*
*/
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#ifndef LIBPD_NO_NUMERIC
# include <locale.h>
#endif
#include "z_libpd.h"
#include "x_libpdreceive.h"
#include "z_hooks.h"
#include "m_imp.h"
#include "g_all_guis.h"
// pd_init() doesn't call socket_init() which is needed on windows for
// libpd_start_gui() to work
#if (defined(_WIN32) || defined(_WIN64)) && PD_MINOR_VERSION > 50
# include "s_net.h"
# define SOCKET_INIT socket_init();
#else
# define SOCKET_INIT
#endif
#if PD_MINOR_VERSION < 46
# define HAVE_SCHED_TICK_ARG
#endif
#ifdef HAVE_SCHED_TICK_ARG
# define SCHED_TICK(x) sched_tick(x)
#else
# define SCHED_TICK(x) sched_tick()
#endif
// forward declares
void pd_init(void);
void sys_setchsr(int chin, int chout, int sr);
int sys_startgui(const char *libdir);
void sys_stopgui(void);
int sys_pollgui(void);
// (optional) setup functions for built-in "extra" externals
#ifdef LIBPD_EXTRA
void bob_tilde_setup(void);
void bonk_tilde_setup(void);
void choice_setup(void);
void fiddle_tilde_setup(void);
void loop_tilde_setup(void);
void lrshift_tilde_setup(void);
void pd_tilde_setup(void);
void pique_setup(void);
void sigmund_tilde_setup(void);
void stdout_setup(void);
#endif
static PERTHREAD t_atom *s_argv = NULL;
static PERTHREAD t_atom *s_curr = NULL;
static PERTHREAD int s_argm = 0;
static PERTHREAD int s_argc = 0;
static void *get_object(const char *s) {
t_pd *x = gensym(s)->s_thing;
return x;
}
// note: could we use pd_this instead?
static int s_initialized = 0;
// this is called instead of sys_main() to start things
int libpd_init(void) {
if (s_initialized) return -1; // only allow init once (for now)
s_initialized = 1;
signal(SIGFPE, SIG_IGN);
libpd_start_message(32); // allocate array for message assembly
sys_externalschedlib = 0;
sys_printtostderr = 0;
sys_usestdpath = 0; // don't use pd_extrapath, only sys_searchpath
sys_debuglevel = 0;
sys_noloadbang = 0;
sys_hipriority = 0;
sys_nmidiin = 0;
sys_nmidiout = 0;
#ifdef HAVE_SCHED_TICK_ARG
sys_time = 0;
#endif
pd_init();
STUFF->st_soundin = NULL;
STUFF->st_soundout = NULL;
STUFF->st_schedblocksize = DEFDACBLKSIZE;
STUFF->st_impdata = &libpd_mainimp;
sys_init_fdpoll();
libpdreceive_setup();
STUFF->st_searchpath = NULL;
sys_libdir = gensym("");
SOCKET_INIT
post("pd %d.%d.%d%s", PD_MAJOR_VERSION, PD_MINOR_VERSION,
PD_BUGFIX_VERSION, PD_TEST_VERSION);
#ifdef LIBPD_EXTRA
bob_tilde_setup();
bonk_tilde_setup();
choice_setup();
fiddle_tilde_setup();
loop_tilde_setup();
lrshift_tilde_setup();
pd_tilde_setup();
pique_setup();
sigmund_tilde_setup();
stdout_setup();
#endif
#ifndef LIBPD_NO_NUMERIC
setlocale(LC_NUMERIC, "C");
#endif
return 0;
}
void libpd_clear_search_path(void) {
sys_lock();
namelist_free(STUFF->st_searchpath);
STUFF->st_searchpath = NULL;
sys_unlock();
}
void libpd_add_to_search_path(const char *path) {
sys_lock();
STUFF->st_searchpath = namelist_append(STUFF->st_searchpath, path, 0);
sys_unlock();
}
void *libpd_openfile(const char *name, const char *dir) {
void *retval;
sys_lock();
pd_globallock();
retval = (void *)glob_evalfile(NULL, gensym(name), gensym(dir));
pd_globalunlock();
sys_unlock();
return retval;
}
void libpd_closefile(void *p) {
sys_lock();
pd_free((t_pd *)p);
sys_unlock();
}
int libpd_getdollarzero(void *p) {
sys_lock();
pd_pushsym((t_pd *)p);
int dzero = canvas_getdollarzero();
pd_popsym((t_pd *)p);
sys_unlock();
return dzero;
}
int libpd_blocksize(void) {
return DEFDACBLKSIZE;
}
int libpd_init_audio(int inChannels, int outChannels, int sampleRate) {
sys_lock();
sched_set_using_audio(SCHED_AUDIO_CALLBACK);
sys_setchsr(inChannels, outChannels, sampleRate);
sys_unlock();
return 0;
}
static const t_sample sample_to_short = SHRT_MAX,
short_to_sample = 1.0 / (t_sample) SHRT_MAX;
#define PROCESS(_x, _y) \
int i, j, k; \
t_sample *p0, *p1; \
sys_lock(); \
sys_pollgui(); \
for (i = 0; i < ticks; i++) { \
for (j = 0, p0 = STUFF->st_soundin; j < DEFDACBLKSIZE; j++, p0++) { \
for (k = 0, p1 = p0; k < STUFF->st_inchannels; k++, p1 += DEFDACBLKSIZE) \
{ \
*p1 = *inBuffer++ _x; \
} \
} \
memset(STUFF->st_soundout, 0, \
STUFF->st_outchannels*DEFDACBLKSIZE*sizeof(t_sample)); \
SCHED_TICK(pd_this->pd_systime + STUFF->st_time_per_dsp_tick); \
for (j = 0, p0 = STUFF->st_soundout; j < DEFDACBLKSIZE; j++, p0++) { \
for (k = 0, p1 = p0; k < STUFF->st_outchannels; k++, p1 += DEFDACBLKSIZE) \
{ \
*outBuffer++ = *p1 _y; \
} \
} \
} \
sys_unlock(); \
return 0;
int libpd_process_short(const int ticks, const short *inBuffer, short *outBuffer) {
PROCESS(* short_to_sample, * sample_to_short)
}
int libpd_process_float(const int ticks, const float *inBuffer, float *outBuffer) {
PROCESS(,)
}
int libpd_process_double(const int ticks, const double *inBuffer, double *outBuffer) {
PROCESS(,)
}
#define PROCESS_RAW(_x, _y) \
size_t n_in = STUFF->st_inchannels * DEFDACBLKSIZE; \
size_t n_out = STUFF->st_outchannels * DEFDACBLKSIZE; \
t_sample *p; \
size_t i; \
sys_lock(); \
sys_pollgui(); \
for (p = STUFF->st_soundin, i = 0; i < n_in; i++) { \
*p++ = *inBuffer++ _x; \
} \
memset(STUFF->st_soundout, 0, n_out * sizeof(t_sample)); \
SCHED_TICK(pd_this->pd_systime + STUFF->st_time_per_dsp_tick); \
for (p = STUFF->st_soundout, i = 0; i < n_out; i++) { \
*outBuffer++ = *p++ _y; \
} \
sys_unlock(); \
return 0;
int libpd_process_raw(const float *inBuffer, float *outBuffer) {
PROCESS_RAW(,)
}
int libpd_process_raw_short(const short *inBuffer, short *outBuffer) {
PROCESS_RAW(* short_to_sample, * sample_to_short)
}
int libpd_process_raw_double(const double *inBuffer, double *outBuffer) {
PROCESS_RAW(,)
}
#define GETARRAY \
t_garray *garray = (t_garray *) pd_findbyclass(gensym(name), garray_class); \
if (!garray) {sys_unlock(); return -1;} \
int libpd_arraysize(const char *name) {
int retval;
sys_lock();
GETARRAY
retval = garray_npoints(garray);
sys_unlock();
return retval;
}
int libpd_resize_array(const char *name, long size) {
sys_lock();
GETARRAY
garray_resize_long(garray, size);
sys_unlock();
return 0;
}
#define MEMCPY(_x, _y) \
GETARRAY \
if (n < 0 || offset < 0 || offset + n > garray_npoints(garray)) return -2; \
t_word *vec = ((t_word *) garray_vec(garray)) + offset; \
int i; \
for (i = 0; i < n; i++) _x = _y;
int libpd_read_array(float *dest, const char *name, int offset, int n) {
sys_lock();
MEMCPY(*dest++, (vec++)->w_float)
sys_unlock();
return 0;
}
int libpd_write_array(const char *name, int offset, const float *src, int n) {
sys_lock();
MEMCPY((vec++)->w_float, *src++)
sys_unlock();
return 0;
}
int libpd_read_array_double(double *dest, const char *name, int offset, int n) {
sys_lock();
MEMCPY(*dest++, (vec++)->w_float)
sys_unlock();
return 0;
}
int libpd_write_array_double(const char *name, int offset, const double *src, int n) {
sys_lock();
MEMCPY((vec++)->w_float, *src++)
sys_unlock();
return 0;
}
int libpd_bang(const char *recv) {
void *obj;
sys_lock();
obj = get_object(recv);
if (obj == NULL)
{
sys_unlock();
return -1;
}
pd_bang(obj);
sys_unlock();
return 0;
}
static int libpd_dofloat(const char *recv, t_float x) {
void *obj;
sys_lock();
obj = get_object(recv);
if (obj == NULL)
{
sys_unlock();
return -1;
}
pd_float(obj, x);
sys_unlock();
return 0;
}
int libpd_float(const char *recv, float x) {
return libpd_dofloat(recv, x);
}
int libpd_double(const char *recv, double x) {
return libpd_dofloat(recv, x);
}
int libpd_symbol(const char *recv, const char *symbol) {
void *obj;
sys_lock();
obj = get_object(recv);
if (obj == NULL)
{
sys_unlock();
return -1;
}
pd_symbol(obj, gensym(symbol));
sys_unlock();
return 0;
}
int libpd_start_message(int maxlen) {
if (maxlen > s_argm) {
t_atom *v = realloc(s_argv, maxlen * sizeof(t_atom));
if (v) {
s_argv = v;
s_argm = maxlen;
} else {
return -1;
}
}
s_argc = 0;
s_curr = s_argv;
return 0;
}
#define ADD_ARG(f) f(s_curr, x); s_curr++; s_argc++;
void libpd_add_float(float x) {
ADD_ARG(SETFLOAT);
}
void libpd_add_double(double x) {
ADD_ARG(SETFLOAT);
}
void libpd_add_symbol(const char *symbol) {
t_symbol *x;
sys_lock();
x = gensym(symbol);
sys_unlock();
ADD_ARG(SETSYMBOL);
}
int libpd_finish_list(const char *recv) {
return libpd_list(recv, s_argc, s_argv);
}
int libpd_finish_message(const char *recv, const char *msg) {
return libpd_message(recv, msg, s_argc, s_argv);
}
void libpd_set_float(t_atom *a, float x) {
SETFLOAT(a, x);
}
void libpd_set_double(t_atom *v, double x) {
SETFLOAT(v, x);
}
void libpd_set_symbol(t_atom *a, const char *symbol) {
SETSYMBOL(a, gensym(symbol));
}
int libpd_list(const char *recv, int argc, t_atom *argv) {
t_pd *obj;
sys_lock();
obj = get_object(recv);
if (obj == NULL)
{
sys_unlock();
return -1;
}
pd_list(obj, &s_list, argc, argv);
sys_unlock();
return 0;
}
int libpd_message(const char *recv, const char *msg, int argc, t_atom *argv) {
t_pd *obj;
sys_lock();
obj = get_object(recv);
if (obj == NULL)
{
sys_unlock();
return -1;
}
pd_typedmess(obj, gensym(msg), argc, argv);
sys_unlock();
return 0;
}
void *libpd_bind(const char *recv) {
t_symbol *x;
sys_lock();
x = gensym(recv);
sys_unlock();
return libpdreceive_new(x);
}
void libpd_unbind(void *p) {
sys_lock();
pd_free((t_pd *)p);
sys_unlock();
}
int libpd_exists(const char *recv) {
int retval;
sys_lock();
retval = (get_object(recv) != NULL);
sys_unlock();
return retval;
}
// when setting hooks, use mainimp if pd is not yet inited
#define IMP (s_initialized ? LIBPDSTUFF : &libpd_mainimp)
void libpd_set_printhook(const t_libpd_printhook hook) {
if (!s_initialized) // set default hook
sys_printhook = (t_printhook)hook;
else // set instance hook
STUFF->st_printhook = (t_printhook)hook;
}
void libpd_set_banghook(const t_libpd_banghook hook) {
IMP->i_hooks.h_banghook = hook;
}
void libpd_set_floathook(const t_libpd_floathook hook) {
IMP->i_hooks.h_floathook = hook;
IMP->i_hooks.h_doublehook = NULL;
}
void libpd_set_doublehook(const t_libpd_doublehook hook) {
IMP->i_hooks.h_floathook = NULL;
IMP->i_hooks.h_doublehook = hook;
}
void libpd_set_symbolhook(const t_libpd_symbolhook hook) {
IMP->i_hooks.h_symbolhook = hook;
}
void libpd_set_listhook(const t_libpd_listhook hook) {
IMP->i_hooks.h_listhook = hook;
}
void libpd_set_messagehook(const t_libpd_messagehook hook) {
IMP->i_hooks.h_messagehook = hook;
}
int libpd_is_float(t_atom *a) {
return (a)->a_type == A_FLOAT;
}
int libpd_is_symbol(t_atom *a) {
return (a)->a_type == A_SYMBOL;
}
float libpd_get_float(t_atom *a) {
return (a)->a_w.w_float;
}
double libpd_get_double(t_atom *a) {
return (a)->a_w.w_float;
}
const char *libpd_get_symbol(t_atom *a) {
return (a)->a_w.w_symbol->s_name;
}
t_atom *libpd_next_atom(t_atom *a) {
return a + 1;
}
#define CHECK_CHANNEL if (channel < 0) return -1;
#define CHECK_PORT if (port < 0 || port > 0x0fff) return -1;
#define CHECK_RANGE_7BIT(v) if (v < 0 || v > 0x7f) return -1;
#define CHECK_RANGE_8BIT(v) if (v < 0 || v > 0xff) return -1;
#define PORT (channel >> 4)
#define CHANNEL (channel & 0x0f)
int libpd_noteon(int channel, int pitch, int velocity) {
CHECK_CHANNEL
CHECK_RANGE_7BIT(pitch)
CHECK_RANGE_7BIT(velocity)
sys_lock();
inmidi_noteon(PORT, CHANNEL, pitch, velocity);
sys_unlock();
return 0;
}
int libpd_controlchange(int channel, int controller, int value) {
CHECK_CHANNEL
CHECK_RANGE_7BIT(controller)
CHECK_RANGE_7BIT(value)
sys_lock();
inmidi_controlchange(PORT, CHANNEL, controller, value);
sys_unlock();
return 0;
}
int libpd_programchange(int channel, int value) {
CHECK_CHANNEL
CHECK_RANGE_7BIT(value)
sys_lock();
inmidi_programchange(PORT, CHANNEL, value);
sys_unlock();
return 0;
}
// note: for consistency with Pd, we center the output of [pitchin] at 8192
int libpd_pitchbend(int channel, int value) {
CHECK_CHANNEL
if (value < -8192 || value > 8191) return -1;
sys_lock();
inmidi_pitchbend(PORT, CHANNEL, value + 8192);
sys_unlock();
return 0;
}
int libpd_aftertouch(int channel, int value) {
CHECK_CHANNEL
CHECK_RANGE_7BIT(value)
sys_lock();
inmidi_aftertouch(PORT, CHANNEL, value);
sys_unlock();
return 0;
}
int libpd_polyaftertouch(int channel, int pitch, int value) {
CHECK_CHANNEL
CHECK_RANGE_7BIT(pitch)
CHECK_RANGE_7BIT(value)
sys_lock();
inmidi_polyaftertouch(PORT, CHANNEL, pitch, value);
sys_unlock();
return 0;
}
int libpd_midibyte(int port, int byte) {
CHECK_PORT
CHECK_RANGE_8BIT(byte)
sys_lock();
inmidi_byte(port, byte);
sys_unlock();
return 0;
}
int libpd_sysex(int port, int byte) {
CHECK_PORT
CHECK_RANGE_8BIT(byte)
sys_lock();
inmidi_sysex(port, byte);
sys_unlock();
return 0;
}
int libpd_sysrealtime(int port, int byte) {
CHECK_PORT
CHECK_RANGE_8BIT(byte)
sys_lock();
inmidi_realtimein(port, byte);
sys_unlock();
return 0;
}
void libpd_set_noteonhook(const t_libpd_noteonhook hook) {
IMP->i_hooks.h_noteonhook = hook;
}
void libpd_set_controlchangehook(const t_libpd_controlchangehook hook) {
IMP->i_hooks.h_controlchangehook = hook;
}
void libpd_set_programchangehook(const t_libpd_programchangehook hook) {
IMP->i_hooks.h_programchangehook = hook;
}
void libpd_set_pitchbendhook(const t_libpd_pitchbendhook hook) {
IMP->i_hooks.h_pitchbendhook = hook;
}
void libpd_set_aftertouchhook(const t_libpd_aftertouchhook hook) {
IMP->i_hooks.h_aftertouchhook = hook;
}
void libpd_set_polyaftertouchhook(const t_libpd_polyaftertouchhook hook) {
IMP->i_hooks.h_polyaftertouchhook = hook;
}
void libpd_set_midibytehook(const t_libpd_midibytehook hook) {
IMP->i_hooks.h_midibytehook = hook;
}
int libpd_start_gui(const char *path) {
int retval;
sys_lock();
retval = sys_startgui(path);
sys_unlock();
return retval;
}
void libpd_stop_gui(void) {
sys_lock();
sys_stopgui();
sys_unlock();
}
int libpd_poll_gui(void) {
int retval;
sys_lock();
retval = sys_pollgui();
sys_unlock();
return (retval);
}
t_pdinstance *libpd_new_instance(void) {
#ifdef PDINSTANCE
t_pdinstance *pd = pdinstance_new();
pd->pd_stuff->st_impdata = libpdimp_new();
return pd;
#else
return NULL;
#endif
}
void libpd_set_instance(t_pdinstance *pd) {
#ifdef PDINSTANCE
pd_setinstance(pd);
#endif
}
void libpd_free_instance(t_pdinstance *pd) {
#ifdef PDINSTANCE
if (pd == &pd_maininstance) return;
libpdimp_free(pd->pd_stuff->st_impdata);
pdinstance_free(pd);
#endif
}
t_pdinstance *libpd_this_instance(void) {
return pd_this;
}
t_pdinstance *libpd_main_instance(void) {
return &pd_maininstance;
}
int libpd_num_instances(void) {
#ifdef PDINSTANCE
return pd_ninstances;
#else
return 1;
#endif
}
void libpd_set_instancedata(void *data, t_libpd_freehook freehook) {
LIBPDSTUFF->i_data = data;
LIBPDSTUFF->i_data_freehook = freehook;
}
void* libpd_get_instancedata() {
return LIBPDSTUFF->i_data;
}
void libpd_set_verbose(int verbose) {
if (verbose < 0) verbose = 0;
sys_verbose = verbose;
}
int libpd_get_verbose(void) {
return sys_verbose;
}
// dummy routines needed because we don't use s_file.c
void glob_loadpreferences(t_pd *dummy, t_symbol *s) {}
void glob_savepreferences(t_pd *dummy, t_symbol *s) {}
void glob_forgetpreferences(t_pd *dummy) {}
void sys_loadpreferences(const char *filename, int startingup) {}
int sys_oktoloadfiles(int done) {return 1;}
void sys_savepreferences(const char *filename) {} // used in s_path.c