From 55c95213b4583584b0cfcaa53d102cf13c7b3adb Mon Sep 17 00:00:00 2001
From: Florent Berthaut <florent.berthaut@univ-lille.fr>
Date: Wed, 1 Jan 2025 21:25:37 +0100
Subject: [PATCH] Add new branch with integrated osc

---
 .gitignore                   |   2 +
 .gitmodules                  |   3 +
 SConstruct                   |  26 ++++++-
 demo/Main.tscn               |  37 ++++++----
 demo/{ => patches}/patch1.pd | Bin 849 -> 813 bytes
 demo/{ => patches}/patch2.pd | Bin
 patches/gdpd.pd              | Bin 0 -> 1814 bytes
 patches/gdpd_receive.pd      | Bin 0 -> 146 bytes
 patches/gdpd_send.pd         | Bin 0 -> 146 bytes
 src/gdpd.cpp                 | 129 +++++++++++++++++++++--------------
 src/gdpd.h                   |  21 ++++++
 src/liblo                    |   1 +
 12 files changed, 154 insertions(+), 65 deletions(-)
 rename demo/{ => patches}/patch1.pd (55%)
 rename demo/{ => patches}/patch2.pd (100%)
 create mode 100644 patches/gdpd.pd
 create mode 100644 patches/gdpd_receive.pd
 create mode 100644 patches/gdpd_send.pd
 create mode 160000 src/liblo

diff --git a/.gitignore b/.gitignore
index c95efac..fa09e8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@
 *.import
 demo/build
 demo/export_presets.cfg
+demo/android
+build
diff --git a/.gitmodules b/.gitmodules
index aee1cce..6c592ef 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -6,3 +6,6 @@
 	path = src/godot-cpp
 	url = https://github.com/GodotNativeTools/godot-cpp
     branch = 4.2
+[submodule "src/liblo"]
+	path = src/liblo
+	url = https://github.com/radarsat1/liblo.git
diff --git a/SConstruct b/SConstruct
index dff6e8f..ba31074 100644
--- a/SConstruct
+++ b/SConstruct
@@ -19,6 +19,21 @@ fiddle_file = open("src/libpd/pure-data/extra/fiddle~/fiddle~.c","w")
 fiddle_file.writelines(out_lines)
 fiddle_file.close()
 
+#Generate liblo headers
+import fileinput
+
+#threadinclude = '#include "lo/lo_serverthread.h"'
+threadinclude = ''
+with open("src/liblo/lo/lo.h.in", "r") as file:
+    filedata = file.read()
+    filedata = filedata.replace("@THREADS_INCLUDE@", threadinclude)
+with open("src/liblo/lo/lo.h", "w") as file:
+    file.write(filedata)
+with open("src/liblo/lo/lo_endian.h.in", "r") as file:
+    filedata = file.read()
+    filedata = filedata.replace("@LO_BIGENDIAN@", "0")
+with open("src/liblo/lo/lo_endian.h", "w") as file:
+    file.write(filedata)
 
 
 env = SConscript("src/godot-cpp/SConstruct")
@@ -33,6 +48,7 @@ env = SConscript("src/godot-cpp/SConstruct")
 
 env.Append(CPPPATH=['.', 'src/libpd/libpd_wrapper',
                     'src/libpd/libpd_wrapper/util',
+                    "src/liblo",
                     'src/libpd/cpp','src/libpd/pure-data/src',  'src/rtaudio'], 
            CCFLAGS=["-fexceptions"])
 
@@ -85,7 +101,15 @@ sources = Glob('src/*.cpp') + Glob('src/rtaudio/*.cpp') +\
 		Glob('src/libpd/pure-data/src/s_net.c') +\
 		Glob('src/libpd/pure-data/src/s_path.c')  +\
 		Glob('src/libpd/pure-data/src/s_print.c') +\
-		Glob('src/libpd/pure-data/src/s_utf8.c')
+		Glob('src/libpd/pure-data/src/s_utf8.c') +\
+        Glob("src/liblo/src/*.c",\
+               exclude=["src/liblo/src/*test*",\
+                        "src/liblo/src/*.cpp",\
+                        "src/liblo/src/*thread*"])
+
+env.Append(CPPDEFINES=[ 'LO_SO_VERSION={11,1,4}',
+                        'PACKAGE_VERSION=\\"0.31\\"', 'PACKAGE_NAME=\\"liblo\\"',
+                        'PRINTF_LL=""'])
 
 if env["platform"] == "linux":
 	env.Append(CPPDEFINES=['__UNIX_JACK__', 'HAVE_LIBDL'])
diff --git a/demo/Main.tscn b/demo/Main.tscn
index dd3ecf6..83f3d8a 100644
--- a/demo/Main.tscn
+++ b/demo/Main.tscn
@@ -7,32 +7,41 @@ script/source = "extends Control
 
 func _ready():
 	add_child(_gdpd)
-	pass
-	
+
 func _process(delta):
 	# Get messages from the patch
 	while _gdpd.has_message() :
 		var msg = _gdpd.get_next()
 		print(\"got message from pd \", msg)
-	pass
 	
 func _load_patch(pd_patch : String) :
+	
+	
 	var fullpath = \"\"
 	if OS.has_feature(\"editor\"):
 		fullpath = ProjectSettings.globalize_path(pd_patch)
 	else:
-		fullpath = OS.get_executable_path().get_base_dir().path_join(pd_patch.lstrip(\"res://\"))
-		#fullpath = pd_patch
-		#print(DirAccess.get_files_at(\"res://\"))
+		var file = pd_patch.split(\"/\")[-1]
+		var path = pd_patch.trim_suffix(file)
+		# Copy files from the patches folder from res to user
+		if OS.get_name() == \"Android\" :
+			fullpath = \"/sdcard/Android/data/com.example.gdpd/files/\"
+		else :
+			fullpath = OS.get_user_data_dir()
+		var dir = DirAccess.open(\"res://\")
+		fullpath = fullpath.path_join(file)
+		# FIXME do that in C++, and for all .pd and .wav files of the project, on start
+		dir.copy(\"res://\"+path+\"/\"+file, fullpath)
+		
 	
-	print(fullpath)
+	print(\"fullpath = \", fullpath)
 	
 	# separate file name from directory
 	var patch_name = fullpath.split(\"/\")[-1]
 	var patch_dir = fullpath.trim_suffix(patch_name)
 
 	# load patch
-	_gdpd.openfile(patch_name, \"./\")
+	_gdpd.openfile(patch_name, patch_dir)
 
 func _on_Start_pressed() :	
 	# retrieve the list of available input and outputs
@@ -43,8 +52,8 @@ func _on_Start_pressed() :
 	_gdpd.init_devices(inps[0], outs[0])
 	
 	# the patch path should be absolute
-	_load_patch(\"res://patch1.pd\")
-	_load_patch(\"res://patch2.pd\")
+	_load_patch(\"patches/patch1.pd\")
+	_load_patch(\"patches/patch2.pd\")
 	
 	# send message to [receive from_godot] with one symbol
 	_gdpd.start_message(1)
@@ -60,8 +69,8 @@ func _on_start_non_rt_pressed():
 	_gdpd.init_nort()
 	
 	# the patch path should be absolute
-	_load_patch(\"res://patch1.pd\")
-	_load_patch(\"res://patch2.pd\")
+	_load_patch(\"patches/patch1.pd\")
+	_load_patch(\"patches/patch2.pd\")
 	
 	# send message to [receive from_godot] with one symbol
 	_gdpd.start_message(1)
@@ -72,8 +81,8 @@ func _on_start_non_rt_pressed():
 	_gdpd.subscribe(\"to_godot\")
 	
 func _on_Stop_pressed():
-	_gdpd.closefile(\"patch1.pd\")
-	_gdpd.closefile(\"patch2.pd\")
+	_gdpd.closefile(\"patches/patch1.pd\")
+	_gdpd.closefile(\"patches/patch2.pd\")
 	_gdpd.stop()
 
 func _on_HSlider_value_changed(value):
diff --git a/demo/patch1.pd b/demo/patches/patch1.pd
similarity index 55%
rename from demo/patch1.pd
rename to demo/patches/patch1.pd
index 82d8b965ff63eba8b115f18be8216809c79ee721..bee8fcfdb3037ab223c4eb996dfd8858c0f2d870 100644
GIT binary patch
delta 289
zcmcb}wwBFK*-s%kF|RDKSi#cFK*7}1Si#cNOu@v=Ou^8|noBuCAtg0AC$T71LAM~W
zBtt<@Pd_mwB|ooNKRu-&MSr5ys>w!-mjcZU%@s@y@T&!zmY<ZRU}k8cU~Z`ZG5{`S
zWUgRn3K5GhPR&c1tjH)L22~Val$xBHS(d6$kXVwOVK`ZosZ`8T!2ql`IX^EiHMvB=
rT){xW(0K9!CJ`|sG$~7vl-cBmOd|T`Xi|p83I+-$=z<`#EI@()Y}-*Z

delta 315
zcmZ3>c9Bg=*-s%kF|RDKSiwNS$XvnD(#Sx;($ZML&}gFFsuXhtLsMggoXp}9g~Wn_
z)VvgHF69V?{G==eBLi~<b4!IHg|wpl-1zkTl>8EeoVkLbxw%5ILP<WljDfj=p|Pn#
zQGRJjszO0xNpgna#CvxHL8ciRm_TeQDay>9Y|2zBW@xNn05&u^KQAvexkSNS!9c-s
z@&P6hAqzAaLxaf|m_#BC4HXO&49(FcjX<Ji=%U671|SnrY%nwfDK$h_YCc(jS%k|1
JBxeAU0{}2nRkr{D

diff --git a/demo/patch2.pd b/demo/patches/patch2.pd
similarity index 100%
rename from demo/patch2.pd
rename to demo/patches/patch2.pd
diff --git a/patches/gdpd.pd b/patches/gdpd.pd
new file mode 100644
index 0000000000000000000000000000000000000000..952c2b7a6e0b70d2284d1c748246add79a565407
GIT binary patch
literal 1814
zcmY%PQ%FwCD@!a^urxDJFf}z+urxJOFflVzFf_8}QjSo_Ps&m-G%-^!GBZ`kOD!o%
zO-{`$OI6S<RnSdBRbXVGkdvR7l9ZU24wlF*PKW3;F;&RPEG|jSQ!p~N)H5<P&@(bL
z)H5<PQm`~KGz6;!*<xyLp<rxk2(qO(H7^C56_y4H`NhcviABY!aBU#R85=^ZP$((N
z%te(kF;FPVFD*$`NKYw9flHYg8!H%DnqWE@Bw=i5sgRtXmzSDcg3ZN<a51(tP%t%D
zC`d`p&nqd)&jA|`@-|4q*jOR4D7{$004xgfAV}2002~$yIjMQ+B^fAEhDNB)0ckNY
zH&ZY)wNxlkNCH8)dQ&3>Lo;)Q;>z5l{2YZC6+|e4EHN}NRwz!*0lD8m!B7F=U~pKO
znL<M(H!(90ZlaN?rGl}EDK;ZPx=f7B6!J?;a#Bm+I*iQ~j4TZmiWJg{@^j<U^HcH>
zav&`rmy{?ZDkQ>n7#SETfD9^zYC(<>a|1I46C;Hpg_8Vu)TlBuS1>XJM|@g-QEno_
z5)(^H1tS9kbSHx%02Dr!kf2V@1BDMFsez=yI*p9su^gjf2vUFuD05Rwu%*Rd=b(7a
z+}J|F&=6v_LP1e#0Vu74T&w^OPmmR+CT0qzX5dr@vL7A>CMG5dhQ@}NF=}F_U~Gi$
z7f>1inPdpcT$#lsa0`o(A_5e2<|YcLVGYt{0kQ@b0k9NppkSb20!@`LF+&9d1tT;u
zBL$F3Na}^DGf^;5Fhx^ms$igCh9+jFV4z@#E@-Y`pkRTf&_cmL!5B@<5~SG>P0$eJ
z7)x|Pkh=}h9b^b{IY>P?%wbM9GzJ-q;c;V7{=g6g`O^r)TvG)@1w(UmH<&6IDHt1|
zi-JPH2tyPU1cqh`klYG)8OX!vmVxvt7@`N45jZ=ehm8@)!^Y^rXaw>wh9KBy=rIHG
zDk!#50?Y{H1q}V52s1(t10#?RjL}086eyVCWeoBHC^AqiGX|wUV|0&!1QbAqqpAgY
i7vwusQIL1Bi5e;xp$8+#si4e;nz)TkKxx4O6gvR>E7iLI

literal 0
HcmV?d00001

diff --git a/patches/gdpd_receive.pd b/patches/gdpd_receive.pd
new file mode 100644
index 0000000000000000000000000000000000000000..d5fa2d14c1273045d6dd60cd2a1e9e5a1a37b48f
GIT binary patch
literal 146
zcmY%PQ%FwCD@!a^urxDJFf}z+urxJOFflVzFf_8}QjSo_Ps&m-G%{1LFi|K{NGr<E
zjZaT0NI{V@v@lV~FD=PQEkPAFuv93@FD*$`h*2>F%OvOL<)tQ<C>SUhC>SXipokeM
J7$_Km!~oa@C9ePg

literal 0
HcmV?d00001

diff --git a/patches/gdpd_send.pd b/patches/gdpd_send.pd
new file mode 100644
index 0000000000000000000000000000000000000000..61e18c7c8ea264bcef5e1d3525271bb6a4d40f0f
GIT binary patch
literal 146
zcmY%PQ%FwCD@!a^urxDJFf}z+urxJOFflVzFf_8}QjSo_Ps&m-w@@%KQ^?HANi9JZ
zGc-0)C{`%Rk55l2NI{mcG*HONEG|(fC`v6z%}Y^;Q85HdC+Fwor6!js7$_Jh7%3Q_
Mh#4suC>Vmo0O@WfLjV8(

literal 0
HcmV?d00001

diff --git a/src/gdpd.cpp b/src/gdpd.cpp
index e5dd8aa..fd6f6fa 100644
--- a/src/gdpd.cpp
+++ b/src/gdpd.cpp
@@ -98,7 +98,7 @@ Array GdPd::get_available_output_devices() {
 }
 
 
-int GdPd::init_devices(String inputDevice, String outputDevice) {
+void GdPd::init_devices(String inputDevice, String outputDevice) {
 	std::string inpStr(inputDevice.utf8().get_data());
 	std::string outStr(outputDevice.utf8().get_data());
 
@@ -120,11 +120,9 @@ int GdPd::init_devices(String inputDevice, String outputDevice) {
 	m_bufferFrames = 128;
 
     m_realtime=true;
-
-	return start();
 }
 
-int GdPd::init_parameters(int nbInputs, int nbOutputs, int sampleRate, int bufferSize) {
+void GdPd::init_parameters(int nbInputs, int nbOutputs, int sampleRate, int bufferSize) {
 	m_inputDevice = m_audio.getDefaultInputDevice();
 	m_outputDevice = m_audio.getDefaultOutputDevice();
 	RtAudio::DeviceInfo inpInfo = m_audio.getDeviceInfo(m_inputDevice);
@@ -135,12 +133,9 @@ int GdPd::init_parameters(int nbInputs, int nbOutputs, int sampleRate, int buffe
 	m_bufferFrames = std::max<int>(64, bufferSize);
 
     m_realtime=true;
-
-	return start();
 }
 
 int GdPd::init_nort() {
-
     m_nbInputs=0;
     m_nbOutputs=2;
     m_sampleRate=44100;
@@ -173,56 +168,79 @@ int GdPd::init_nort() {
 }
 
 int GdPd::start() {
-    RtAudio::StreamParameters outParams, inpParams;
-    inpParams.deviceId = m_inputDevice;
-    inpParams.nChannels = m_nbInputs;
-    outParams.deviceId = m_outputDevice;
-    outParams.nChannels = m_nbOutputs;
-    print("Output channels = "+std::to_string(outParams.nChannels));
-    print("Input channels = "+std::to_string(inpParams.nChannels));
-
-
-	if(!m_pd.init(m_nbInputs, m_nbOutputs, m_sampleRate, true)) {
-		print("GDPD : Error starting libpd");
-		return 1;
-	}
-
-	//libpd_set_verbose(1);
-
-	//start dsp
-	m_pd.computeAudio(true);
 
-    if(m_realtime) {
-        //intialize rtaudio
-        if(m_audio.getDeviceCount()==0){
-            UtilityFunctions::print("There are no available sound devices.");
+    if(m_mode==OSC) {
+
+        //Create OSC sender
+        m_sender = NULL;
+        m_sender = lo_server_new_multicast(
+                            MULTICAST_ADDRESS.c_str(), SENDER_PORT, NULL);
+        m_receiver = NULL;
+        m_receiver = lo_server_new_multicast(
+                            MULTICAST_ADDRESS.c_str(), RECEIVER_PORT, NULL);
+        /*
+        m_sender = lo_server_new_with_proto(
+                            to_string(UDP_SERVER_PORT).c_str(), 
+                            LO_UDP, NULL);
+        */
+        if(m_receiver!=NULL) {
+            lo_server_add_method(m_receiver, NULL, NULL, oscHandler, this);
+        }
+        else {
+            cout<<"GdPd : Could not create UDP connection"<<endl;
+        }
+    }
+    else {
+        RtAudio::StreamParameters outParams, inpParams;
+        inpParams.deviceId = m_inputDevice;
+        inpParams.nChannels = m_nbInputs;
+        outParams.deviceId = m_outputDevice;
+        outParams.nChannels = m_nbOutputs;
+        print("Output channels = "+std::to_string(outParams.nChannels));
+        print("Input channels = "+std::to_string(inpParams.nChannels));
+
+        if(!m_pd.init(m_nbInputs, m_nbOutputs, m_sampleRate, true)) {
+            print("GDPD : Error starting libpd");
             return 1;
         }
 
-        RtAudio::StreamOptions options;
-        options.streamName = "gdpd";
-        options.flags = RTAUDIO_SCHEDULE_REALTIME;
-        if(m_audio.getCurrentApi() != RtAudio::MACOSX_CORE) {
-            options.flags |= RTAUDIO_MINIMIZE_LATENCY;
+        //libpd_set_verbose(1);
+
+        //start dsp
+        m_pd.computeAudio(true);
+
+        if(m_mode==AUDIO_RT) {
+            //intialize rtaudio
+            if(m_audio.getDeviceCount()==0){
+                UtilityFunctions::print("There are no available sound devices.");
+                return 1;
+            }
+
+            RtAudio::StreamOptions options;
+            options.streamName = "gdpd";
+            options.flags = RTAUDIO_SCHEDULE_REALTIME;
+            if(m_audio.getCurrentApi() != RtAudio::MACOSX_CORE) {
+                options.flags |= RTAUDIO_MINIMIZE_LATENCY;
+            }
+            try {
+                m_audio.openStream(&outParams, &inpParams, RTAUDIO_FLOAT32, 
+                                   m_sampleRate, &m_bufferFrames, &audioCallback, 
+                                   this, &options);
+                m_audio.startStream();
+                print("Stream started");
+            }
+            catch(RtAudioError& e) {
+                UtilityFunctions::print(e.getMessage().c_str());
+            }
         }
-        try {
-            m_audio.openStream(&outParams, &inpParams, RTAUDIO_FLOAT32, 
-                               m_sampleRate, &m_bufferFrames, &audioCallback, 
-                               this, &options);
-            m_audio.startStream();
-            print("Stream started");
+        else {
         }
-        catch(RtAudioError& e) {
-            UtilityFunctions::print(e.getMessage().c_str());
-        }
-    }
-    else {
-    }
 
-	//create message hook
-	m_pd.subscribe("to_gdpd");
-	m_pd.setReceiver(this);
-	m_init=true;
+        //create message hook
+        m_pd.subscribe("to_gdpd");
+        m_pd.setReceiver(this);
+        m_init=true;
+    }
 
 	print("Initialized");
 
@@ -241,6 +259,17 @@ void GdPd::stop() {
 	print("Stopped");
 }
 
+void GdPd::process(float delta) {
+    /*
+    if(m_udpServer!=NULL) {
+        lo_server_recv_noblock(m_udpServer, 10);
+    }*/
+}
+
+void GdPd::handleOSC(const char* path, const char* types, 
+                                lo_arg** argv, int argc, lo_message msg) {
+}
+
 void GdPd::processAudio(void *outputBuffer, void *inputBuffer, 
 						unsigned int nBufferFrames) {
 	int ticks = nBufferFrames / libpd_blocksize();
diff --git a/src/gdpd.h b/src/gdpd.h
index 90e8d59..b092b32 100644
--- a/src/gdpd.h
+++ b/src/gdpd.h
@@ -42,6 +42,8 @@
 #include "PdBase.hpp"
 #include "RtAudio.h"
 
+#include <lo/lo.h>
+
 using namespace godot;
 
 class GdPdStreamPlayback;
@@ -52,6 +54,18 @@ class GdPd : public Node, public pd::PdReceiver {
 
 protected:
 	static void _bind_methods();
+    static int oscHandler(const char *path, const char *types, 
+                            lo_arg ** argv,
+                            int argc, lo_message msg, void *user_data) {
+        GdPd* gdpd = (GdPd*)user_data; 
+        gdpd->handleOSC(path, types, argv, argc, msg);
+        return 0;
+    }
+    static void errorHandler(int num, const char *msg, const char *path){
+        std::cout<<"GdPd Error : "<<msg<<" "<<path<<std::endl;
+    }
+    virtual void handleOSC(const char* path, const char* types, 
+                            lo_arg** argv, int argc, lo_message msg){};
 
 private:
     bool m_realtime;
@@ -75,6 +89,13 @@ private:
     enum Mode {OSC, AUDIO, AUDIO_RT};
     int m_mode;
 
+    const int SENDER_PORT = 9211;
+    const int RECEIVER_PORT = 9212;
+    const std::string MULTICAST_ADDRESS = "239.210.211.212";
+    lo_server m_sender;
+    lo_server m_receiver;
+    int m_senderPort;
+    int m_receiverPort;
 
 private:
 	int start();
diff --git a/src/liblo b/src/liblo
new file mode 160000
index 0000000..a3dc0cb
--- /dev/null
+++ b/src/liblo
@@ -0,0 +1 @@
+Subproject commit a3dc0cb3b73d0cfdf31463f26df72342f3c26e2b
-- 
GitLab