diff --git a/.gitignore b/.gitignore
index c95efac5ec16b1a6a4d48c1f89fd10e941455584..fa09e8c011c624117d34700cc93ffcf9ea70353d 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 aee1cced30f16eb4acb57ad58313c4765ef703b2..6c592ef7f6021bea930ccee10cd1b1abe256e65f 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 dff6e8f5fbcc598447ec883d4efc5e2b12a2c5cc..ba310745cb14a1ab5b333f7837c5e32bbe1fe8dd 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 dd3ecf60f4e091218b2aaa2d0d9e81067694af8c..83f3d8ac51beb03629177421266daaba5164646c 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
Binary files a/demo/patch1.pd and b/demo/patches/patch1.pd differ
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
Binary files /dev/null and b/patches/gdpd.pd differ
diff --git a/patches/gdpd_receive.pd b/patches/gdpd_receive.pd
new file mode 100644
index 0000000000000000000000000000000000000000..d5fa2d14c1273045d6dd60cd2a1e9e5a1a37b48f
Binary files /dev/null and b/patches/gdpd_receive.pd differ
diff --git a/patches/gdpd_send.pd b/patches/gdpd_send.pd
new file mode 100644
index 0000000000000000000000000000000000000000..61e18c7c8ea264bcef5e1d3525271bb6a4d40f0f
Binary files /dev/null and b/patches/gdpd_send.pd differ
diff --git a/src/gdpd.cpp b/src/gdpd.cpp
index e5dd8aa6d4cc62b9b7c0edecfda8fa084d0cbfa6..fd6f6fabee20b6fbda511abb5864fe080f84df2b 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 90e8d590b86979cb732ee61c7c101b14fe9e4900..b092b32a6ed8a420eb5e86d0b4ec1b6c57241a0e 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 0000000000000000000000000000000000000000..a3dc0cb3b73d0cfdf31463f26df72342f3c26e2b
--- /dev/null
+++ b/src/liblo
@@ -0,0 +1 @@
+Subproject commit a3dc0cb3b73d0cfdf31463f26df72342f3c26e2b