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.

280 lines
9.9 KiB

/*
Calf Box, an open source musical instrument.
Copyright (C) 2010-2011 Krzysztof Foltman
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, 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "errors.h"
#include "master.h"
#include "pattern.h"
#include "rt.h"
#include "seq.h"
#include "track.h"
#include "song.h"
#include <assert.h>
#include <malloc.h>
CBOX_CLASS_DEFINITION_ROOT(cbox_track)
CBOX_CLASS_DEFINITION_ROOT(cbox_track_item)
static gboolean cbox_track_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error);
static gboolean cbox_track_item_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error);
void cbox_track_item_destroyfunc(struct cbox_objhdr *hdr)
{
struct cbox_track_item *item = CBOX_H2O(hdr);
item->owner->items = g_list_remove(item->owner->items, item);
free(item);
}
struct cbox_track *cbox_track_new(struct cbox_document *document)
{
struct cbox_track *p = malloc(sizeof(struct cbox_track));
CBOX_OBJECT_HEADER_INIT(p, cbox_track, document);
p->name = g_strdup("Unnamed");
p->items = NULL;
p->pb = NULL;
p->owner = NULL;
p->external_output_set = FALSE;
p->generation = 0;
p->mute = FALSE;
cbox_command_target_init(&p->cmd_target, cbox_track_process_cmd, p);
CBOX_OBJECT_REGISTER(p);
return p;
}
#define CBTI(it) ((struct cbox_track_item *)(it)->data)
void cbox_track_add_item_to_list(struct cbox_track *track, struct cbox_track_item *item)
{
GList *it = track->items;
while(it != NULL && CBTI(it)->time < item->time)
it = g_list_next(it);
// all items earlier than the new one -> append
if (it == NULL)
{
track->items = g_list_append(track->items, item);
cbox_track_set_dirty(track);
return;
}
// Here, I don't really care about overlaps - it's more important to preserve
// all clips as sent by the caller.
track->items = g_list_insert_before(track->items, it, item);
cbox_track_set_dirty(track);
}
struct cbox_track_item *cbox_track_add_item(struct cbox_track *track, uint32_t time, struct cbox_midi_pattern *pattern, uint32_t offset, uint32_t length)
{
struct cbox_track_item *item = malloc(sizeof(struct cbox_track_item));
CBOX_OBJECT_HEADER_INIT(item, cbox_track_item, CBOX_GET_DOCUMENT(track));
item->owner = track;
item->time = time;
item->pattern = pattern;
item->offset = offset;
item->length = length;
cbox_command_target_init(&item->cmd_target, cbox_track_item_process_cmd, item);
cbox_track_add_item_to_list(track, item);
CBOX_OBJECT_REGISTER(item);
return item;
}
void cbox_track_clear_clips(struct cbox_track *track)
{
while(track->items) {
cbox_object_destroy(track->items->data);
}
cbox_track_set_dirty(track);
}
void cbox_track_set_dirty(struct cbox_track *track)
{
++track->generation;
}
void cbox_track_destroyfunc(struct cbox_objhdr *objhdr)
{
struct cbox_track *track = CBOX_H2O(objhdr);
if (track->owner)
cbox_song_remove_track(track->owner, track);
// XXXKF I'm not sure if I want the lifecycle of track playback objects to be managed by the track itself
if (track->pb && track->pb->ref_count == 1)
cbox_track_playback_destroy(track->pb);
// The items will unlink themselves from the list in destructor
while(track->items)
cbox_object_destroy(track->items->data);
g_free((gchar *)track->name);
free(track);
}
gboolean cbox_track_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
struct cbox_track *track = ct->user_data;
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
GList *it = track->items;
while(it != NULL)
{
struct cbox_track_item *trki = it->data;
if (!cbox_execute_on(fb, NULL, "/clip", "iiioo", error, trki->time, trki->offset, trki->length, trki->pattern, trki))
return FALSE;
it = g_list_next(it);
}
return cbox_execute_on(fb, NULL, "/mute", "i", error, track->mute) &&
cbox_execute_on(fb, NULL, "/name", "s", error, track->name) &&
(track->external_output_set ? cbox_uuid_report_as(&track->external_output, "/external_output", fb, error) : TRUE) &&
CBOX_OBJECT_DEFAULT_STATUS(track, fb, error);
}
else if (!strcmp(cmd->command, "/clear_clips") && !strcmp(cmd->arg_types, ""))
{
cbox_track_clear_clips(track);
return TRUE;
}
else if (!strcmp(cmd->command, "/add_clip") && !strcmp(cmd->arg_types, "iiis"))
{
int pos = CBOX_ARG_I(cmd, 0);
int offset = CBOX_ARG_I(cmd, 1);
int length = CBOX_ARG_I(cmd, 2);
if (pos < 0)
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Invalid pattern position %d (cannot be negative)", pos);
return FALSE;
}
if (offset < 0)
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Invalid pattern offset %d (cannot be negative)", offset);
return FALSE;
}
if (length <= 0)
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Invalid pattern length %d (must be positive)", length);
return FALSE;
}
struct cbox_objhdr *pattern = CBOX_ARG_O(cmd, 3, track, cbox_midi_pattern, error);
if (!pattern)
return FALSE;
struct cbox_midi_pattern *mp = CBOX_H2O(pattern);
struct cbox_track_item *trki = cbox_track_add_item(track, pos, mp, offset, length);
if (fb)
return cbox_execute_on(fb, NULL, "/uuid", "o", error, trki);
cbox_track_set_dirty(track);
return TRUE;
}
else if (!strcmp(cmd->command, "/name") && !strcmp(cmd->arg_types, "s"))
{
char *old_name = track->name;
track->name = g_strdup(CBOX_ARG_S(cmd, 0));
g_free(old_name);
cbox_track_set_dirty(track);
return TRUE;
}
else if (!strcmp(cmd->command, "/external_output") && !strcmp(cmd->arg_types, "s"))
{
if (*CBOX_ARG_S(cmd, 0))
{
if (cbox_uuid_fromstring(&track->external_output, CBOX_ARG_S(cmd, 0), error)) {
track->external_output_set = TRUE;
cbox_track_set_dirty(track);
}
}
else {
track->external_output_set = FALSE;
cbox_track_set_dirty(track);
}
return TRUE;
}
else if (!strcmp(cmd->command, "/mute") && !strcmp(cmd->arg_types, "i"))
{
if (CBOX_ARG_I(cmd, 0) == (int)track->mute) // no-op
return TRUE;
track->mute = CBOX_ARG_I(cmd, 0) != 0;
cbox_track_set_dirty(track);
return TRUE;
}
else
return cbox_object_default_process_cmd(ct, fb, cmd, error);
}
gboolean cbox_track_item_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
struct cbox_track_item *trki = ct->user_data;
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
return cbox_execute_on(fb, NULL, "/pos", "i", error, trki->time) &&
cbox_execute_on(fb, NULL, "/offset", "i", error, trki->offset) &&
cbox_execute_on(fb, NULL, "/length", "i", error, trki->length) &&
cbox_execute_on(fb, NULL, "/pattern", "o", error, trki->pattern) &&
CBOX_OBJECT_DEFAULT_STATUS(trki, fb, error);
}
if (!strcmp(cmd->command, "/delete") && !strcmp(cmd->arg_types, ""))
{
cbox_track_set_dirty(trki->owner);
cbox_object_destroy(CBOX_O2H(trki));
return TRUE;
}
if (!strcmp(cmd->command, "/pattern") && !strcmp(cmd->arg_types, "s"))
{
struct cbox_objhdr *pattern = CBOX_ARG_O(cmd, 0, trki->owner, cbox_midi_pattern, error);
if (!pattern)
return FALSE;
if (trki->pattern == CBOX_H2O(pattern)) // no-op
return TRUE;
trki->pattern = CBOX_H2O(pattern);
cbox_track_item_set_dirty(trki);
return TRUE;
}
if (!strcmp(cmd->command, "/length") && !strcmp(cmd->arg_types, "i"))
{
if (CBOX_ARG_I(cmd, 0) == (int)trki->length) // no-op
return TRUE;
trki->length = CBOX_ARG_I(cmd, 0);
cbox_track_item_set_dirty(trki);
return TRUE;
}
if (!strcmp(cmd->command, "/pos") && !strcmp(cmd->arg_types, "i"))
{
if (CBOX_ARG_I(cmd, 0) == (int)trki->time) // no-op
return TRUE;
trki->owner->items = g_list_remove(trki->owner->items, trki);
trki->time = CBOX_ARG_I(cmd, 0);
cbox_track_add_item_to_list(trki->owner, trki);
cbox_track_item_set_dirty(trki);
return TRUE;
}
if (!strcmp(cmd->command, "/offset") && !strcmp(cmd->arg_types, "i"))
{
if (CBOX_ARG_I(cmd, 0) == (int)trki->offset) // no-op
return TRUE;
cbox_track_item_set_dirty(trki);
trki->offset = CBOX_ARG_I(cmd, 0);
return TRUE;
}
return cbox_object_default_process_cmd(ct, fb, cmd, error);
}
extern void cbox_track_item_set_dirty(struct cbox_track_item *track_item)
{
cbox_track_set_dirty(track_item->owner);
}