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.

358 lines
12 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 "app.h"
#include "engine.h"
#include "errors.h"
#include "song.h"
#include "track.h"
#include <assert.h>
#include <stdlib.h>
CBOX_CLASS_DEFINITION_ROOT(cbox_song)
/////////////////////////////////////////////////////////////////////////////////////////////////
void cbox_master_track_item_destroy(struct cbox_master_track_item *item)
{
free(item);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
gboolean cbox_song_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
struct cbox_song *song = ct->user_data;
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
for(GList *p = song->tracks; p; p = g_list_next(p))
{
struct cbox_track *trk = p->data;
if (!cbox_execute_on(fb, NULL, "/track", "sio", error, trk->name, g_list_length(trk->items), trk))
return FALSE;
}
for(GList *p = song->patterns; p; p = g_list_next(p))
{
struct cbox_midi_pattern *pat = p->data;
if (!cbox_execute_on(fb, NULL, "/pattern", "sio", error, pat->name, pat->loop_end, pat))
return FALSE;
}
uint32_t pos = 0;
for(GList *p = song->master_track_items; p; p = g_list_next(p))
{
struct cbox_master_track_item *mti = p->data;
// Omit dummy item at 0 position.
if (pos || (mti->timesig_num && mti->timesig_denom) || mti->tempo)
{
if (!cbox_execute_on(fb, NULL, "/mti", "ifii", error, pos, mti->tempo, mti->timesig_num, mti->timesig_denom))
return FALSE;
}
pos += mti->duration_ppqn;
}
return cbox_execute_on(fb, NULL, "/loop_start", "i", error, (int)song->loop_start_ppqn) &&
cbox_execute_on(fb, NULL, "/loop_end", "i", error, (int)song->loop_end_ppqn) &&
CBOX_OBJECT_DEFAULT_STATUS(song, fb, error);
}
else
if (!strcmp(cmd->command, "/set_loop") && !strcmp(cmd->arg_types, "ii"))
{
song->loop_start_ppqn = CBOX_ARG_I(cmd, 0);
song->loop_end_ppqn = CBOX_ARG_I(cmd, 1);
return TRUE;
}
else
if (!strcmp(cmd->command, "/set_mti") && !strcmp(cmd->arg_types, "ifii"))
{
cbox_song_set_mti(song, CBOX_ARG_I(cmd, 0), CBOX_ARG_F(cmd, 1), CBOX_ARG_I(cmd, 2), CBOX_ARG_I(cmd, 3));
return TRUE;
}
else
if (!strcmp(cmd->command, "/clear") && !strcmp(cmd->arg_types, ""))
{
cbox_song_clear(song);
return TRUE;
}
else
if (!strcmp(cmd->command, "/add_track") && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
struct cbox_track *track = cbox_track_new(CBOX_GET_DOCUMENT(song));
cbox_song_add_track(song, track);
if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, track))
{
CBOX_DELETE(track);
return FALSE;
}
return TRUE;
}
else
if (!strcmp(cmd->command, "/load_pattern") && !strcmp(cmd->arg_types, "si"))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
struct cbox_midi_pattern *pattern = cbox_midi_pattern_load(song, CBOX_ARG_S(cmd, 0), CBOX_ARG_I(cmd, 1), app.engine->master->ppqn_factor);
if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
{
CBOX_DELETE(pattern);
return FALSE;
}
return TRUE;
}
else
if (!strcmp(cmd->command, "/load_track") && !strcmp(cmd->arg_types, "si"))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
struct cbox_midi_pattern *pattern = cbox_midi_pattern_load_track(song, CBOX_ARG_S(cmd, 0), CBOX_ARG_I(cmd, 1), app.engine->master->ppqn_factor);
if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
{
CBOX_DELETE(pattern);
return FALSE;
}
return TRUE;
}
else
if (!strcmp(cmd->command, "/load_metronome") && !strcmp(cmd->arg_types, "i"))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
struct cbox_midi_pattern *pattern = cbox_midi_pattern_new_metronome(song, CBOX_ARG_I(cmd, 0), app.engine->master->ppqn_factor);
if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
{
CBOX_DELETE(pattern);
return FALSE;
}
return TRUE;
}
else
if (!strcmp(cmd->command, "/load_blob") && !strcmp(cmd->arg_types, "bi"))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
struct cbox_midi_pattern *pattern = cbox_midi_pattern_new_from_blob(song, CBOX_ARG_B(cmd, 0), CBOX_ARG_I(cmd, 1), app.engine->master->ppqn_factor);
if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
{
CBOX_DELETE(pattern);
return FALSE;
}
return TRUE;
}
else
return cbox_object_default_process_cmd(ct, fb, cmd, error);
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
struct cbox_song *cbox_song_new(struct cbox_document *document)
{
struct cbox_song *p = calloc(1, sizeof(struct cbox_song));
CBOX_OBJECT_HEADER_INIT(p, cbox_song, document);
// Create the first, dummy tempo map item
struct cbox_master_track_item *mti = calloc(1, sizeof(struct cbox_master_track_item));
mti->timesig_num = 0;
mti->timesig_denom = 0;
mti->tempo = 0;
mti->duration_ppqn = 0;
p->master_track_items = g_list_append(NULL, mti);
p->tracks = NULL;
p->patterns = NULL;
p->lyrics_sheet = NULL;
p->chord_sheet = NULL;
p->loop_start_ppqn = 0;
p->loop_end_ppqn = 0;
cbox_command_target_init(&p->cmd_target, cbox_song_process_cmd, p);
CBOX_OBJECT_REGISTER(p);
return p;
}
void cbox_song_set_mti(struct cbox_song *song, uint32_t pos, double tempo, int timesig_num, int timesig_denom)
{
uint32_t tstart = 0, tend = 0;
GList *prev = NULL;
// A full no-op
if (tempo < 0 && timesig_num < 0)
return;
gboolean is_noop = tempo == 0 && timesig_num == 0;
struct cbox_master_track_item *mti = NULL;
for(GList *p = song->master_track_items; p; p = g_list_next(p))
{
mti = p->data;
tend = tstart + mti->duration_ppqn;
// printf("range %d-%d %f %d\n", tstart, tend, mti->tempo, mti->timesig_num);
if (pos == tstart)
{
double new_tempo = tempo >= 0 ? tempo : mti->tempo;
int new_timesig_num = timesig_num >= 0 ? timesig_num : mti->timesig_num;
// Is this operation going to become a no-op after the change?
gboolean is_noop_here = new_tempo <= 0 && new_timesig_num <= 0;
// If the new item is a no-op and not the first item, delete it
// and extend the previous item by deleted item's duration
if (is_noop_here)
{
uint32_t deleted_duration = mti->duration_ppqn;
if (prev) {
song->master_track_items = g_list_remove(song->master_track_items, mti);
mti = prev->data;
mti->duration_ppqn += deleted_duration;
} else {
// Instead of deleting the first item, make it a dummy one.
mti->tempo = 0;
mti->timesig_num = 0;
mti->timesig_denom = 0;
}
return;
}
goto set_values;
}
if (pos >= tstart && pos < tend)
{
if (is_noop || (tempo <= 0 && timesig_num <= 0))
return;
// Split old item's duration
mti->duration_ppqn = pos - tstart;
mti = calloc(1, sizeof(struct cbox_master_track_item));
mti->duration_ppqn = tend - pos;
p = g_list_next(p);
song->master_track_items = g_list_insert_before(song->master_track_items, p, mti);
goto set_values;
}
prev = p;
tstart = tend;
}
// The new item is a no-op and it's not deleting any of the current MTIs.
// Ignore it then.
if (is_noop)
return;
// The add position is past the end of the current MTIs.
if (pos > tend)
{
// Either extend the previous item, if there's any
if (prev)
{
mti = prev->data;
mti->duration_ppqn += pos - tend;
}
else
{
// ... or add a dummy 'pad' item
mti = calloc(1, sizeof(struct cbox_master_track_item));
mti->duration_ppqn = pos;
assert(!song->master_track_items);
song->master_track_items = g_list_append(song->master_track_items, mti);
prev = song->master_track_items;
}
}
// Add the new item at the end
mti = calloc(1, sizeof(struct cbox_master_track_item));
song->master_track_items = g_list_append(song->master_track_items, mti);
set_values:
// No effect if -1
if (tempo >= 0)
mti->tempo = tempo;
if ((timesig_num > 0 && timesig_denom > 0) ||
(timesig_num == 0 && timesig_denom == 0))
{
mti->timesig_num = timesig_num;
mti->timesig_denom = timesig_denom;
}
}
void cbox_song_add_track(struct cbox_song *song, struct cbox_track *track)
{
track->owner = song;
song->tracks = g_list_append(song->tracks, track);
}
void cbox_song_remove_track(struct cbox_song *song, struct cbox_track *track)
{
assert(track->owner == song);
song->tracks = g_list_remove(song->tracks, track);
track->owner = NULL;
}
void cbox_song_add_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern)
{
pattern->owner = song;
song->patterns = g_list_append(song->patterns, pattern);
}
void cbox_song_remove_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern)
{
assert(pattern->owner == song);
pattern->owner = NULL;
song->patterns = g_list_remove(song->patterns, pattern);
}
void cbox_song_clear(struct cbox_song *song)
{
while(song->tracks)
cbox_object_destroy(song->tracks->data);
while(song->patterns)
cbox_object_destroy(song->patterns->data);
while(song->master_track_items)
{
struct cbox_master_track_item *mti = song->master_track_items->data;
song->master_track_items = g_list_remove(song->master_track_items, mti);
cbox_master_track_item_destroy(mti);
}
}
void cbox_song_use_looped_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern)
{
assert(pattern->owner == song);
song->patterns = g_list_remove(song->patterns, pattern);
pattern->owner = NULL;
cbox_song_clear(song);
struct cbox_track *trk = cbox_track_new(CBOX_GET_DOCUMENT(song));
cbox_song_add_track(song, trk);
cbox_song_add_pattern(song, pattern);
song->loop_start_ppqn = 0;
song->loop_end_ppqn = pattern->loop_end;
cbox_track_add_item(trk, 0, pattern, 0, pattern->loop_end);
cbox_engine_update_song_playback(app.engine);
}
void cbox_song_destroyfunc(struct cbox_objhdr *objhdr)
{
struct cbox_song *song = CBOX_H2O(objhdr);
cbox_song_clear(song);
free(song);
}