Skip to content
Snippets Groups Projects
Commit 5fd1ec15 authored by ale's avatar ale
Browse files

Start adding humanization UI controls

parent bc4080a9
Branches
No related tags found
No related merge requests found
......@@ -35,7 +35,8 @@ hydrumkit_la_LIBADD = libengine.la
hydrumkit_ui_la_SOURCES = \
plugin/ui.c \
plugin/uris.h \
plugin/lv2_util.h
plugin/lv2_util.h \
plugin/widgets/knob.c plugin/widgets/knob.h
hydrumkit_ui_la_CPPFLAGS = $(GTK_CFLAGS)
hydrumkit_ui_la_LDFLAGS = -module -avoid-version -shared
hydrumkit_ui_la_LIBADD = libengine.la $(GTK_LIBS)
......
......@@ -21,7 +21,7 @@ void write_sample(const char *filename, float *data_l, float *data_r, int num_fr
info.samplerate = SR;
info.channels = 2;
info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
sndfile = sf_open(filename, SFM_WRITE, &info);
sf_writef_float(sndfile, buf, num_frames);
......
......@@ -44,6 +44,7 @@ int voice_noteon(struct voice *voice, struct instrument *ins, int note, int velo
voice->in_preamble = 1;
voice->preamble_countdown = preamble;
} else {
voice->in_preamble = 0;
voice->playing = 1;
voice->ticks = 0;
}
......@@ -52,7 +53,7 @@ int voice_noteon(struct voice *voice, struct instrument *ins, int note, int velo
}
int voice_noteoff_if_note(struct voice *voice, int note) {
if ((voice->playing || voice->preamble_countdown) && voice->note == note) {
if ((voice->playing || voice->in_preamble) && voice->note == note) {
adsr_release(&voice->adsr);
return 1;
}
......@@ -60,7 +61,7 @@ int voice_noteoff_if_note(struct voice *voice, int note) {
}
int voice_noteoff_if_mute_group(struct voice *voice, int mute_group) {
if ((voice->playing || voice->preamble_countdown) && voice->mute_group == mute_group) {
if ((voice->playing || voice->in_preamble) && voice->mute_group == mute_group) {
adsr_release(&voice->adsr);
return 1;
}
......
......@@ -26,7 +26,7 @@ int voice_noteoff_if_mute_group(struct voice *, int);
void voice_process(struct voice *, float *, float *, int);
static inline int voice_is_playing(struct voice *voice) {
return voice->playing | voice->in_preamble;
return voice->playing || voice->in_preamble;
}
#endif
......@@ -22,6 +22,7 @@
#include <glib.h>
#include <gobject/gclosure.h>
#include <gtk/gtk.h>
#include "plugin/widgets/knob.h"
#include <stdint.h>
#include <stdlib.h>
......@@ -30,6 +31,8 @@
#define SAMPLER_UI_URI "http://lv2.incal.net/plugins/hydrumkit#ui"
#define KNOB_SIZE 32
struct plugin_ui {
LV2_Atom_Forge forge;
LV2_URID_Map *map;
......@@ -44,8 +47,20 @@ struct plugin_ui {
GtkWidget *load_dialog;
GtkWidget *button_box;
GtkWidget *box;
GtkWidget *humanizer_box;
GtkWidget *humanizer_button;
GtkWidget *humanizer_latency_max_knob;
GtkWidget *humanizer_latency_stddev_knob;
GtkWidget *humanizer_latency_laid_back_knob;
GtkWidget *humanizer_velocity_knob;
GtkWidget *window; /* For optional show interface. */
int humanizer_enabled;
float humanizer_latency_max_value;
float humanizer_latency_stddev_value;
float humanizer_latency_laid_back_value;
float humanizer_velocity_value;
char *filename;
uint8_t forge_buf[1024];
......@@ -95,6 +110,22 @@ static void on_load_dialog_response(GtkDialog *widget, gint response, void *hand
};
}
static void on_humanizer_button_click(GtkButton *widget, void *handle) {
struct plugin_ui *ui = (struct plugin_ui *)handle;
ui->humanizer_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
printf("humanizer %s\n", ui->humanizer_enabled ? "enabled" : "disabled");
}
static void on_humanizer_knob_change(KnobWidget *widget, void *handle) {
struct plugin_ui *ui = (struct plugin_ui *)handle;
printf("humanizer value change: latency=%g/%g/%g velocity=%g\n",
ui->humanizer_latency_max_value,
ui->humanizer_latency_stddev_value,
ui->humanizer_latency_laid_back_value,
ui->humanizer_velocity_value);
}
static int path_exists(const char *path) {
struct stat stbuf;
return (0 == stat(path, &stbuf)) ? 1 : 0;
......@@ -138,9 +169,14 @@ static LV2UI_Handle instantiate(const LV2UI_Descriptor *descriptor,
// Construct Gtk UI
ui->box = gtk_grid_new();
gtk_orientable_set_orientation(GTK_ORIENTABLE(ui->box), GTK_ORIENTATION_VERTICAL);
ui->label = gtk_label_new("No drumkit loaded.");
ui->button_box = gtk_grid_new();
ui->humanizer_box = gtk_grid_new();
// Create the file chooser (we use a custom GtkButton instead of the
// default GtkFileChooserButton because the latter takes way too
// much UI real estate).
filter = gtk_file_filter_new();
gtk_file_filter_add_pattern(filter, "drumkit.xml");
......@@ -170,14 +206,33 @@ static LV2UI_Handle instantiate(const LV2UI_Descriptor *descriptor,
}
free(homebuf);
}
// Create the humanizer controls.
ui->humanizer_button = gtk_check_button_new_with_label("Humanize");
ui->humanizer_latency_max_knob = knob_widget_new(&ui->humanizer_latency_max_value, KNOB_SIZE, 0);
ui->humanizer_latency_stddev_knob = knob_widget_new(&ui->humanizer_latency_stddev_value, KNOB_SIZE, 0);
ui->humanizer_latency_laid_back_knob = knob_widget_new(&ui->humanizer_latency_laid_back_value, KNOB_SIZE, 0);
ui->humanizer_velocity_knob = knob_widget_new(&ui->humanizer_velocity_value, KNOB_SIZE, 0);
g_signal_connect(ui->humanizer_button, "clicked", G_CALLBACK(on_humanizer_button_click), ui);
g_signal_connect(ui->humanizer_latency_max_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui);
g_signal_connect(ui->humanizer_latency_stddev_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui);
g_signal_connect(ui->humanizer_latency_laid_back_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui);
g_signal_connect(ui->humanizer_velocity_knob, "value-changed", G_CALLBACK(on_humanizer_knob_change), ui);
// Place all elements inside their containers.
gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4);
gtk_container_add(GTK_CONTAINER(ui->box), ui->button_box);
gtk_container_add(GTK_CONTAINER(ui->button_box), ui->label);
gtk_widget_set_hexpand(ui->label, TRUE);
gtk_widget_set_halign(ui->label, GTK_ALIGN_CENTER);
gtk_container_add(GTK_CONTAINER(ui->button_box), ui->load_button);
gtk_widget_set_hexpand(ui->load_button, FALSE);
gtk_container_add(GTK_CONTAINER(ui->box), ui->button_box);
gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_button);
gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_latency_max_knob);
gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_latency_stddev_knob);
gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_latency_laid_back_knob);
gtk_container_add(GTK_CONTAINER(ui->humanizer_box), ui->humanizer_velocity_knob);
gtk_container_add(GTK_CONTAINER(ui->box), ui->humanizer_box);
g_signal_connect(ui->load_dialog, "response", G_CALLBACK(on_load_dialog_response), ui);
......@@ -202,6 +257,13 @@ static void cleanup(LV2UI_Handle handle) {
gtk_widget_destroy(ui->load_dialog);
gtk_widget_destroy(ui->load_button);
gtk_widget_destroy(ui->button_box);
gtk_widget_destroy(ui->humanizer_button);
gtk_widget_destroy(ui->humanizer_latency_max_knob);
gtk_widget_destroy(ui->humanizer_latency_stddev_knob);
gtk_widget_destroy(ui->humanizer_latency_laid_back_knob);
gtk_widget_destroy(ui->humanizer_velocity_knob);
gtk_widget_destroy(ui->humanizer_box);
gtk_widget_destroy(ui->box);
free(ui);
}
......
/*
* gui/widgets/knob.c - knob
*
* Copyright (C) 2018 Alexandros Theodotou
* Copyright (C) 2010 Paul Davis
*
* This file is part of Zrythm
*
* Zrythm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zrythm 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
*/
/** \file
*/
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include "plugin/widgets/knob.h"
#include <gtk/gtk.h>
G_DEFINE_TYPE (KnobWidget, knob_widget, GTK_TYPE_DRAWING_AREA)
enum {
VALUE_CHANGED,
LAST_SIGNAL
};
static guint knob_signals[LAST_SIGNAL] = { 0 };
/**
* Draws the knob.
*/
static int
draw_cb (GtkWidget * widget, cairo_t * cr, void* data)
{
guint width, height;
GdkRGBA color;
GtkStyleContext *context;
KnobWidget * self = (KnobWidget *) data;
context = gtk_widget_get_style_context (widget);
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
gtk_render_background (context, cr, 0, 0, width, height);
cairo_pattern_t* shade_pattern;
const float scale = width;
const float pointer_thickness = 3.0 * (scale/80); //(if the knob is 80 pixels wide, we want a 3-pix line on it)
const float start_angle = ((180 - 65) * G_PI) / 180;
const float end_angle = ((360 + 65) * G_PI) / 180;
const float value_angle = start_angle + (*self->value * (end_angle - start_angle));
const float zero_angle = start_angle + (self->zero * (end_angle - start_angle));
float value_x = cos (value_angle);
float value_y = sin (value_angle);
float xc = 0.5 + width/ 2.0;
float yc = 0.5 + height/ 2.0;
cairo_translate (cr, xc, yc); //after this, everything is based on the center of the knob
//get the knob color from the theme
float center_radius = 0.48*scale;
float border_width = 0.8;
if (self->arc)
{
center_radius = scale*0.33;
float inner_progress_radius = scale*0.38;
float outer_progress_radius = scale*0.48;
float progress_width = (outer_progress_radius-inner_progress_radius);
float progress_radius = inner_progress_radius + progress_width/2.0;
/* dark surrounding arc background */
cairo_set_source_rgb (cr, 0.3, 0.3, 0.3 );
cairo_set_line_width (cr, progress_width);
cairo_arc (cr, 0, 0, progress_radius, start_angle, end_angle);
cairo_stroke (cr);
//look up the surrounding arc colors from the config
// TODO
//vary the arc color over the travel of the knob
float intensity = fabsf (*self->value - self->zero) / MAX(self->zero, (1.f - self->zero));
const float intensity_inv = 1.0 - intensity;
float r = intensity_inv * self->end_color.red +
intensity * self->start_color.red;
float g = intensity_inv * self->end_color.green +
intensity * self->start_color.green;
float b = intensity_inv * self->end_color.blue +
intensity * self->start_color.blue;
//draw the arc
cairo_set_source_rgb (cr, r,g,b);
cairo_set_line_width (cr, progress_width);
if (zero_angle > value_angle)
{
cairo_arc (cr, 0, 0, progress_radius, value_angle, zero_angle);
}
else
{
cairo_arc (cr, 0, 0, progress_radius, zero_angle, value_angle);
}
cairo_stroke (cr);
//shade the arc
if (!self->flat)
{
shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the pattern from our centerpoint
cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.15);
cairo_pattern_add_color_stop_rgba (shade_pattern, 0.5, 1,1,1, 0.0);
cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1,1,1, 0.0);
cairo_set_source (cr, shade_pattern);
cairo_arc (cr, 0, 0, outer_progress_radius-1, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_pattern_destroy (shade_pattern);
}
#if 0 //black border
const float start_angle_x = cos (start_angle);
const float start_angle_y = sin (start_angle);
const float end_angle_x = cos (end_angle);
const float end_angle_y = sin (end_angle);
cairo_set_source_rgb (cr, 0, 0, 0 );
cairo_set_line_width (cr, border_width);
cairo_move_to (cr, (outer_progress_radius * start_angle_x), (outer_progress_radius * start_angle_y));
cairo_line_to (cr, (inner_progress_radius * start_angle_x), (inner_progress_radius * start_angle_y));
cairo_stroke (cr);
cairo_move_to (cr, (outer_progress_radius * end_angle_x), (outer_progress_radius * end_angle_y));
cairo_line_to (cr, (inner_progress_radius * end_angle_x), (inner_progress_radius * end_angle_y));
cairo_stroke (cr);
cairo_arc (cr, 0, 0, outer_progress_radius, start_angle, end_angle);
cairo_stroke (cr);
#endif
}
if (!self->flat)
{
//knob shadow
cairo_save(cr);
cairo_translate(cr, pointer_thickness+1, pointer_thickness+1 );
cairo_set_source_rgba (cr, 0, 0, 0, 0.1 );
cairo_arc (cr, 0, 0, center_radius-1, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_restore(cr);
//inner circle
#define KNOB_COLOR 0, 90, 0, 0
cairo_set_source_rgba(cr, KNOB_COLOR); /* knob color */
cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
cairo_fill (cr);
//gradient
if (self->bevel)
{
//knob gradient
shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the gradient from our centerpoint
cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2);
cairo_pattern_add_color_stop_rgba (shade_pattern, 0.2, 1,1,1, 0.2);
cairo_pattern_add_color_stop_rgba (shade_pattern, 0.8, 0,0,0, 0.2);
cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.2);
cairo_set_source (cr, shade_pattern);
cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_pattern_destroy (shade_pattern);
//flat top over beveled edge
cairo_set_source_rgba (cr, 90, 0, 0, 0.5 );
cairo_arc (cr, 0, 0, center_radius-pointer_thickness, 0, 2.0*G_PI);
cairo_fill (cr);
}
else
{
//radial gradient
shade_pattern = cairo_pattern_create_radial ( -center_radius, -center_radius, 1, -center_radius, -center_radius, center_radius*2.5 ); //note we have to offset the gradient from our centerpoint
cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2);
cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.3);
cairo_set_source (cr, shade_pattern);
cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
cairo_fill (cr);
cairo_pattern_destroy (shade_pattern);
}
}
else
{
/* color inner circle */
cairo_set_source_rgba(cr, 70, 70, 70, 0.2);
cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
cairo_fill (cr);
}
//black knob border
cairo_set_line_width (cr, border_width);
cairo_set_source_rgba (cr, 0,0,0, 1 );
cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
cairo_stroke (cr);
//line shadow
if (!self->flat) {
cairo_save(cr);
cairo_translate(cr, 1, 1 );
cairo_set_source_rgba (cr, 0,0,0,0.3 );
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width (cr, pointer_thickness);
cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y));
cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
cairo_stroke (cr);
cairo_restore(cr);
}
//line
cairo_set_source_rgba (cr, 1,1,1, 1 );
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_width (cr, pointer_thickness);
cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y));
cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
cairo_stroke (cr);
//highlight if grabbed or if mouse is hovering over me
if (self->hover)
{
cairo_set_source_rgba (cr, 1,1,1, 0.12 );
cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
cairo_fill (cr);
}
cairo_identity_matrix(cr);
}
static void
on_crossing (GtkWidget * widget, GdkEvent *event, void * data)
{
KnobWidget * self = (KnobWidget *) data;
switch (gdk_event_get_event_type (event))
{
case GDK_ENTER_NOTIFY:
self->hover = 1;
break;
case GDK_LEAVE_NOTIFY:
if (!gtk_gesture_drag_get_offset (self->drag,
NULL,
NULL))
self->hover = 0;
break;
}
gtk_widget_queue_draw(widget);
}
static double clamp
(double x, double upper, double lower)
{
return MIN(upper, MAX(x, lower));
}
static void
drag_update (GtkGestureDrag * gesture,
gdouble offset_x,
gdouble offset_y,
gpointer user_data)
{
KnobWidget * self = (KnobWidget *) user_data;
offset_y = - offset_y;
int use_y = abs(offset_y - self->last_y) > abs(offset_x - self->last_x);
*self->value = clamp (*self->value + 0.004 * (use_y ? offset_y - self->last_y : offset_x - self->last_x),
1.0f, 0.0f);
self->last_x = offset_x;
self->last_y = offset_y;
gtk_widget_queue_draw ((GtkWidget *)user_data);
}
static void
drag_end (GtkGestureDrag *gesture,
gdouble offset_x,
gdouble offset_y,
gpointer user_data)
{
KnobWidget * self = (KnobWidget *) user_data;
self->last_x = 0;
self->last_y = 0;
g_signal_emit(self, knob_signals[VALUE_CHANGED], 0);
}
/**
* Creates a knob widget with the given options and binds it to the given value.
*/
KnobWidget *
knob_widget_new (float * value,
int size,
float zero)
{
KnobWidget * self = g_object_new (KNOB_WIDGET_TYPE, NULL);
self->value = value;
self->size = size; /* default 30 */
self->hover = 0;
self->zero = zero; /* default 0.05f */
self->value = value;
self->arc = 1;
self->bevel = 1;
self->flat = 1;
self->start_color.red = 0.8;
self->start_color.green = 0.8;
self->start_color.blue = 0.8;
self->end_color.red = 0.7;
self->end_color.red = 0.7;
self->end_color.red = 0.7;
self->last_x = 0;
self->last_y = 0;
/* set size */
gtk_widget_set_size_request (GTK_WIDGET (self), size, size);
/* connect signals */
g_signal_connect (G_OBJECT (self), "draw",
G_CALLBACK (draw_cb), self);
g_signal_connect (G_OBJECT (self), "enter-notify-event",
G_CALLBACK (on_crossing), self);
g_signal_connect (G_OBJECT(self), "leave-notify-event",
G_CALLBACK (on_crossing), self);
g_signal_connect (G_OBJECT(self->drag), "drag-update",
G_CALLBACK (drag_update), self);
g_signal_connect (G_OBJECT(self->drag), "drag-end",
G_CALLBACK (drag_end), self);
return self;
}
static void
knob_widget_init (KnobWidget * self)
{
/* make it able to notify */
gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
int crossing_mask = GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK;
gtk_widget_add_events (GTK_WIDGET (self), crossing_mask);
self->drag = GTK_GESTURE_DRAG (gtk_gesture_drag_new (GTK_WIDGET (&self->parent_instance)));
}
static void
knob_widget_class_init (KnobWidgetClass * klass)
{
knob_signals[VALUE_CHANGED] =
g_signal_new ("value-changed",
G_TYPE_FROM_CLASS(G_OBJECT_CLASS(klass)),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(KnobWidgetClass, value_changed),
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
}
/*
* gui/widgets/knob.h - knob
*
* Copyright (C) 2018 Alexandros Theodotou
*
* This file is part of Zrythm
*
* Zrythm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zrythm 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
*/
/** \file
*/
#ifndef __GUI_WIDGETS_KNOB_H__
#define __GUI_WIDGETS_KNOB_H__
#include <gtk/gtk.h>
#define KNOB_WIDGET_TYPE (knob_widget_get_type ())
#define KNOB_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), KNOB_WIDGET_TYPE, Knob))
#define KNOB_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), KNOB_WIDGET, KnobWidgetClass))
#define IS_KNOB_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), KNOB_WIDGET_TYPE))
#define IS_KNOB_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), KNOB_WIDGET_TYPE))
#define KNOB_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), KNOB_WIDGET_TYPE, KnobWidgetClass))
typedef struct KnobWidget
{
GtkDrawingArea parent_instance;
float * value; ///< value to bind to.
///< this value will get updated as the knob turns
int size; ///< size in px
int hover; ///< used to detect if hovering or not
float zero; ///< zero point 0.0-1.0 */
int arc; ///< draw arc around the knob or not
int bevel; ///< bevel
int flat; ///< flat or 3D
GdkColor start_color; ///< color away from zero point
GdkColor end_color; ///< color close to zero point
GtkGestureDrag *drag; ///< used for drag gesture
double last_x; ///< used in gesture drag
double last_y; ///< used in gesture drag
} KnobWidget;
typedef struct KnobWidgetClass
{
GtkDrawingAreaClass parent_class;
void (* value_changed)(KnobWidget *);
} KnobWidgetClass;
/**
* Creates a knob widget with the given options and binds it to the given value.
*/
KnobWidget *
knob_widget_new (float * value,
int size,
float zero);
#endif
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment