# HG changeset patch # User samer # Date 1326295821 0 # Node ID bbd2b1abfb32a839452c53363e5643f671a9faeb Initial check in. diff -r 000000000000 -r bbd2b1abfb32 CHANGES --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CHANGES Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,8 @@ +*** version 0.2 + +Supports receiving OSC messages using the server framework from liblo. + +osc_now/2 returns the current time in the OSC timetag frame, in terms +of seconds and fractions of second since 1st Jan 1900 (I think). + + diff -r 000000000000 -r bbd2b1abfb32 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,43 @@ +# ---------------- configuration ---------------------- + +# set this to the prefix directory of your liblo installation +export LIBLO=/opt/local + +# target extension is dylib for OSX, so under Linux +export SO=dylib + +# if you have multiple SWI Prolog installations or an installation +# in a non-standard place, set PLLD to the appropriate plld invokation, eg +# PLLD=/usr/local/bin/plld -p /usr/local/bin/swipl +export PLLD=swipl-ld + +# install directories for foreign library and prolog module respectively +export INSTALL_LIB_TO=~/lib/prolog/x86_64 +export INSTALL_PL_TO=~/lib/prolog + +# flags for install - BSD install seems to be different from GNU install +# use '-bp' under Linux +export INSTALL_FLAGS='-bCS' + +VER=0.3 +# ---------------- end of configuration --------------- + +main: + make -C c + +clean: + make -C c clean + +install: main + make -C c install + make -C prolog install + +install-bin: main + make -C c install + +install-pl: + make -C prolog install + +tarball: + mkdirhier release + (cd .. && tar czf plosc/release/plosc-$(VER).tar.gz --exclude .DS_Store --exclude .swiple_history --exclude CVS --exclude "*.gz" --exclude release plosc) diff -r 000000000000 -r bbd2b1abfb32 README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,107 @@ +*** +*** plosc - OSC message sending from SWI Prolog +*** + +This module allows Prolog code to send Open Sound Control (OSC) +messages using liblo. + + +PREREQUISITES + +- SWI Prolog +- liblo + + +INSTALLATION + +First check and edit as necessary the variables in the top half of the +root Makefile. + +The build process installs a Prolog module file and a foreign library +to ~/lib/prolog by default. If you wish to change this, edit the root Makefile +accordingly and be sure that the referenced directories are in your +Prolog file_search_path. + +In the root directory of this package, type + + $ make install + + + +USAGE + +This example talks to a Supercollider server running on the local machine +listening to port 57110. It sends a message to create a Synth from SynthDef 'Square' +with some given parameters. + +__________________________________________________________ +:- use_module(library(plosc)). +:- dynamic osc_addr/1. + +init :- + osc_mk_address(localhost,57110, A), + assert(osc_addr(A)). + +bing :- + osc_addr(A), + get_time(T), + osc_send(A,'/s_new',[string('Square'),int(-1),int(0),int(1),string('freq'),float(440)],T). + +:- init, bing. +__________________________________________________________ + + +The following code shows how to make an OSC server. +__________________________________________________________ + :- use_module(library(plosc)). + + dumposc(P,A) :- writeln(msg(P,A)). + forward(P,[string(Host),int(Port),string(Msg)|Args]) :- + osc_mk_address(Host,Port,Addr), + osc_send(Addr,Msg,Args). + + :- osc_mk_server(7770,S), + osc_mk_address(localhost,7770,P), + osc_add_handler(S,'/fish',any,dumposc), + osc_add_handler(S,'/fwd',any,forward), + assert(server(S,P)). + + % start and stop the asynchronous server + start :- server(S,_), osc_start_server(S). + stop :- server(S,_), osc_stop_server(S). + + % run the server synchronously - send /plosc/stop to stop it + run :- server(S,_), osc_run_server(S). + + % send a message to the current server + send(M,A) :- server(_,P), osc_send(P,M,A). +__________________________________________________________ + + +To run the code in the example directory, from the shell type + + $ swipl -s example/testosc.pl + + + +BUGS AND LIMITATIONS + +The message sending predicates are limited in the types of arguments +they can use - currently, the following functors can be used: + + Head functor OSC Type + ------------ -------- + int i - 32 bit integer + float f - Single precision float + double d - Double precision float + string s - String + symbol S - Symbol + true T - True + false F - False + nil N - Nil + inf I - Infinitum or Impuse + +BLOBs, 64 bit integers, 8 bit integers, time tags and MIDI messages cannot be sent. +However, all types can be received except BLOBs. + + diff -r 000000000000 -r bbd2b1abfb32 c/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/c/Makefile Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,24 @@ +# This will not work as a stand-alone make file - it must be +# called recursively from the make file in the directory above. + +TARGET=plosc.$(SO) +PLLDFLAGS=-I$(LIBLO)/include -L$(LIBLO)/lib -llo -fPIC -Wall + +.SUFFIXES: .c .o .so .dylib + +main: $(TARGET) + +clean: + rm $(TARGET) + +.c.so: + $(PLLD) -v $(PLLDFLAGS) -shared -o $@ $< + strip -x $@ + +.c.dylib: + $(PLLD) -v $(PLLDFLAGS) -shared -o $@ $< + strip -x $@ + +install: + install $(INSTALL_FLAGS) $(TARGET) $(INSTALL_LIB_TO) + diff -r 000000000000 -r bbd2b1abfb32 c/plosc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/c/plosc.c Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,860 @@ +/* + * Copyright (C) 2009 Samer Abdallah + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + */ + +#include +#include + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- + +// Reimplementation of lo_server_thread to all calls to +// Prolog from the server thread. + +typedef struct _my_server_thread { + lo_server s; + pthread_t thread; + volatile int active; + volatile int done; +} *my_server_thread; + +int my_server_thread_start(my_server_thread st); +int my_server_thread_stop(my_server_thread st); +int my_server_thread_run(my_server_thread st, int timeout); +void my_server_thread_free(my_server_thread st); +my_server_thread my_server_thread_new(const char *port, lo_err_handler err_h); + +// --------------------------------------------------------------------------- + +// BLOB to hold a lo_address +static PL_blob_t addr_blob; + +// BLOB to hold server thread +static PL_blob_t server_blob; + +static predicate_t call3, call5; +static atom_t osc_immed; +static functor_t osc_ts_2; +static functor_t int_1, float_1, double_1, string_1; + +install_t install(); + +foreign_t mk_address( term_t host, term_t port, term_t addr); +foreign_t is_address( term_t addr); +foreign_t send_osc_now( term_t addr, term_t msg, term_t args); +foreign_t send_osc_at( term_t addr, term_t msg, term_t args, term_t time); +foreign_t send_osc_from_at( term_t serv, term_t addr, term_t msg, term_t args, term_t time); +foreign_t send_timestamped( term_t addr, term_t msg, term_t args, term_t sec, term_t frac); +foreign_t now( term_t sec, term_t frac); +foreign_t time_to_ts( term_t time, term_t sec, term_t frac); +foreign_t time_from_ts( term_t time, term_t sec, term_t frac); + +// OSC server predicates +foreign_t mk_server( term_t port, term_t server); +foreign_t start_server( term_t server); +foreign_t stop_server( term_t server); +foreign_t del_handler( term_t server, term_t msg, term_t types); +foreign_t add_handler( term_t server, term_t msg, term_t types, term_t handler); +foreign_t add_handler_x( term_t server, term_t msg, term_t types, term_t handler); +foreign_t run_server( term_t server); + + +// BLOB functions +int addr_release(atom_t a) { + PL_blob_t *type; + size_t len; + void *p=PL_blob_data(a,&len,&type); + if (p) lo_address_free(*(lo_address *)p); + return TRUE; +} + +int addr_write(IOSTREAM *s, atom_t a, int flags) { + PL_blob_t *type; + size_t len; + lo_address *p=(lo_address *)PL_blob_data(a,&len,&type); + if (p) { + const char *host = lo_address_get_hostname(*p); + const char *port = lo_address_get_port(*p); + if (host!=NULL && port!=NULL) { + Sfprintf(s,"osc_address<%s:%s>",host,port); + } else { + Sfprintf(s,"osc_address"); + } + } + return TRUE; +} + +int server_release(atom_t a) { + PL_blob_t *type; + size_t len; + void *p=PL_blob_data(a,&len,&type); + if (p) my_server_thread_free(*(my_server_thread *)p); + return TRUE; +} + +int server_write(IOSTREAM *s, atom_t a, int flags) { + PL_blob_t *type; + size_t len; + my_server_thread *p=(my_server_thread *)PL_blob_data(a,&len,&type); + if (p) { + char *url=lo_server_get_url((*p)->s); + Sfprintf(s,"osc_server<%s>",url); + free(url); + } + return TRUE; +} + +install_t install() { + PL_register_foreign("osc_now", 2, (void *)now, 0); + PL_register_foreign("time_to_ts", 3, (void *)time_to_ts, 0); + PL_register_foreign("time_from_ts", 3, (void *)time_from_ts, 0); + PL_register_foreign("osc_mk_address", 3, (void *)mk_address, 0); + PL_register_foreign("osc_is_address", 1, (void *)is_address, 0); + PL_register_foreign("osc_send_now", 3, (void *)send_osc_now, 0); + PL_register_foreign("osc_send_at", 4, (void *)send_osc_at, 0); + PL_register_foreign("osc_send_from_at", 5, (void *)send_osc_from_at, 0); + PL_register_foreign("osc_mk_server", 2, (void *)mk_server, 0); + PL_register_foreign("osc_start_server", 1, (void *)start_server, 0); + PL_register_foreign("osc_stop_server", 1, (void *)stop_server, 0); + PL_register_foreign("osc_run_server", 1, (void *)run_server, 0); + PL_register_foreign("osc_del_method", 3, (void *)del_handler, 0); + PL_register_foreign("osc_add_method", 4, (void *)add_handler, 0); + PL_register_foreign("osc_add_method_x", 4, (void *)add_handler_x, 0); + + addr_blob.magic = PL_BLOB_MAGIC; + addr_blob.flags = PL_BLOB_UNIQUE; + addr_blob.name = "osc_address"; + addr_blob.acquire = 0; + addr_blob.release = addr_release; + addr_blob.write = addr_write; + addr_blob.compare = 0; + + server_blob.magic = PL_BLOB_MAGIC; + server_blob.flags = PL_BLOB_UNIQUE; + server_blob.name = "osc_server"; + server_blob.acquire = 0; + server_blob.release = server_release; + server_blob.write = server_write; + server_blob.compare = 0; + + call3 = PL_predicate("call",3,"user"); + call5 = PL_predicate("call",5,"user"); + osc_immed = PL_new_atom("osc_immed"); + osc_ts_2 = PL_new_functor(PL_new_atom("osc_ts"),2); + int_1 = PL_new_functor(PL_new_atom("int"),1); + float_1 = PL_new_functor(PL_new_atom("float"),1); + double_1 = PL_new_functor(PL_new_atom("double"),1); + string_1 = PL_new_functor(PL_new_atom("string"),1); +} + +// throws a Prolog exception to signal type error +static int type_error(term_t actual, const char *expected) +{ + term_t ex = PL_new_term_ref(); + int rc; + + rc = PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 2, + PL_FUNCTOR_CHARS, "type_error", 2, + PL_CHARS, expected, + PL_TERM, actual, + PL_VARIABLE); + + return PL_raise_exception(ex); +} + +static int osc_error(int errno, const char *errmsg, const char *msg) +{ + term_t ex = PL_new_term_ref(); + int rc; + + rc=PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 1, + PL_FUNCTOR_CHARS, "osc_error", 3, + PL_INTEGER, errno, + PL_CHARS, errmsg, + PL_CHARS, msg==NULL ? "none" : msg); + + return PL_raise_exception(ex); +} + +static int arg_error(const char *type, term_t arg) +{ + term_t ex = PL_new_term_ref(); + int rc; + + rc=PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 1, + PL_FUNCTOR_CHARS, "arg_error", 2, + PL_CHARS, type, + PL_TERM, arg); + + return PL_raise_exception(ex); +} + +// put lo_address into a Prolog BLOB +static int unify_addr(term_t addr,lo_address a) { + return PL_unify_blob(addr, &a, sizeof(lo_address), &addr_blob); +} + +// get lo_address from BLOB +static int get_addr(term_t addr, lo_address *a) +{ + PL_blob_t *type; + size_t len; + lo_address *a1; + + PL_get_blob(addr, (void **)&a1, &len, &type); + if (type != &addr_blob) { + return type_error(addr, "osc_address"); + } else { + *a=*a1; + return TRUE; + } +} + +// put lo_address into a Prolog BLOB +static int unify_server(term_t server,my_server_thread s) { + return PL_unify_blob(server, &s, sizeof(my_server_thread), &server_blob); +} + +// get my_server_thread from BLOB +static int get_server(term_t server, my_server_thread *a) +{ + PL_blob_t *type; + size_t len; + my_server_thread *a1; + + PL_get_blob(server, (void **)&a1, &len, &type); + if (type != &server_blob) { + return type_error(server, "osc_server"); + } else { + *a=*a1; + return TRUE; + } +} + +// get Prolog (Unix) time value and convert to OSC timestamp +static int get_prolog_time(term_t time, lo_timetag *ts) { + double t, ft; + int ok = PL_get_float(time, &t); + + ft=floor(t); + ts->sec = ((uint32_t)ft)+2208988800u; + ts->frac = (uint32_t)(4294967296.0*(t-ft)); + return ok; +} + +static int get_timetag(term_t sec, term_t frac, lo_timetag *ts) { + int64_t s, f; + int ok = PL_get_int64(sec, &s) && PL_get_int64(frac, &f); + ts->sec = s; + ts->frac = f; + return ok; +} + + +static int get_msg(term_t msg, char **m) { + int rc=PL_get_chars(msg, m, CVT_ATOM | CVT_STRING); + if (rc && strcmp(*m,"any")==0) *m=NULL; + return rc; +} + +// parse a list of Prolog terms and add arguments to an OSC message +static int add_msg_args(lo_message msg, term_t list) +{ + term_t head=PL_new_term_ref(); + + // copy term ref so as not to modify original + list=PL_copy_term_ref(list); + + while (PL_get_list(list,head,list)) { + atom_t name; + int rc, arity; + const char *type; + + if (!PL_get_name_arity(head,&name,&arity)) return type_error(head,"term"); + type=PL_atom_chars(name); + switch (arity) { + case 1: { + term_t a1=PL_new_term_ref(); + rc=PL_get_arg(1,head,a1); // !!!! check return value + + if (!strcmp(type,"int")) { + int x; + if (!PL_get_integer(a1,&x)) return type_error(a1,"integer"); + lo_message_add_int32(msg,x); + } else if (!strcmp(type,"double")) { + double x; + if (!PL_get_float(a1,&x)) return type_error(a1,"float"); + lo_message_add_double(msg,x); + } else if (!strcmp(type,"string")) { + char *x; + if (!PL_get_chars(a1,&x,CVT_ATOM|CVT_STRING)) return type_error(a1,"string"); + lo_message_add_string(msg,x); + } else if (!strcmp(type,"symbol")) { + char *x; + if (!PL_get_chars(a1,&x,CVT_ATOM)) return type_error(a1,"atom"); + lo_message_add_symbol(msg,x); + } else if (!strcmp(type,"float")) { + double x; + if (!PL_get_float(a1,&x)) return type_error(a1,"float"); + lo_message_add_float(msg,(float)x); + } else { + return arg_error(type,head); + } + + break; + } + case 0: { + if (!strcmp(type,"true")) lo_message_add_true(msg); + else if (!strcmp(type,"false")) lo_message_add_false(msg); + else if (!strcmp(type,"nil")) lo_message_add_nil(msg); + else if (!strcmp(type,"inf")) lo_message_add_infinitum(msg); + break; + } + } + } + if (!PL_get_nil(list)) return type_error(list,"nil"); + return TRUE; +} + +static int send_msg_timestamped(lo_address a, lo_timetag *ts, char *path, term_t args) +{ + lo_message msg=lo_message_new(); + lo_bundle bun=lo_bundle_new(*ts); + + if (add_msg_args(msg,args)) { + int ret; + + lo_bundle_add_message(bun,path,msg); + ret = lo_send_bundle(a,bun); + lo_message_free(msg); + lo_bundle_free(bun); + if (ret==-1) { + return osc_error(lo_address_errno(a),lo_address_errstr(a),path); + } else { + return TRUE; + } + } else return FALSE; +} + +static int send_msg_timestamped_from(lo_address a, lo_server s, lo_timetag *ts, char *path, term_t args) +{ + lo_message msg=lo_message_new(); + lo_bundle bun=lo_bundle_new(*ts); + + if (add_msg_args(msg,args)) { + int ret; + + lo_bundle_add_message(bun,path,msg); + ret = lo_send_bundle_from(a,s,bun); + lo_message_free(msg); + lo_bundle_free(bun); + if (ret==-1) { + return osc_error(lo_address_errno(a),lo_address_errstr(a),path); + } else { + return TRUE; + } + } else return FALSE; +} + +static int send_msg(lo_address a, char *path, term_t args) +{ + lo_message msg=lo_message_new(); + + if (add_msg_args(msg,args)) { + if (lo_send_message(a,path,msg)==-1) { + lo_message_free(msg); + return osc_error(lo_address_errno(a),lo_address_errstr(a),path); + } else { + lo_message_free(msg); + return TRUE; + } + } else return FALSE; +} + +foreign_t mk_address(term_t host, term_t port, term_t addr) { + char *h, *p; + + if (PL_get_chars(host, &h, CVT_ATOM | CVT_STRING)) { + if (PL_get_chars(port, &p, CVT_INTEGER)) { + lo_address a = lo_address_new(h,p); + return unify_addr(addr,a); + } else { + return type_error(port,"integer"); + } + } else { + return type_error(host,"atom"); + } +} + +foreign_t now(term_t sec, term_t frac) { + lo_timetag ts; + int64_t s, f; + + lo_timetag_now(&ts); + s=ts.sec; f=ts.frac; + return PL_unify_int64(sec,s) && PL_unify_int64(frac,f); +} + +foreign_t time_to_ts(term_t time, term_t sec, term_t frac) { + lo_timetag ts; + + return get_prolog_time(time,&ts) && + PL_unify_int64(sec,ts.sec) && + PL_unify_int64(frac,ts.frac); +} + +foreign_t time_from_ts(term_t time, term_t sec, term_t frac) { + lo_timetag ts; + + return get_timetag(sec,frac,&ts) && + PL_unify_float(time, (double)(ts.sec-2208988800u) + ts.frac/4294967296.0); +} + + + +// set current random state structure to values in Prolog term +foreign_t is_address(term_t addr) { + PL_blob_t *type; + return PL_is_blob(addr,&type) && type==&addr_blob; +} + +foreign_t send_osc_from_at(term_t serv, term_t addr, term_t msg, term_t args, term_t time) { + my_server_thread s; + lo_address a; + lo_timetag ts; + char *m; + + return get_addr(addr,&a) && + get_server(serv,&s) && + get_prolog_time(time,&ts) && + get_msg(msg, &m) && + send_msg_timestamped_from(a,s->s,&ts,m,args); +} + +foreign_t send_osc_at(term_t addr, term_t msg, term_t args, term_t time) { + lo_address a; + lo_timetag ts; + char *m; + + return get_addr(addr,&a) && + get_prolog_time(time,&ts) && + get_msg(msg, &m) && + send_msg_timestamped(a,&ts,m,args); +} + +foreign_t send_timestamped(term_t addr, term_t msg, term_t args, term_t secs, term_t frac) { + lo_address a; + lo_timetag ts; + char *m; + + return get_addr(addr,&a) && + get_timetag(secs,frac,&ts) && + get_msg(msg, &m) && + send_msg_timestamped(a,&ts,m,args); +} + + + +foreign_t send_osc_now(term_t addr, term_t msg, term_t args) { + lo_address a; + char *m; + + return get_addr(addr,&a) && + get_msg(msg, &m) && + send_msg(a,m,args); +} + + + +/* + * Server Bits + */ + +static void prolog_thread_func(void *data); + +// parse a list of type terms and encode as a NULL terminated +// string where each character encodes the type of one argument. +static int get_types_list(term_t list, char *typespec, int len) +{ + term_t head=PL_new_term_ref(); + int count=0; + + // copy term ref so as not to modify original + list=PL_copy_term_ref(list); + + while (PL_get_list(list,head,list) && countactive=0; + return 1; +} + +// get message arguments and unify given term with list of arg terms +static int unify_msg_args(term_t list, const char *types, lo_arg **argv, int argc) +{ + int i, rc=0; + for (i=0; ii); break; + case 'f': rc=PL_unify_term(head,PL_FUNCTOR, float_1, PL_FLOAT,(double)argv[i]->f); break; + case 'd': rc=PL_unify_term(head,PL_FUNCTOR, double_1, PL_DOUBLE,argv[i]->d); break; + case 's': rc=PL_unify_term(head,PL_FUNCTOR, string_1, PL_CHARS,&argv[i]->s); break; + case 'h': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"int64",1,PL_INT64,argv[i]->h); break; + case 'c': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"char",1,PL_INT,(int)argv[i]->c); break; + case 'S': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"symbol",1,PL_CHARS,&argv[i]->S); break; + case 'T': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"true",0); break; + case 'F': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"false",0); break; + case 'N': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"nil",0); break; + case 'I': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"inf",0); break; + case 'b': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"blob",0); break; + case 't': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"timetag",2, + PL_INT64,(int64_t)argv[i]->t.sec, + PL_INT64,(int64_t)argv[i]->t.frac); + break; + case 'm': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"midi",4, + PL_INT,(int)argv[i]->m[0], PL_INT,(int)argv[i]->m[1], + PL_INT,(int)argv[i]->m[2], PL_INT,(int)argv[i]->m[3]); + break; + } + if (!rc) PL_fail; + list=tail; + } + return PL_unify_nil(list); +} + +// handle OSC message by calling the associated Prolog goal +static int prolog_handler(const char *path, const char *types, lo_arg **argv, + int argc, lo_message msg, void *user_data) +{ + term_t goal = PL_new_term_ref(); + term_t term0 = PL_new_term_refs(3); + + + PL_recorded((record_t)user_data,goal); // retrieve the goal term + PL_put_term(term0,goal); // term_t goal encoded in user_data + PL_put_atom_chars(term0+1,path); + + return !( unify_msg_args(PL_copy_term_ref(term0+2),types,argv,argc) + && PL_call_predicate(NULL,PL_Q_NORMAL,call3,term0)); +} + +static int prolog_handler_x(const char *path, const char *types, lo_arg **argv, + int argc, lo_message msg, void *user_data) +{ + term_t goal = PL_new_term_ref(); + term_t term0 = PL_new_term_refs(5); + int rc=1; + + lo_timetag ts = lo_message_get_timestamp(msg); + lo_address sender = lo_message_get_source(msg); + // printf("osc tt: %u s + %u micros\n",ts.sec,ts.frac); + + PL_recorded((record_t)user_data,goal); // retrieve the goal term + PL_put_term(term0,goal); // term_t goal encoded in user_data + PL_put_atom_chars(term0+3,path); + + if (ts.sec==0u) PL_put_atom(term0+2,osc_immed); + else { + rc=PL_unify_term( term0+2, PL_FUNCTOR, osc_ts_2, + PL_INT64, (int64_t)ts.sec, + PL_INT64, (int64_t)ts.frac); + } + // PL_put_float(term0+2, (double)(ts.sec-2208988800u) + ts.frac/4294967296.0); + + return !( rc + && unify_addr(term0+1,sender) + && unify_msg_args(PL_copy_term_ref(term0+4),types,argv,argc) + && PL_call_predicate(NULL,PL_Q_NORMAL,call5,term0)); +} + +/* +static int generic_handler(const char *path, const char *types, lo_arg **argv, + int argc, lo_message msg, void *user_data) +{ + int i; + + printf("path: <%s>\n", path); + for (i=0; is, "/plosc/stop", NULL, stop_handler, (void *)s); + my_server_thread_run(s,timeout); + lo_server_del_method(s->s,"/plosc/stop",NULL); + return TRUE; +} + +foreign_t mk_server(term_t port, term_t server) +{ + char *p; + + if (PL_get_chars(port, &p, CVT_INTEGER)) { + my_server_thread s = my_server_thread_new(p, server_error); + if (s) return unify_server(server,s); + else return FALSE; + } else { + return type_error(port,"integer"); + } +} + +foreign_t add_handler_x(term_t server, term_t msg, term_t types, term_t goal) +{ + my_server_thread s; + lo_method method; + char *pattern, *typespec; + char buffer[256]; // !! space for up to 255 arguments + int rc; + + rc = get_server(server,&s) + && get_msg(msg,&pattern) + && get_types(types,buffer,256,&typespec); + + if (rc) { + record_t goal_record=PL_record(goal); + method = lo_server_add_method(s->s, pattern, typespec, prolog_handler_x, (void *)goal_record); + } + return rc; +} + +foreign_t add_handler(term_t server, term_t msg, term_t types, term_t goal) +{ + my_server_thread s; + lo_method method; + char *pattern, *typespec; + char buffer[256]; // !! space for up to 255 arguments + int rc; + + rc = get_server(server,&s) + && get_msg(msg,&pattern) + && get_types(types,buffer,256,&typespec); + + if (rc) { + record_t goal_record=PL_record(goal); + method = lo_server_add_method(s->s, pattern, typespec, prolog_handler, (void *)goal_record); + } + return rc; +} + +foreign_t del_handler(term_t server, term_t msg, term_t types) +{ + my_server_thread s; + char *pattern, *typespec; + char buffer[256]; // !! space for up to 255 arguments + int rc; + + rc = get_server(server,&s) + && get_msg(msg,&pattern) + && get_types(types,buffer,256,&typespec); + + if (rc) lo_server_del_method(s->s,pattern,typespec); + return rc; +} + +foreign_t start_server( term_t server) +{ + my_server_thread s; + return get_server(server,&s) && (my_server_thread_start(s)==0); +} + +foreign_t stop_server( term_t server) +{ + my_server_thread s; + return get_server(server,&s) && (my_server_thread_stop(s)==0); +} + +foreign_t run_server( term_t server) +{ + my_server_thread s; + printf("running OSC server synchronously...\n"); + return get_server(server,&s) && run_stoppable_server(s,10); +} + + +// ------------------------------------------------------------------------- +// my_server_thread implementation + +my_server_thread my_server_thread_new(const char *port, lo_err_handler err_h) +{ + my_server_thread st = malloc(sizeof(struct _my_server_thread)); + st->s = lo_server_new(port, err_h); + st->active = 0; + st->done = 0; + + if (!st->s) { + free(st); + return NULL; + } + return st; +} + +void my_server_thread_free(my_server_thread st) +{ + if (st) { + if (st->active) { + my_server_thread_stop(st); + } + lo_server_free(st->s); + } + free(st); +} + +int my_server_thread_stop(my_server_thread st) +{ + int result; + + if (st->active) { + st->active = 0; // Signal thread to stop + + result = pthread_join( st->thread, NULL ); + if (result) { + fprintf(stderr,"Failed to stop thread: pthread_join(), %s",strerror(result)); + return -result; + } + } + + return 0; +} + + +int my_server_thread_start(my_server_thread st) +{ + int result; + + if (!st->active) { + st->active = 1; + st->done = 0; + + // Create the server thread + result = pthread_create(&(st->thread), NULL, (void *)&prolog_thread_func, st); + if (result) { + fprintf(stderr, "Failed to create thread: pthread_create(), %s", + strerror(result)); + return -result; + } + + } + return 0; +} + +int my_server_thread_run(my_server_thread st, int timeout) +{ + st->active = 1; + st->done = 0; + while (st->active) { + lo_server_recv_noblock(st->s, timeout); + } + st->done = 1; + return 0; +} + +// code for the asynchronous server loop +// we must create and attach a new Prolog engine to enable +// calls to Prolog from this thread. +static void prolog_thread_func(void *data) +{ + my_server_thread st = (my_server_thread)data; + + printf("OSC server started.\n"); + PL_thread_attach_engine(NULL); + my_server_thread_run(st,50); + PL_thread_destroy_engine(); + printf("OSC server stopped.\n"); + pthread_exit(NULL); +} + diff -r 000000000000 -r bbd2b1abfb32 example/testosc.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/example/testosc.pl Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,62 @@ +:- module(testosc, [osc/1]). + +:- use_module(library(plosc)). + +:- dynamic server/2. + +ack(_,_) :- writeln(ack_received). + +echo(P,A) :- writeln(msg(P,A)). +echo(Sender,Time,P,A) :- + format('from ~w at ~w: ~w.\n',[Sender,Time,msg(P,A)]), + ( Time=osc_immed -> osc_send(Sender,'/ack',[]) + ; osc_time_ts(T0,Time) -> osc_send(Sender,'/ack',[],T0+1) + ). + +forward(_,[string(Host),int(Port),string(Msg)|Args]) :- + osc_mk_address(Host,Port,Addr), + osc_send(Addr,Msg,Args). + +sched_at(_,[double(Delay),string(Host),int(Port),string(Msg)|Args]) :- + get_time(Now), Time is Now+Delay, + osc_mk_address(Host,Port,Addr), + osc_send(Addr,Msg,Args,Time). + +osc(init(Port)) :- osc_mk_server(Port,S), + osc_mk_address(localhost,Port,P), + osc_add_handler_x(S, '/echox', any, echo), + osc_add_handler(S, '/echo', any, echo), + osc_add_handler(S, '/fwd', any, forward), + osc_add_handler(S, '/after', any, sched_in), + osc_add_handler(S, '/ack', any, ack), + assert(server(S,P)). + +osc(start) :- server(S,_), osc_start_server(S), at_halt(osc(stop)). +osc(stop) :- server(S,_), osc_stop_server(S). +osc(run) :- server(S,_), osc_run_server(S). + +osc(send(M,A)) :- server(_,P), osc_send(P,M,A). +osc(send(M,A,T)) :- server(_,P), osc_send(P,M,A,T). +osc(send_from(M,A,T)) :- server(S,P), osc_send_from(S,P,M,A,T). + +run(Port) :- osc(init(Port)), + nl, + writeln('Commands:'), nl, + writeln(' osc(start) to start the server in a new thread.'), + writeln(' osc(stop) to stop the server thread.'), + writeln(' osc(run) run the server synchronously in the current thread.'), + writeln(' osc(send(Path,Args)) send message with Path and Args.'), + writeln(' osc(send(Path,Args,Time)) send timestamped message with Path and Args.'), + nl, + writeln('OSC messages:'), nl, + writeln(' /echo <>'), + writeln(' write messages and arguments.'), + writeln(' /echox <>'), + writeln(' write messages and arguments with sender and timestamp.'), + writeln(' /fwd s i s <>'), + writeln(' forward message to given server.'), + writeln(' /after d s i s <>'), + writeln(' forward message after delay.'), + writeln(' /plosc/stop'), + writeln(' stop the synchronous server.'), + nl. diff -r 000000000000 -r bbd2b1abfb32 prolog/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/Makefile Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,4 @@ + +install: + install $(INSTALL_FLAGS) -m 644 plosc.pl $(INSTALL_PL_TO) + diff -r 000000000000 -r bbd2b1abfb32 prolog/plosc.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/prolog/plosc.pl Wed Jan 11 15:30:21 2012 +0000 @@ -0,0 +1,162 @@ +/* + * Prolog library for sending and receiving OSC messages + * Samer Abdallah (2009) +*/ + +:- module(plosc, [ + osc_now/2 % -Seconds:int, -Fraction:int + , osc_now/1 % -TS:osc_timestamp + , osc_mk_address/3 % +Host:atom, +Port:nonneg, -Ref:osc_addr + , osc_is_address/1 % +Ref + , osc_send/3 % +Ref, +Path:atom, +Args:list(osc_arg) + , osc_send/4 % +Ref, +Path:atom, +Args:list(osc_arg), +Time:float + , osc_send_from/5 % +Ref, +Ref, +Path:atom, +Args:list(osc_arg), +Time:float + , osc_mk_server/2 % +Port:nonneg, -Ref + , osc_start_server/1 % +Ref + , osc_stop_server/1 % +Ref + , osc_run_server/1 % +Ref + , osc_del_handler/3 % +Ref, +Path:atom, +Types:list(osc_arg) + , osc_add_handler/4 % +Ref, +Path:atom, +Types:list(osc_arg), +Goal:callable + , osc_add_handler_x/4 % +Ref, +Path:atom, +Types:list(osc_arg), +Goal:callable + + , osc_time_ts/2 + ]). + +:- meta_predicate osc_add_handler(+,+,+,2). +:- meta_predicate osc_add_handler_x(+,+,+,4). + +/** OSC server and client + + == + time == float. + osc_timestamp ---> osc_ts(int,int). + == +*/ +:- load_foreign_library(foreign(plosc)). + +%% osc_mk_address(+Host:atom, +Port:nonneg, -Ref:osc_addr) is det. +% +% Construct a BLOB atom representing an OSC destination. +% +% @param Host is the hostname or IP address of the OSC receiver +% @param Port is the port number of the OSC receiver +% @param Ref is an atom representing the address + +%% osc_is_address(+Ref) is semidet. +% +% Succeeds if Ref is an OSC address created by osc_mk_address/3 + +%% osc_send(+Ref:osc_addr, +Path:atom, +Args:list(osc_arg)) is det. +%% osc_send(+Ref:osc_addr, +Path:atom, +Args:list(osc_arg), +Time:time) is det. +% +% Sends an OSC message scheduled for immediate execution (osc_send/3) or +% at a given time (osc_send/4). +% +% @param Ref is an OSC address BLOB as returned by osc_mk_address/3. +% @param Path is an atom representing the OSC message path, eg '/foo/bar' +% @param Args is a list of OSC message arguments, which can be any of: +% * string(+X:text) +% String as atom or Prolog string +% * symbol(+X:atom) +% * double(+X:float) +% Double precision floating point +% * float(+X:float) +% Single precision floating point +% * int(+X:integer) +% * true +% * false +% * nil +% * inf +% +osc_send(A,B,C) :- osc_send_now(A,B,C). +osc_send(A,B,C,T) :- T1 is T, osc_send_at(A,B,C,T1). + +%% osc_send_from(+Server:osc_server, +Address:osc_addr, +Path:atom, +Args:list(osc_arg), +T:time) is det. +% +% Like osc_send/4 but sets the return address to that of the given server. +osc_send_from(Srv,Targ,Path,Args,Time) :- T1 is Time, osc_send_from_at(Srv,Targ,Path,Args,T1). + +%% osc_now(-Secs:integer,-Frac:integer) is det. +% +% Gets the current OSC time in seconds and 1/2^64 ths of second. + +%% osc_now(-TS:osc_timestamp) is det. +% +% Gets the current OSC time in seconds and 1/2^64 ths of second. +osc_now(osc_ts(Secs,Fracs)) :- osc_now(Secs,Fracs). + + +osc_time_ts(Time,osc_ts(Secs,Fracs)) :- + ( var(Time) -> time_from_ts(Time,Secs,Fracs) + ; time_to_ts(Time,Secs,Fracs)). + +%% osc_mk_server(+Port:nonneg, -Ref:osc_server) is det. +% +% Create an OSC server and return a BLOB atom representing it. +% +% @param Port is the port number of the OSC server +% @param Ref is an atom representing the server + +%% osc_start_server(+Ref:osc_server) is det. +% +% Run the OSC server referred to by Ref in a new thread. The new thread +% dispatches OSC messages received to the appropriate handlers as registered +% using osc_add_handler/4. + +%% osc_stop_server(+Ref:osc_server) is det. +% +% If Ref refers to a running server thread, stop the thread. + +%% osc_run_server(+Ref:osc_server) is det. +% +% The OSC server is run in the current thread, and does not return until +% the message loop terminates. This can be triggered by sending the +% message /plosc/stop to the server. Using this synchronous server +% avoids creating a new thread and a new Prolog engine. + +%% osc_add_handler( +Ref:osc_server, +Path:atom, +Types:list(osc_arg), +Goal:handler) is det. +% +% This registers a callable goal to handle the specified message Path for the +% OSC server referred to by Ref. +% The handler type is =|handler == pred(+atom,+list(osc_arg)).|= +% +% @param Types is a list of terms specifying the argument types that this handler +% will match. The terms are just like those descibed in osc_send/3 +% and osc_send/4, except that the actual values are not used and +% can be left as anonymous variables, eg [int(_),string(_)]. +% Alternatively, Types can be the atom 'any', which will match any +% arguments. +% +% @param Goal is any term which can be called with call/3 with two further +% arguments, which will be the message Path and the argument list, eg +% call( Goal, '/foo', [int(45),string(bar)]). + +osc_add_handler(Ref,Path,Types,Goal) :- osc_add_method(Ref,Path,Types,Goal). + +%% osc_add_handler_x( +Ref:osc_server, +Path:atom, +Types:list(osc_arg), +Goal:handler_x) is det. +% +% This registers a callable goal to handle the specified message Path for the +% OSC server referred to by Ref. +% The extended handler type is =|handler_x == pred(+osc_addr,+time,+atom,+list(osc_arg)).|= +% +% @param Types is a list of terms specifying the argument types that this handler +% will match. The terms are just like those descibed in osc_send/3 +% and osc_send/4, except that the actual values are not used and +% can be left as anonymous variables, eg [int(_),string(_)]. +% Alternatively, Types can be the atom 'any', which will match any +% arguments. +% +% @param Goal is any term which can be called with call/3 with two further +% arguments, which will be the message Path and the argument list, eg +% call( Goal, '/foo', [int(45),string(bar)]). + +osc_add_handler_x(Ref,Path,Types,Goal) :- osc_add_method_x(Ref,Path,Types,Goal). + +%% osc_del_handler( +Ref:osc_server, +Path:atom, +Types:list(osc_arg)) is det. +% +% Deregister a message handler previously registered with osc_add_handler/4. + +osc_del_handler(Ref,Path,Types) :- osc_del_method(Ref,Path,Types). + + +prolog:message(error(osc_error(Num,Msg,Path)), ['LIBLO error ~w: ~w [~w]'-[Num,Msg,Path] |Z],Z).