diff --git a/.gitmodules b/.gitmodules index 2e035855f8a44c3c9035b0aad61fc9dd90e33e78..1527b7a1e22cf4fbb1f3978292fcb2533ac784be 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,4 @@ [submodule "src/godot-cpp"] path = src/godot-cpp url = https://github.com/GodotNativeTools/godot-cpp + branch = 4.2 diff --git a/SConstruct b/SConstruct index 1ced9da4bc2d12fa6638fe72a235cfd7d55105e5..43122b64a3e6c5b9232174008b12d614c7032327 100644 --- a/SConstruct +++ b/SConstruct @@ -29,7 +29,10 @@ env = SConscript("src/godot-cpp/SConstruct") # - CPPDEFINES are for pre-processor defines # - LINKFLAGS are for linking flags -env.Append(CPPPATH=['.', 'src/libpd/libpd_wrapper', 'src/libpd/libpd_wrapper/util', 'src/libpd/cpp','src/libpd/pure-data/src', 'src/rtaudio']) +env.Append(CPPPATH=['.', 'src/libpd/libpd_wrapper', + 'src/libpd/libpd_wrapper/util', + 'src/libpd/cpp','src/libpd/pure-data/src', 'src/rtaudio'], + CCFLAGS=["-fexceptions"]) env.Append(CFLAGS=['-DUSEAPI_DUMMY', '-DPD', '-DHAVE_UNISTD_H',\ '-D_GNU_SOURCE','-DLIBPD_EXTRA']) diff --git a/demo/Main.tscn b/demo/Main.tscn index 37ec47b180647eca5791778c1713e8bc67423eb9..810ce477b0a1883d9e949d9c95ca2ea3c2ab72d0 100644 --- a/demo/Main.tscn +++ b/demo/Main.tscn @@ -6,6 +6,7 @@ script/source = "extends Control @onready var _gdpd = GdPd.new() func _ready(): + add_child(_gdpd) pass func _process(delta): @@ -42,6 +43,23 @@ func _on_Start_pressed() : # listen to messages sent with [send to_godot] _gdpd.subscribe(\"to_godot\") + + +func _on_start_non_rt_pressed(): + # initialise without realtime + _gdpd.init_nort() + + # the patch path should be absolute + _load_patch(ProjectSettings.globalize_path(\"res://patch1.pd\")) + _load_patch(ProjectSettings.globalize_path(\"res://patch2.pd\")) + + # send message to [receive from_godot] with one symbol + _gdpd.start_message(1) + _gdpd.add_symbol(\"hello\") + _gdpd.finish_list(\"from_godot\") + + # listen to messages sent with [send to_godot] + _gdpd.subscribe(\"to_godot\") func _on_Stop_pressed(): _gdpd.closefile(\"patch1.pd\") @@ -62,6 +80,8 @@ func _on_HSlider2_value_changed(value): _gdpd.add_symbol(\"pitch\") _gdpd.add_float(value) _gdpd.finish_list(\"from_godot\") + + " [node name="Control" type="Control"] @@ -73,21 +93,29 @@ grow_horizontal = 2 grow_vertical = 2 script = SubResource("1") +[node name="Start" type="Button" parent="."] +layout_mode = 0 +offset_left = 72.0 +offset_top = 80.0 +offset_right = 176.0 +offset_bottom = 128.0 +text = "Start" + [node name="Stop" type="Button" parent="."] layout_mode = 0 -offset_left = 213.0 -offset_top = 83.0 -offset_right = 316.0 -offset_bottom = 129.0 +offset_left = 296.0 +offset_top = 80.0 +offset_right = 399.0 +offset_bottom = 128.0 text = "Stop" -[node name="Start" type="Button" parent="."] +[node name="StartNonRT" type="Button" parent="."] layout_mode = 0 -offset_left = 73.0 -offset_top = 83.0 -offset_right = 177.0 -offset_bottom = 134.0 -text = "Start" +offset_left = 184.0 +offset_top = 80.0 +offset_right = 290.0 +offset_bottom = 128.0 +text = "Start Non RT" [node name="HSlider" type="HSlider" parent="."] layout_mode = 0 @@ -121,7 +149,8 @@ offset_right = 204.0 offset_bottom = 260.0 text = "Send pitch to patch2" -[connection signal="pressed" from="Stop" to="." method="_on_Stop_pressed"] [connection signal="pressed" from="Start" to="." method="_on_Start_pressed"] +[connection signal="pressed" from="Stop" to="." method="_on_Stop_pressed"] +[connection signal="pressed" from="StartNonRT" to="." method="_on_start_non_rt_pressed"] [connection signal="value_changed" from="HSlider" to="." method="_on_HSlider_value_changed"] [connection signal="value_changed" from="HSlider2" to="." method="_on_HSlider2_value_changed"] diff --git a/src/gdpd.cpp b/src/gdpd.cpp index 40481dbb3e6a72e75bc434d69ca682fbd43c71d9..aae9f73dfe9545a101ab79f7a5bd805003093ae2 100644 --- a/src/gdpd.cpp +++ b/src/gdpd.cpp @@ -2,7 +2,7 @@ * gdpd.cpp * Part of GdPd * 2023- see README for AUTHORS - * https://gitlab.univ-lille.fr/immersync + * https://gitlab.univ-lille.fr/ivmi ****************************************************************************/ /* * This program is free software; you can redistribute it and/or modify @@ -24,14 +24,16 @@ #include <godot_cpp/core/class_db.hpp> - #include <godot_cpp/classes/os.hpp> #include <godot_cpp/classes/global_constants.hpp> #include <godot_cpp/classes/label.hpp> #include <godot_cpp/variant/utility_functions.hpp> +#include <godot_cpp/classes/audio_server.hpp> using namespace godot; +const int PCM_BUFFER_SIZE = 4096 * 4; + void GdPd::_bind_methods() { ADD_GROUP("GdPd", "gdpd_"); @@ -40,7 +42,8 @@ void GdPd::_bind_methods() { ClassDB::bind_method(D_METHOD("get_available_output_devices"), &GdPd::get_available_output_devices); ClassDB::bind_method(D_METHOD("init_devices"), &GdPd::init_devices); - ClassDB::bind_method(D_METHOD("init"), &GdPd::init); + ClassDB::bind_method(D_METHOD("init_parameters"), &GdPd::init_parameters); + ClassDB::bind_method(D_METHOD("init_nort"), &GdPd::init_nort); ClassDB::bind_method(D_METHOD("stop"), &GdPd::stop); ClassDB::bind_method(D_METHOD("openfile"), &GdPd::openfile); ClassDB::bind_method(D_METHOD("closefile"), &GdPd::closefile); @@ -52,6 +55,7 @@ void GdPd::_bind_methods() { ClassDB::bind_method(D_METHOD("add_float"), &GdPd::add_float); ClassDB::bind_method(D_METHOD("finish_list"), &GdPd::finish_list); ClassDB::bind_method(D_METHOD("set_volume"), &GdPd::set_volume); + } GdPd::GdPd() : m_vol(1) { @@ -62,12 +66,13 @@ GdPd::GdPd() : m_vol(1) { GdPd::~GdPd() {} + + int GdPd::audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData){ GdPd* gdpd = static_cast<GdPd*>(userData); - gdpd->processAudio(outputBuffer, inputBuffer, nBufferFrames, streamTime, - status, userData); + gdpd->processAudio(outputBuffer, inputBuffer, nBufferFrames); return 0; } @@ -113,10 +118,13 @@ int GdPd::init_devices(String inputDevice, String outputDevice) { m_nbOutputs = outInfo.outputChannels; m_sampleRate = outInfo.preferredSampleRate; m_bufferFrames = 128; + + m_realtime=true; + return start(); } -int GdPd::init(int nbInputs, int nbOutputs, int sampleRate, int bufferSize) { +int 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); @@ -125,17 +133,54 @@ int GdPd::init(int nbInputs, int nbOutputs, int sampleRate, int bufferSize) { m_nbOutputs = std::min<int>(nbOutputs, outInfo.outputChannels); m_sampleRate = sampleRate; 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; + m_realtime=false; + + + if(!m_pd.init(m_nbInputs, m_nbOutputs, m_sampleRate, true)) { + print("GDPD : Error starting libpd"); + return 1; + } + + // Create message hook + m_pd.subscribe("to_gdpd"); + m_pd.setReceiver(this); + m_init=true; + + // Create stream player + AudioStreamPlayer* p = new AudioStreamPlayer(); + add_child(p); + + Ref<GdPdStream> s; + s.instantiate(); + s->setGdPd(this); + + p->set_stream(s); + p->play(); + + print("Initialized"); + + return 0; +} + 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)); + 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)) { @@ -148,27 +193,32 @@ int GdPd::start() { //start dsp m_pd.computeAudio(true); - //intialize rtaudio - if(m_audio.getDeviceCount()==0){ - UtilityFunctions::print("There are no available sound devices."); - } - - 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()); - } + if(m_realtime) { + //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()); + } + } + else { + } //create message hook m_pd.subscribe("to_gdpd"); @@ -182,15 +232,18 @@ int GdPd::start() { void GdPd::stop() { m_init=false; - m_audio.stopStream(); - m_audio.closeStream(); + if(m_realtime) { + m_audio.stopStream(); + m_audio.closeStream(); + } + else { + } m_pd.computeAudio(false); print("Stopped"); } void GdPd::processAudio(void *outputBuffer, void *inputBuffer, - unsigned int nBufferFrames, double streamTime, - RtAudioStreamStatus status, void *userData) { + unsigned int nBufferFrames) { int ticks = nBufferFrames / libpd_blocksize(); m_pd.processFloat(ticks, (float*)inputBuffer, (float*)outputBuffer); @@ -301,4 +354,44 @@ void GdPd::set_volume(float vol) { m_vol=vol; } +/* GdPdStream */ +GdPdStream::GdPdStream() { + +} +Ref<AudioStreamPlayback> GdPdStream::_instantiate_playback() const { + Ref<GdPdStreamPlayback> gdPlayback; + gdPlayback.instantiate(); + gdPlayback->m_base = Ref<GdPdStream>(this); + return gdPlayback; +} + +#define zeromem(to, count) memset(to, 0, count) + +/* GdPdStreamplayback */ +GdPdStreamPlayback::GdPdStreamPlayback() { + m_buffer.resize(4096*2,0.0); + AudioServer::get_singleton()->lock(); + pcm_buffer = memalloc(PCM_BUFFER_SIZE); + zeromem(pcm_buffer, PCM_BUFFER_SIZE); + AudioServer::get_singleton()->unlock(); +} + +GdPdStreamPlayback::~GdPdStreamPlayback() { + +} + +int32_t GdPdStreamPlayback::_mix_resampled(AudioFrame *buffer, + int32_t nbFrames) { + m_buffer.resize(4096*2, 0.0); + m_base->getGdPd()->processAudio(m_buffer.data(), NULL, nbFrames); + for(int i = 0; i < nbFrames; i++) { + buffer[i].left = m_buffer[i*2]; + buffer[i].right = m_buffer[i*2+1]; + } + return nbFrames; +} + +double GdPdStreamPlayback::_get_stream_sampling_rate() const { + return AudioServer::get_singleton()->get_mix_rate(); +} diff --git a/src/gdpd.h b/src/gdpd.h index e0b97aacb3c2625bce0a312c6025ec58ed6f2ffb..44be93ac1792e34832b0a395aa951cad337da5bc 100644 --- a/src/gdpd.h +++ b/src/gdpd.h @@ -1,7 +1,8 @@ /*************************************************************************** - * gd4pd.h + * gdpd.h * Part of GdPd * 2023- see README for AUTHORS + * https://gitlab.univ-lille.fr/ivmi ****************************************************************************/ /* * This program is free software; you can redistribute it and/or modify @@ -34,19 +35,26 @@ #include <godot_cpp/core/binder_common.hpp> #include <godot_cpp/classes/engine.hpp> #include <godot_cpp/classes/audio_stream.hpp> +#include <godot_cpp/classes/audio_stream_playback_resampled.hpp> +#include <godot_cpp/classes/audio_stream_player.hpp> +#include <godot_cpp/classes/audio_frame.hpp> #include "PdBase.hpp" #include "RtAudio.h" using namespace godot; -class GdPd : public AudioStream, public pd::PdReceiver { - GDCLASS(GdPd, AudioStream); +class GdPdStreamPlayback; + + +class GdPd : public Node, public pd::PdReceiver { + GDCLASS(GdPd, Node); protected: static void _bind_methods(); private: + bool m_realtime; float *m_inBuf; float *m_outBuf; Array* m_messages; @@ -64,6 +72,8 @@ private: bool m_init; + int start(); + public: GdPd(); @@ -72,9 +82,9 @@ public: //libpd functions Array get_available_input_devices(); Array get_available_output_devices(); + int init_nort(); int init_devices(String inputDevice, String outputDevice); - int init(int nbInputs, int nbOutputs, int sampleRate, int bufferSize); - int start(); + int init_parameters(int nbInputs, int nbOutputs, int sampleRate, int bufferSize); void stop(); bool openfile(String basename, String dirname); void closefile(String basename); @@ -101,9 +111,56 @@ public: unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData); void processAudio(void *outputBuffer, void *inputBuffer, - unsigned int nBufferFrames, double streamTime, - RtAudioStreamStatus status, void *userData); + unsigned int nBufferFrames); +}; + +class GdPdStream : public AudioStream { + GDCLASS(GdPdStream, AudioStream); + friend class GdPdStreamPlayback; + +private: + GdPd* m_gdpd; + +protected: + static void _bind_methods(){} + +public: + GdPdStream(); + Ref<AudioStreamPlayback> _instantiate_playback() const override; + + virtual String _get_stream_name() const {return "GdPd";} + virtual double _get_length() const { return 0; } + + void setGdPd(GdPd* gdpd){m_gdpd=gdpd;} + GdPd* getGdPd(){return m_gdpd;} }; +class GdPdStreamPlayback: public AudioStreamPlaybackResampled { + GDCLASS(GdPdStreamPlayback, AudioStreamPlaybackResampled); + friend class GdPdStream; + +protected: + Ref<GdPdStream> m_base; + std::vector<float> m_buffer; + bool active; + void *pcm_buffer; + + static void _bind_methods(){} + +public: + GdPdStreamPlayback(); + ~GdPdStreamPlayback(); + + int32_t _mix_resampled(AudioFrame *dst_buffer, int32_t frame_count) override; + double _get_stream_sampling_rate() const override; + + bool _is_playing() const override {return active;} + void _start(double from_pos) override {active=true;} + void _seek(double position) override {} + void _stop() override {active=false;} +}; + + + #endif diff --git a/src/godot-cpp b/src/godot-cpp index 28494f0bd59f9c35289524e503648ed8b4baaa59..98c143a48365f3f3bf5f99d6289a2cb25e6472d1 160000 --- a/src/godot-cpp +++ b/src/godot-cpp @@ -1 +1 @@ -Subproject commit 28494f0bd59f9c35289524e503648ed8b4baaa59 +Subproject commit 98c143a48365f3f3bf5f99d6289a2cb25e6472d1 diff --git a/src/register_types.cpp b/src/register_types.cpp index 3097aa76755a0ca5256cbc0f1c282e5fdc7f5bad..574b624de8725a9f0962a586f258646d6fef219c 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -15,6 +15,8 @@ void initialize_gdpd_module(ModuleInitializationLevel p_level) { return; } ClassDB::register_class<GdPd>(); + ClassDB::register_class<GdPdStream>(); + ClassDB::register_class<GdPdStreamPlayback>(); } void uninitialize_gdpd_module(ModuleInitializationLevel p_level) { diff --git a/src/rtaudio/RtAudio.os b/src/rtaudio/RtAudio.os index 4dd83fb517790ff7e696d80faeef48fc2feb7ed5..2ff0399f747003294a06aead1af63b69ec45ff03 100644 Binary files a/src/rtaudio/RtAudio.os and b/src/rtaudio/RtAudio.os differ