/* 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 . */ #include "app.h" #include "engine.h" #include "errors.h" #include "song.h" #include "track.h" #include #include 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); }