Carla API tester app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

611 lines
14 KiB

/*
* Copyright (C) 2021 Alexandros Theodotou <alex at zrythm dot org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdbool.h>
#include <stdlib.h>
#include "carla_api_tester_app.h"
#include <gtk/gtk.h>
/** This is declared extern in zrythm_app.h. */
CarlaApiTesterApp * carla_api_tester_app = NULL;
G_DEFINE_TYPE (
CarlaApiTesterApp, carla_api_tester_app,
GTK_TYPE_APPLICATION)
/**
* Tick callback for the plugin UI.
*/
static int
carla_plugin_tick_cb (
GtkWidget * widget,
GdkFrameClock * frame_clock,
CarlaApiTesterApp * self)
{
self->native_plugin_descriptor->ui_idle (
self->native_plugin_handle);
return G_SOURCE_CONTINUE;
}
static uint32_t
host_get_buffer_size (
NativeHostHandle handle)
{
uint32_t buffer_size = 512;
return buffer_size;
}
static double
host_get_sample_rate (
NativeHostHandle handle)
{
double sample_rate = 44000.0;
return sample_rate;
}
static bool
host_is_offline (
NativeHostHandle handle)
{
return false;
}
static const NativeTimeInfo*
host_get_time_info (
CarlaApiTesterApp * self)
{
return &self->time_info;
}
static bool
host_write_midi_event (
CarlaApiTesterApp * self,
const NativeMidiEvent * event)
{
g_message ("write midi event");
return 0;
}
static void
host_ui_parameter_changed (
CarlaApiTesterApp * self,
uint32_t index,
float value)
{
g_message ("handle ui param changed");
}
static void
host_ui_custom_data_changed (
CarlaApiTesterApp * self,
const char* key,
const char* value)
{
}
static void
host_ui_closed (
CarlaApiTesterApp * self)
{
g_message ("ui closed");
}
static intptr_t
host_dispatcher (
CarlaApiTesterApp * self,
NativeHostDispatcherOpcode opcode,
int32_t index,
intptr_t value,
void* ptr,
float opt)
{
/* TODO */
g_message ("host dispatcher (opcode %d)", opcode);
switch (opcode)
{
case NATIVE_HOST_OPCODE_HOST_IDLE:
/* some expensive computation is happening.
* this is used so that the GTK ui does not
* block */
/* note: disabled because some logic depends
* on this plugin being activated */
#if 0
while (gtk_events_pending ())
{
gtk_main_iteration ();
}
#endif
break;
#if 0
case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
/* falktx: you will need to call the new
* juce functions, then return 1 on the
* INTERNAL_PLUGIN host opcode.
* when that opcode returns 1, carla-plugin
* will let the host do the juce idling
* which is for the best here, since you want
* to show UIs without carla itself */
return 1;
break;
#endif
case NATIVE_HOST_OPCODE_GET_FILE_PATH:
g_debug ("get file path");
g_return_val_if_fail (ptr, 0);
if (!strcmp ((char *) ptr, "carla"))
{
g_debug ("ptr is carla");
}
break;
case NATIVE_HOST_OPCODE_UI_RESIZE:
g_debug ("ui resize");
/* TODO handle UI resize */
break;
case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
g_debug ("ui touch");
break;
case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
/* TODO handle UI close */
g_debug ("UI unavailable");
break;
default:
break;
}
return 0;
}
static void
engine_callback (
CarlaApiTesterApp * self,
EngineCallbackOpcode action,
uint plugin_id,
int val1,
int val2,
int val3,
float valf,
const char * val_str)
{
switch (action)
{
case ENGINE_CALLBACK_UI_STATE_CHANGED:
switch (val1)
{
case 0:
case -1:
g_message ("plugin became hidden");
break;
case 1:
g_message ("plugin became visible");
break;
}
break;
default:
break;
}
}
static PluginType
get_plugin_type_from_str (
const char * str)
{
if (!strcmp (str, "lv2"))
{
return PLUGIN_LV2;
}
else if (!strcmp (str, "ladspa"))
{
return PLUGIN_LADSPA;
}
else if (!strcmp (str, "dssi"))
{
return PLUGIN_DSSI;
}
else if (!strcmp (str, "vst2"))
{
return PLUGIN_VST2;
}
else if (!strcmp (str, "vst3"))
{
return PLUGIN_VST3;
}
else if (!strcmp (str, "au"))
{
return PLUGIN_AU;
}
else if (!strcmp (str, "sfz"))
{
return PLUGIN_SFZ;
}
else if (!strcmp (str, "sf2"))
{
return PLUGIN_SF2;
}
}
/*
* Called after startup if no filename is passed on
* command line.
*/
static void
carla_api_tester_app_activate (
GApplication * _app)
{
g_message ("Activating...");
CarlaApiTesterApp * self =
CARLA_API_TESTER_APP (_app);
self->native_host_descriptor.handle = self;
self->native_host_descriptor.uiName =
"Carla API tester";
self->native_host_descriptor.uiParentId = 0;
/* set resources dir */
const char * carla_filename =
carla_get_library_filename ();
char * tmp = g_path_get_dirname (carla_filename);
char * dir = g_path_get_dirname (tmp);
g_free (tmp);
tmp = g_path_get_dirname (dir);
g_free (dir);
dir = tmp;
self->native_host_descriptor.resourceDir =
g_build_filename (
dir, "share", "carla", "resources", NULL);
g_free (dir);
self->native_host_descriptor.get_buffer_size =
host_get_buffer_size;
self->native_host_descriptor.get_sample_rate =
host_get_sample_rate;
self->native_host_descriptor.is_offline =
host_is_offline;
self->native_host_descriptor.get_time_info =
(const NativeTimeInfo * (*)(void *))
host_get_time_info;
self->native_host_descriptor.write_midi_event =
(_Bool (*)(void *, const NativeMidiEvent *))
host_write_midi_event;
self->native_host_descriptor.
ui_parameter_changed =
(void (*)(void *, uint32_t, float))
host_ui_parameter_changed;
self->native_host_descriptor.
ui_custom_data_changed =
(intptr_t (*)(void *, NativeHostDispatcherOpcode, int32_t, intptr_t, void *, float))
host_ui_custom_data_changed;
self->native_host_descriptor.ui_closed =
(void (*)(void *))
host_ui_closed;
self->native_host_descriptor.ui_open_file =
NULL;
self->native_host_descriptor.ui_save_file =
NULL;
self->native_host_descriptor.dispatcher =
(intptr_t (*)(CarlaApiTesterApp *, NativeHostDispatcherOpcode, int32_t, intptr_t, void *, float))
host_dispatcher;
self->time_info.bbt.valid = 1;
/* instantiate the plugin to get its info */
self->native_plugin_descriptor =
carla_get_native_rack_plugin ();
self->native_plugin_handle =
self->native_plugin_descriptor->instantiate (
&self->native_host_descriptor);
self->host_handle =
carla_create_native_plugin_host_handle (
self->native_plugin_descriptor,
self->native_plugin_handle);
/* set binary paths */
g_message (
"setting carla engine option "
"[ENGINE_OPTION_PATH_BINARIES] to '%s'",
CONFIGURE_BINDIR);
carla_set_engine_option (
self->host_handle,
ENGINE_OPTION_PATH_BINARIES, 0,
CONFIGURE_BINDIR);
#if 0
/* set lv2 path */
carla_set_engine_option (
self->host_handle,
ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2,
PLUGIN_MANAGER->lv2_path);
/* set UI scale factor */
carla_set_engine_option (
self->host_handle,
ENGINE_OPTION_FRONTEND_UI_SCALE,
(int)
((float) self->plugin->ui_scale_factor *
1000.f),
NULL);
/* set whether UI should stay on top */
if (!ZRYTHM_TESTING &&
g_settings_get_boolean (
S_P_PLUGINS_UIS, "stay-on-top"))
{
carla_set_engine_option (
self->host_handle,
ENGINE_OPTION_UIS_ALWAYS_ON_TOP, true,
NULL);
}
#endif
g_message (
"%s: using bridge mode %s", __func__,
self->bridge);
if (!strcmp (self->bridge, "full"))
{
carla_set_engine_option (
self->host_handle,
ENGINE_OPTION_PREFER_PLUGIN_BRIDGES,
true, NULL);
}
else if (!strcmp (self->bridge, "ui"))
{
carla_set_engine_option (
self->host_handle,
ENGINE_OPTION_PREFER_UI_BRIDGES,
true, NULL);
}
int ret = -1;
PluginType type =
get_plugin_type_from_str (self->format);
if (!strcmp (self->format, "lv2") ||
!strcmp (self->format, "au"))
{
g_message ("uri %s", self->plugin);
ret =
carla_add_plugin (
self->host_handle,
self->woe32_binary ?
BINARY_WIN32 : BINARY_NATIVE,
type, NULL, "name",
self->plugin, 0, NULL, 0);
}
else if (!strcmp (self->format, "vst2") ||
!strcmp (self->format, "vst3"))
{
ret =
carla_add_plugin (
self->host_handle,
self->woe32_binary ?
BINARY_WIN32 : BINARY_NATIVE,
type, self->plugin, "name",
/* TODO UNIQUE ID */
"name", 0, NULL, 0);
}
else if (!strcmp (self->format, "dssi") ||
!strcmp (self->format, "ladspa"))
{
ret =
carla_add_plugin (
self->host_handle, BINARY_NATIVE,
type, self->plugin, "name",
self->plugin, 0, NULL, 0);
}
else if (!strcmp (self->format, "sfz") ||
!strcmp (self->format, "sf2"))
{
ret =
carla_add_plugin (
self->host_handle,
BINARY_NATIVE,
type, self->plugin, "name",
"name", 0, NULL, 0);
}
else
{
g_error ("unknown format %s", self->format);
}
if (ret != 1)
{
g_error (
"Error adding carla plugin: %s",
carla_get_last_error (self->host_handle));
}
/* enable various messages */
#define ENABLE_OPTION(x) \
carla_set_option ( \
self->host_handle, 0, \
PLUGIN_OPTION_##x, true)
ENABLE_OPTION (FORCE_STEREO);
ENABLE_OPTION (SEND_CONTROL_CHANGES);
ENABLE_OPTION (SEND_CHANNEL_PRESSURE);
ENABLE_OPTION (SEND_NOTE_AFTERTOUCH);
ENABLE_OPTION (SEND_PITCHBEND);
ENABLE_OPTION (SEND_ALL_SOUND_OFF);
ENABLE_OPTION (SEND_PROGRAM_CHANGES);
/* add engine callback */
carla_set_engine_callback (
self->host_handle, engine_callback, self);
g_message ("done");
}
/**
* Called when a filename is passed to the command line
* instead of activate.
*
* Always gets called after startup and before the tasks.
*/
static void
carla_api_tester_app_open (
GApplication * _app,
GFile ** files,
gint n_files,
const gchar * hint)
{
g_message ("Opening...");
#if 0
g_warn_if_fail (n_files == 1);
GFile * file = files[0];
ZRYTHM->open_filename =
g_file_get_path (file);
g_message ("open %s", ZRYTHM->open_filename);
#endif
g_message ("done");
}
/**
* First function that gets called.
*/
static void
carla_api_tester_app_startup (
GApplication * app)
{
g_message ("Starting up...");
CarlaApiTesterApp * self =
CARLA_API_TESTER_APP (app);
G_APPLICATION_CLASS (
carla_api_tester_app_parent_class)->
startup (G_APPLICATION (self));
g_message (
"called startup on G_APPLICATION_CLASS");
bool ret =
g_application_get_is_registered (
G_APPLICATION (self));
bool remote =
g_application_get_is_remote (
G_APPLICATION (self));
GMenuModel * app_menu_model =
gtk_application_get_app_menu (
GTK_APPLICATION (app));
bool prefers_app_menu =
gtk_application_prefers_app_menu (
GTK_APPLICATION (app));
g_message (
"application registered: %d, is remote %d, "
"app menu exists: %d, prefers app menu: %d",
ret, remote, app_menu_model ? true : false,
prefers_app_menu);
g_message (
"application resources base path: %s",
g_application_get_resource_base_path (
G_APPLICATION (app)));
g_message ("done");
}
/**
* Called immediately after the main GTK loop
* terminates.
*
* This is also called manually on SIGINT.
*/
static void
carla_api_tester_app_on_shutdown (
GApplication * application,
CarlaApiTesterApp * self)
{
g_message ("Shutting down...");
g_message ("done");
}
/**
* Creates the CarlaApiTester GApplication.
*/
CarlaApiTesterApp *
carla_api_tester_app_new (
const char * plugin,
const char * format,
const char * bridge,
bool woe32_binary)
{
CarlaApiTesterApp * self = g_object_new (
CARLA_API_TESTER_APP_TYPE,
/* if an ID is provided, this application
* becomes unique (only one instance allowed) */
/*"application-id", "org.carla-api-tester.App",*/
NULL);
carla_api_tester_app = self;
self->bridge = bridge;
self->plugin = plugin;
self->format = format;
self->woe32_binary = woe32_binary;
/* add shutdown handler */
g_signal_connect (
G_OBJECT (self), "shutdown",
G_CALLBACK (carla_api_tester_app_on_shutdown),
self);
return self;
}
static void
finalize (
CarlaApiTesterApp * self)
{
g_message ("finalizing app...");
g_message ("done");
}
static void
carla_api_tester_app_class_init (
CarlaApiTesterAppClass * class)
{
G_APPLICATION_CLASS (class)->activate =
carla_api_tester_app_activate;
G_APPLICATION_CLASS (class)->startup =
carla_api_tester_app_startup;
G_APPLICATION_CLASS (class)->open =
carla_api_tester_app_open;
GObjectClass * klass = G_OBJECT_CLASS (class);
klass->finalize = (GObjectFinalizeFunc) finalize;
}
static void
carla_api_tester_app_init (
CarlaApiTesterApp * self)
{
g_message ("initing app");
g_message ("done");
}