zynjacku/midi_cc_map.c

716 lines
19 KiB
C

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*****************************************************************************
*
* This file is part of zynjacku
*
* Copyright (C) 2008,2009 Nedko Arnaudov <nedko@arnaudov.name>
*
* This program 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; version 2 of the License
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
*****************************************************************************/
#if !defined(_ISOC99_SOURCE)
#define _ISOC99_SOURCE /* roundf from math.h */
#endif
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <glib-object.h>
#include "midi_cc_map.h"
#include "list.h"
#include "midi_cc_map_internal.h"
#include "plugin_internal.h"
//#define LOG_LEVEL LOG_LEVEL_DEBUG
#include "log.h"
#define ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_CREATED 0
#define ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_REMOVED 1
#define ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_CC_CHANGED 2
#define ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_VALUE_CHANGED 3
#define ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_NO_ASSIGNED 4
#define ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_VALUE_CHANGED 5
#define ZYNJACKU_MIDI_CC_MAP_SIGNALS_COUNT 6
struct point
{
struct list_head siblings;
guint cc_value;
gfloat parameter_value;
};
struct point_and_func
{
guint next_cc_value;
/* slope and y-intercept of linear function that matches this point and next one */
/* these are not valid if next_cc_value is G_MAXUINT */
gfloat slope;
gfloat y_intercept;
};
struct zynjacku_midi_cc_map
{
gboolean dispose_has_run;
gint cc_no;
gint cc_value;
gboolean pending_assign;
gboolean pending_value_change;
GObject * plugin_obj_ptr;
struct list_head points;
gboolean points_need_ui_update;
gboolean points_need_rt_update;
struct point_and_func points_rt[MIDICC_VALUE_COUNT];
struct point_and_func points_ui[MIDICC_VALUE_COUNT];
};
#define ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ZYNJACKU_MIDI_CC_MAP_TYPE, struct zynjacku_midi_cc_map))
static guint g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNALS_COUNT];
static void
zynjacku_midi_cc_map_dispose(GObject * obj)
{
struct zynjacku_midi_cc_map * map_ptr;
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(obj);
LOG_DEBUG("zynjacku_midi_cc_map_dispose() called.");
if (map_ptr->dispose_has_run)
{
/* If dispose did already run, return. */
LOG_DEBUG("zynjacku_midi_cc_map_dispose() already run!");
return;
}
/* Make sure dispose does not run twice. */
map_ptr->dispose_has_run = TRUE;
/*
* In dispose, you are supposed to free all types referenced from this
* object which might themselves hold a reference to self. Generally,
* the most simple solution is to unref all members on which you own a
* reference.
*/
/* Chain up to the parent class */
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->dispose(obj);
}
static void
zynjacku_midi_cc_map_finalize(GObject * obj)
{
// struct zynjacku_midi_cc_map * self = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(obj);
LOG_DEBUG("zynjacku_midi_cc_map_finalize() called.");
/*
* Here, complete object destruction.
* You might not need to do much...
*/
//g_free(self->private);
/* Chain up to the parent class */
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
}
static
void
zynjacku_midi_cc_map_class_init(
gpointer class_ptr,
gpointer class_data_ptr)
{
LOG_DEBUG("zynjacku_midi_cc_map_class() called.");
G_OBJECT_CLASS(class_ptr)->dispose = zynjacku_midi_cc_map_dispose;
G_OBJECT_CLASS(class_ptr)->finalize = zynjacku_midi_cc_map_finalize;
g_type_class_add_private(G_OBJECT_CLASS(class_ptr), sizeof(struct zynjacku_midi_cc_map));
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_CREATED] =
g_signal_new(
"point-created", /* signal_name */
ZYNJACKU_MIDI_CC_MAP_TYPE, /* itype */
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION, /* signal_flags */
0, /* class_offset */
NULL, /* accumulator */
NULL, /* accu_data */
NULL, /* c_marshaller */
G_TYPE_NONE, /* return type */
2, /* n_params */
G_TYPE_UINT, /* MIDI CC value 0..127 */
G_TYPE_FLOAT); /* uri of plugin being scanned */
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_REMOVED] =
g_signal_new(
"point-removed", /* signal_name */
ZYNJACKU_MIDI_CC_MAP_TYPE, /* itype */
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION, /* signal_flags */
0, /* class_offset */
NULL, /* accumulator */
NULL, /* accu_data */
NULL, /* c_marshaller */
G_TYPE_NONE, /* return type */
1, /* n_params */
G_TYPE_UINT); /* MIDI CC value 0..127 */
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_CC_CHANGED] =
g_signal_new(
"point-cc-changed", /* signal_name */
ZYNJACKU_MIDI_CC_MAP_TYPE, /* itype */
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION, /* signal_flags */
0, /* class_offset */
NULL, /* accumulator */
NULL, /* accu_data */
NULL, /* c_marshaller */
G_TYPE_NONE, /* return type */
2, /* n_params */
G_TYPE_UINT, /* old MIDI CC value 0..127 */
G_TYPE_UINT); /* new MIDI CC value 0..127 */
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_VALUE_CHANGED] =
g_signal_new(
"point-value-changed", /* signal_name */
ZYNJACKU_MIDI_CC_MAP_TYPE, /* itype */
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION, /* signal_flags */
0, /* class_offset */
NULL, /* accumulator */
NULL, /* accu_data */
NULL, /* c_marshaller */
G_TYPE_NONE, /* return type */
2, /* n_params */
G_TYPE_UINT, /* MIDI CC value 0..127 */
G_TYPE_FLOAT); /* uri of plugin being scanned */
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_NO_ASSIGNED] =
g_signal_new(
"cc-no-assigned", /* signal_name */
ZYNJACKU_MIDI_CC_MAP_TYPE, /* itype */
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION, /* signal_flags */
0, /* class_offset */
NULL, /* accumulator */
NULL, /* accu_data */
NULL, /* c_marshaller */
G_TYPE_NONE, /* return type */
1, /* n_params */
G_TYPE_UINT); /* MIDI CC No, 0..127 */
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_VALUE_CHANGED] =
g_signal_new(
"cc-value-changed", /* signal_name */
ZYNJACKU_MIDI_CC_MAP_TYPE, /* itype */
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION, /* signal_flags */
0, /* class_offset */
NULL, /* accumulator */
NULL, /* accu_data */
NULL, /* c_marshaller */
G_TYPE_NONE, /* return type */
1, /* n_params */
G_TYPE_UINT); /* MIDI CC value 0..127 */
}
static
void
zynjacku_midi_cc_map_init(
GTypeInstance * instance,
gpointer g_class)
{
struct zynjacku_midi_cc_map * map_ptr;
LOG_DEBUG("zynjacku_midi_cc_map_init() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(instance);
INIT_LIST_HEAD(&map_ptr->points);
map_ptr->cc_no = G_MAXUINT;
map_ptr->pending_assign = FALSE;
map_ptr->pending_value_change = FALSE;
map_ptr->points_rt[0].next_cc_value = G_MAXUINT;
map_ptr->points_need_ui_update = FALSE;
map_ptr->points_need_rt_update = FALSE;
}
GType zynjacku_midiccmap_get_type()
{
static GType type = 0;
if (type == 0)
{
type = g_type_register_static_simple(
G_TYPE_OBJECT,
"zynjacku_midi_cc_map_type",
sizeof(ZynjackuMidiCcMapClass),
zynjacku_midi_cc_map_class_init,
sizeof(ZynjackuMidiCcMap),
zynjacku_midi_cc_map_init,
0);
}
return type;
}
void
zynjacku_midiccmap_point_created(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value,
gfloat parameter_value)
{
struct zynjacku_midi_cc_map * map_ptr;
LOG_DEBUG("zynjacku_midiccmap_point_created() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_CREATED],
0,
cc_value,
parameter_value);
}
void
zynjacku_midiccmap_point_removed(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value)
{
LOG_DEBUG("zynjacku_midiccmap_point_removed() called.");
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_REMOVED],
0,
cc_value);
}
void
zynjacku_midiccmap_point_cc_changed(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value_old,
guint cc_value_new)
{
LOG_DEBUG("zynjacku_midiccmap_point_cc_value_changed() called.");
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_CC_CHANGED],
0,
cc_value_old,
cc_value_new);
}
void
zynjacku_midiccmap_point_value_changed(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value,
gfloat parameter_value)
{
struct zynjacku_midi_cc_map * map_ptr;
LOG_DEBUG("zynjacku_midiccmap_point_value_changed() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_POINT_VALUE_CHANGED],
0,
cc_value,
parameter_value);
}
void
zynjacku_midiccmap_get_points(
ZynjackuMidiCcMap * map_obj_ptr)
{
struct zynjacku_midi_cc_map * map_ptr;
struct list_head * node_ptr;
struct point * point_ptr;
LOG_DEBUG("zynjacku_midiccmap_get_points() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
list_for_each(node_ptr, &map_ptr->points)
{
point_ptr = list_entry(node_ptr, struct point, siblings);
zynjacku_midiccmap_point_created(map_obj_ptr, point_ptr->cc_value, point_ptr->parameter_value);
}
}
void
zynjacku_midiccmap_point_create(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value,
gfloat parameter_value)
{
struct zynjacku_midi_cc_map * map_ptr;
struct point * point_ptr;
LOG_DEBUG("zynjacku_midiccmap_point_create(%u, %f) called.", cc_value, parameter_value);
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
point_ptr = malloc(sizeof(struct point));
point_ptr->cc_value = cc_value;
point_ptr->parameter_value = parameter_value;
list_add_tail(&point_ptr->siblings, &map_ptr->points);
map_ptr->points_need_ui_update = TRUE;
zynjacku_midiccmap_point_created(map_obj_ptr, cc_value, parameter_value);
}
static
struct point *
zynjacku_midiccmap_point_find(
struct zynjacku_midi_cc_map * map_ptr,
guint cc_value)
{
struct list_head * node_ptr;
struct point * point_ptr;
list_for_each(node_ptr, &map_ptr->points)
{
point_ptr = list_entry(node_ptr, struct point, siblings);
if (point_ptr->cc_value == cc_value)
{
return point_ptr;
}
}
return NULL;
}
void
zynjacku_midiccmap_point_remove(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value)
{
struct zynjacku_midi_cc_map * map_ptr;
struct point * point_ptr;
LOG_DEBUG("zynjacku_midiccmap_point_remove() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
point_ptr = zynjacku_midiccmap_point_find(map_ptr, cc_value);
if (point_ptr == NULL)
{
LOG_ERROR("cannot find point with cc value of %u", cc_value);
return;
}
map_ptr->points_need_ui_update = TRUE;
zynjacku_midiccmap_point_removed(map_obj_ptr, cc_value);
}
void
zynjacku_midiccmap_point_cc_value_change(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value_old,
guint cc_value_new)
{
struct zynjacku_midi_cc_map * map_ptr;
struct point * point_ptr;
LOG_DEBUG("zynjacku_midiccmap_point_cc_value_change() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
point_ptr = zynjacku_midiccmap_point_find(map_ptr, cc_value_old);
if (point_ptr == NULL)
{
LOG_ERROR("cannot find point with cc value of %u", cc_value_old);
return;
}
point_ptr->cc_value = cc_value_new;
map_ptr->points_need_ui_update = TRUE;
zynjacku_midiccmap_point_cc_changed(map_obj_ptr, cc_value_old, cc_value_new);
}
void
zynjacku_midiccmap_point_parameter_value_change(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_value,
gfloat parameter_value)
{
struct zynjacku_midi_cc_map * map_ptr;
struct point * point_ptr;
LOG_DEBUG("zynjacku_midiccmap_point_parameter_value_change() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
point_ptr = zynjacku_midiccmap_point_find(map_ptr, cc_value);
if (point_ptr == NULL)
{
LOG_ERROR("cannot find point with cc value of %u", cc_value);
return;
}
point_ptr->parameter_value = parameter_value;
map_ptr->points_need_ui_update = TRUE;
zynjacku_midiccmap_point_value_changed(map_obj_ptr, cc_value, parameter_value);
}
void
zynjacku_midiccmap_midi_cc_rt(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_no,
guint cc_value)
{
struct zynjacku_midi_cc_map * map_ptr;
LOG_DEBUG("zynjacku_midiccmap_midi_cc_rt_cc(%u, %u) called.", cc_no, cc_value);
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
assert(map_ptr != NULL);
if (map_ptr->cc_no == G_MAXUINT)
{
map_ptr->pending_assign = TRUE;
}
map_ptr->cc_no = cc_no;
map_ptr->cc_value = cc_value;
map_ptr->pending_value_change = TRUE;
if (map_ptr->points_need_rt_update)
{
LOG_DEBUG("updating points_rt array...");
memcpy(map_ptr->points_rt, map_ptr->points_ui, sizeof(map_ptr->points_rt));
map_ptr->points_need_rt_update = FALSE;
}
}
void
zynjacku_midiccmap_ui_run(
ZynjackuMidiCcMap * map_obj_ptr)
{
struct zynjacku_midi_cc_map * map_ptr;
struct list_head * node_ptr;
struct point * point_ptr;
struct point * points_map[MIDICC_VALUE_COUNT];
int index;
int prev;
gfloat x1, x2, y1, y2;
//LOG_DEBUG("zynjacku_midiccmap_ui_run() called.");
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
if (map_ptr->pending_assign)
{
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_NO_ASSIGNED],
0,
map_ptr->cc_no);
map_ptr->pending_assign = FALSE;
}
if (map_ptr->pending_value_change)
{
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_VALUE_CHANGED],
0,
map_ptr->cc_value);
map_ptr->pending_value_change = FALSE;
}
if (!map_ptr->points_need_ui_update)
{
return;
}
/* regenerate points_ui array from points list */
LOG_DEBUG("regenerating points_ui array of map %p", map_ptr);
map_ptr->points_need_ui_update = FALSE;
memset(points_map, 0, sizeof(points_map));
list_for_each(node_ptr, &map_ptr->points)
{
point_ptr = list_entry(node_ptr, struct point, siblings);
assert(point_ptr->cc_value < MIDICC_VALUE_COUNT);
points_map[point_ptr->cc_value] = point_ptr;
}
if (points_map[0] == NULL || points_map[MIDICC_VALUE_COUNT - 1] == NULL)
{
/* if we dont have the extreme points then map is not complete
and thus it cannot be used for mapping */
LOG_DEBUG("not complete map");
return;
}
prev = -1;
for (index = 0; index < MIDICC_VALUE_COUNT; index++)
{
map_ptr->points_ui[index].next_cc_value = G_MAXUINT;
if (points_map[index] == NULL)
{
continue;
}
if (prev == -1)
{
prev = index;
continue;
}
map_ptr->points_ui[prev].next_cc_value = index;
x1 = (gfloat)prev / (gfloat)(MIDICC_VALUE_COUNT - 1);
x2 = (gfloat)index / (gfloat)(MIDICC_VALUE_COUNT - 1);
y1 = points_map[prev]->parameter_value;
y2 = points_map[index]->parameter_value;
map_ptr->points_ui[prev].slope = (y2 - y1) / (x2 - x1);
map_ptr->points_ui[prev].y_intercept = (y1 * x2 - x1 * y2) / (x2 - x1);
LOG_DEBUG("%u -> %u has slope of %f and y-intercept of %f", prev, index, map_ptr->points_ui[prev].slope, map_ptr->points_ui[prev].y_intercept);
prev = index;
}
/* schedule update of points_rt array on next zynjacku_midiccmap_midi_cc_rt() call */
/* this function and zynjacku_midiccmap_midi_cc_rt() are called with same lock obtained */
/* however zynjacku_midiccmap_map_cc_rt() is called without lock obtained */
map_ptr->points_need_rt_update = TRUE;
}
gboolean
zynjacku_midiccmap_cc_no_assign(
ZynjackuMidiCcMap * map_obj_ptr,
guint cc_no)
{
struct zynjacku_midi_cc_map * map_ptr;
LOG_DEBUG("zynjacku_midiccmap_cc_no_assign(%u) called.", cc_no);
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
if (map_ptr->plugin_obj_ptr == NULL)
{
//LOG_DEBUG("no plugin associated");
if (map_ptr->cc_no != cc_no)
{
map_ptr->cc_no = cc_no;
g_signal_emit(
map_obj_ptr,
g_zynjacku_midi_cc_map_signals[ZYNJACKU_MIDI_CC_MAP_SIGNAL_CC_NO_ASSIGNED],
0,
cc_no);
}
return TRUE;
}
else
{
//LOG_DEBUG("plugin associated");
}
return zynjacku_plugin_midi_cc_map_cc_no_assign(map_ptr->plugin_obj_ptr, G_OBJECT(map_obj_ptr), cc_no);
}
gint
zynjacku_midiccmap_get_cc_no(
ZynjackuMidiCcMap * map_obj_ptr)
{
struct zynjacku_midi_cc_map * map_ptr;
map_ptr = ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
return map_ptr->cc_no;
}
/* we meed this function because engine has to call zynjacku_midiccmap_map_cc_rt
and ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE is not good to be used in realtime thread */
void *
zynjacku_midiccmap_get_internal_ptr(
ZynjackuMidiCcMap * map_obj_ptr)
{
return ZYNJACKU_MIDI_CC_MAP_GET_PRIVATE(map_obj_ptr);
}
#define map_ptr ((struct zynjacku_midi_cc_map *)internal_ptr)
gfloat
zynjacku_midiccmap_map_cc_rt(
void * internal_ptr,
gfloat cc_value)
{
int index;
LOG_DEBUG("zynjacku_midiccmap_map_cc_rt(%p, %f)", map_ptr, cc_value);
if (map_ptr->points_rt[0].next_cc_value == G_MAXUINT)
{
/* no valid map */
LOG_DEBUG("no valid map");
return 0.0;
}
index = roundf(cc_value * (MIDICC_VALUE_COUNT - 1));
assert(index < MIDICC_VALUE_COUNT);
while (map_ptr->points_rt[index].next_cc_value == G_MAXUINT)
{
index--;
assert(index >= 0);
}
LOG_DEBUG("%u -> %u has slope of %f and y-intercept of %f", index, map_ptr->points_rt[index].next_cc_value, map_ptr->points_rt[index].slope, map_ptr->points_rt[index].y_intercept);
return map_ptr->points_rt[index].slope * cc_value + map_ptr->points_rt[index].y_intercept;
}
#undef map_ptr