diff --git a/src/detail/standalone/entry.cpp b/src/detail/standalone/entry.cpp index 7e3c85f1..502e8bef 100644 --- a/src/detail/standalone/entry.cpp +++ b/src/detail/standalone/entry.cpp @@ -29,7 +29,10 @@ std::shared_ptr mainCreatePlugin(const clap_plugin_entry *ee, cons return nullptr; } - standaloneHost = std::make_unique(); + if (!standaloneHost) + { + standaloneHost = std::make_unique(); + } if (clapId.empty()) { @@ -86,6 +89,10 @@ std::shared_ptr getMainPlugin() StandaloneHost *getStandaloneHost() { + if (!standaloneHost) + { + standaloneHost = std::make_unique(); + } return standaloneHost.get(); } diff --git a/src/detail/standalone/linux/gtkutils.cpp b/src/detail/standalone/linux/gtkutils.cpp index 15bf96f7..0ba56e81 100644 --- a/src/detail/standalone/linux/gtkutils.cpp +++ b/src/detail/standalone/linux/gtkutils.cpp @@ -7,18 +7,70 @@ #include "gtkutils.h" #include "detail/standalone/standalone_details.h" +#include "detail/standalone/standalone_host.h" +#include "detail/standalone/entry.h" #include -namespace freeaudio::clap_wrapper::standalone::linux +namespace freeaudio::clap_wrapper::standalone::linux_standalone { static void activate(GtkApplication *app, gpointer user_data) { + GdkDisplay *display = gdk_display_get_default(); + + if (!GDK_IS_X11_DISPLAY(display)) + { + std::cout << "clap-wrapper standalone requires GDK X11 backend" << std::endl; + std::terminate(); + } + auto g = (GtkGui *)user_data; g->setupPlugin(app); } +static gboolean onResize(GtkWidget *wid, GdkEventConfigure *event, gpointer user_data) +{ + auto g = (GtkGui *)user_data; + return g->resizePlugin(wid, event->width, event->height); +} + +bool GtkGui::resizePlugin(GtkWidget *wid, uint32_t w, uint32_t h) +{ + if (plugin->_ext._gui) + { + auto gui = plugin->_ext._gui; + auto p = plugin->_plugin; + + if (!gui->can_resize(p)) + { + gui->get_size(p, &w, &h); + gtk_window_resize(GTK_WINDOW(wid), w, h); + return TRUE; + } + +#if 1 + gui->set_size(p, w, h); +#else + // For some reason, this freaks out with drags in surge on gtk on linux. + auto adj = false; + auto aw = w, ah = h; + gui->adjust_size(p, &aw, &ah); + gui->set_size(p, aw, ah); + + if (aw != w || ah != h) adj = true; + + w = aw; + h = ah; + + gtk_window_resize(GTK_WINDOW(wid), w, h); + + return adj; +#endif + } + return FALSE; +} + void GtkGui::setupPlugin(_GtkApplication *app) { GtkWidget *window; @@ -34,15 +86,27 @@ void GtkGui::setupPlugin(_GtkApplication *app) uint32_t w, h; ui->get_size(p, &w, &h); + ui->adjust_size(p, &w, &h); window = gtk_application_window_new(app); - gtk_window_set_title(GTK_WINDOW(window), "Standalone Window"); + gtk_window_set_title(GTK_WINDOW(window), p->desc->name); gtk_window_set_default_size(GTK_WINDOW(window), w, h); + + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + // Create the 'inner window' + GtkWidget *frame = gtk_frame_new("Inner 'Window'"); + gtk_widget_set_size_request(frame, w, h); + gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); + + g_signal_connect(window, "configure-event", G_CALLBACK(onResize), this); + gtk_widget_show_all(window); clap_window win; win.api = CLAP_WINDOW_API_X11; - auto gw = gtk_widget_get_window(GTK_WIDGET(window)); + auto gw = gtk_widget_get_window(GTK_WIDGET(frame)); win.x11 = GDK_WINDOW_XID(gw); ui->set_parent(p, &win); ui->show(p); @@ -162,6 +226,73 @@ int GtkGui::runFD(int fd, clap_posix_fd_flags_t flags) return true; } -} // namespace freeaudio::clap_wrapper::standalone::linux +bool GtkGui::parseCommandLine(int argc, char **argv) +{ + auto sah = freeaudio::clap_wrapper::standalone::getStandaloneHost(); + + auto [i, o, s] = sah->getDefaultAudioInOutSampleRate(); + + bool list_devices{false}; + int sampleRate{s}; + unsigned int inId{i}, outId{o}; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored \ + "-Wmissing-field-initializers" // other peoples errors are outside my scope +#endif + const GOptionEntry entries[] = { + {"list-devices", 'l', 0, G_OPTION_ARG_NONE, &list_devices, "List Input Output and MIDI Devices", + nullptr}, + {"sample-rate", 's', 0, G_OPTION_ARG_INT, &sampleRate, "Sample Rate", nullptr}, + {"input-device", 'i', 0, G_OPTION_ARG_INT, &inId, "Input Device (0 for no input)", nullptr}, + {"output-device", 'o', 0, G_OPTION_ARG_INT, &outId, "Output Device (0 for no input)", nullptr}, + {NULL}}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + GOptionContext *context; + GError *error = nullptr; + + context = g_option_context_new("Clap Wrapper Standalone"); + g_option_context_add_main_entries(context, entries, NULL); + + g_option_context_add_group(context, gtk_get_option_group(TRUE)); + if (!g_option_context_parse(context, &argc, &argv, &error)) + { + g_print("Failed to parse options: %s\n", error->message); + g_error_free(error); + return false; + } + + if (list_devices) + { + std::cout << "\n\nAvailable Audio Interfaces:\n\nOutput:\n"; + auto outD = sah->getOutputAudioDevices(); + for (auto &d : outD) + { + std::cout << " - " << d.name << " (id=" << d.ID << " channels=" << d.outputChannels << ")" + << std::endl; + } + + std::cout << "\nInput:\n"; + auto inD = sah->getInputAudioDevices(); + for (auto &d : inD) + { + std::cout << " - " << d.name << " (id=" << d.ID << " channels=" << d.outputChannels << ")" + << std::endl; + } + return false; + } + + LOG << "Post Argument Parse: inId=" << inId << " outId=" << outId << " sampleRate=" << sampleRate + << std::endl; + sah->setStartupAudio(inId, outId, sampleRate); + + return true; +} + +} // namespace freeaudio::clap_wrapper::standalone::linux_standalone #endif diff --git a/src/detail/standalone/linux/gtkutils.h b/src/detail/standalone/linux/gtkutils.h index b3ab4eec..b95a770d 100644 --- a/src/detail/standalone/linux/gtkutils.h +++ b/src/detail/standalone/linux/gtkutils.h @@ -5,22 +5,27 @@ #include "detail/standalone/standalone_host.h" struct _GtkApplication; // sigh their typedef screws up forward decls -namespace freeaudio::clap_wrapper::standalone::linux +struct _GtkWidget; + +namespace freeaudio::clap_wrapper::standalone::linux_standalone { struct GtkGui { _GtkApplication *app{nullptr}; std::shared_ptr plugin; + bool parseCommandLine(int argc, char **argv); + void initialize(freeaudio::clap_wrapper::standalone::StandaloneHost *); void setPlugin(std::shared_ptr); void runloop(int argc, char **argv); void shutdown(); void setupPlugin(_GtkApplication *app); + bool resizePlugin(_GtkWidget *wid, uint32_t w, uint32_t h); clap_id currTimer{8675309}; - std::mutex cbMutex; + std::mutex cbMutex{}; struct TimerCB { @@ -47,4 +52,4 @@ struct GtkGui bool unregister_fd(int fd); int runFD(int fd, clap_posix_fd_flags_t flags); }; -} // namespace freeaudio::clap_wrapper::standalone::linux \ No newline at end of file +} // namespace freeaudio::clap_wrapper::standalone::linux_standalone \ No newline at end of file diff --git a/src/detail/standalone/standalone_host.cpp b/src/detail/standalone/standalone_host.cpp index 03add15b..ecfaac95 100644 --- a/src/detail/standalone/standalone_host.cpp +++ b/src/detail/standalone/standalone_host.cpp @@ -248,6 +248,7 @@ const char *StandaloneHost::host_get_name() bool StandaloneHost::register_timer(uint32_t period_ms, clap_id *timer_id) { #if LIN && CLAP_WRAPPER_HAS_GTK3 + assert(gtkGui); return gtkGui->register_timer(period_ms, timer_id); #else return false; diff --git a/src/detail/standalone/standalone_host.h b/src/detail/standalone/standalone_host.h index 0a5d4740..91f40a5f 100644 --- a/src/detail/standalone/standalone_host.h +++ b/src/detail/standalone/standalone_host.h @@ -31,7 +31,7 @@ namespace freeaudio::clap_wrapper::standalone { #if LIN #if CLAP_WRAPPER_HAS_GTK3 -namespace linux +namespace linux_standalone { struct GtkGui; } @@ -183,7 +183,7 @@ struct StandaloneHost : Clap::IHost #if LIN #if CLAP_WRAPPER_HAS_GTK3 - freeaudio::clap_wrapper::standalone::linux::GtkGui *gtkGui{nullptr}; + freeaudio::clap_wrapper::standalone::linux_standalone::GtkGui *gtkGui{nullptr}; #endif #endif @@ -247,6 +247,17 @@ struct StandaloneHost : Clap::IHost int32_t sampleRate); void stopAudioThread(); + bool startupAudioSet{false}; + unsigned int startAudioIn{0}, startAudioOut{0}; + int startSampleRate{0}; + void setStartupAudio(unsigned int in, unsigned int out, int sr) + { + startupAudioSet = true; + startAudioIn = in; + startAudioOut = out; + startSampleRate = sr; + } + void activatePlugin(int32_t sr, int32_t minBlock, int32_t maxBlock); bool isActive{false}; diff --git a/src/detail/standalone/standalone_host_audio.cpp b/src/detail/standalone/standalone_host_audio.cpp index 199d590a..ab6088ef 100644 --- a/src/detail/standalone/standalone_host_audio.cpp +++ b/src/detail/standalone/standalone_host_audio.cpp @@ -30,13 +30,27 @@ int rtaCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrame return 0; } -void rtaErrorCallback(RtAudioErrorType, const std::string &errorText) +void rtaErrorCallback(RtAudioErrorType errorType, const std::string &errorText) { - LOG << "[ERROR] RtAudio reports '" << errorText << "'" << std::endl; - auto ae = getStandaloneHost()->displayAudioError; - if (ae) + if (errorType != RTAUDIO_OUTPUT_UNDERFLOW && errorType != RTAUDIO_INPUT_OVERFLOW) { - ae(errorText); + LOG << "[ERROR] RtAudio reports '" << errorText << "'" + << " " << errorType << std::endl; + auto ae = getStandaloneHost()->displayAudioError; + if (ae) + { + ae(errorText); + } + } + else + { + static bool reported = false; + if (!reported) + { + LOG << "[ERROR] RtAudio reports '" << errorText << "'" << std::endl; + LOG << "[ERROR] Supressing future underflow reports" << std::endl; + reported = true; + } } } @@ -64,8 +78,18 @@ void StandaloneHost::startAudioThread() { guaranteeRtAudioDAC(); - auto [in, out, sr] = getDefaultAudioInOutSampleRate(); - startAudioThreadOn(in, 2, numAudioInputs > 0, out, 2, numAudioOutputs > 0, sr); + if (startupAudioSet) + { + auto in = startAudioIn; + auto out = startAudioOut; + auto sr = startSampleRate; + startAudioThreadOn(in, 2, in > 0 && numAudioInputs > 0, out, 2, out > 0 && numAudioOutputs > 0, sr); + } + else + { + auto [in, out, sr] = getDefaultAudioInOutSampleRate(); + startAudioThreadOn(in, 2, numAudioInputs > 0, out, 2, numAudioOutputs > 0, sr); + } } std::vector filterDevicesBy(const std::unique_ptr &rtaDac, diff --git a/src/wrapasstandalone.cpp b/src/wrapasstandalone.cpp index 4f6d208a..e7ef094e 100644 --- a/src/wrapasstandalone.cpp +++ b/src/wrapasstandalone.cpp @@ -37,6 +37,18 @@ int main(int argc, char **argv) } } +#endif + +#if LIN +#if CLAP_WRAPPER_HAS_GTK3 + freeaudio::clap_wrapper::standalone::linux_standalone::GtkGui gtkGui{}; + + if (!gtkGui.parseCommandLine(argc, argv)) + { + return 1; + } + gtkGui.initialize(freeaudio::clap_wrapper::standalone::getStandaloneHost()); +#endif #endif if (!entry) @@ -54,9 +66,6 @@ int main(int argc, char **argv) #if LIN #if CLAP_WRAPPER_HAS_GTK3 - freeaudio::clap_wrapper::standalone::linux::GtkGui gtkGui{}; - - gtkGui.initialize(freeaudio::clap_wrapper::standalone::getStandaloneHost()); gtkGui.setPlugin(plugin); gtkGui.runloop(argc, argv); gtkGui.shutdown();