Merge pull request #1 from atsampson/master
[calfbox.git] / song.c
blobb00726673405c55581bea8b09a2387a75d5bd8bb
1 /*
2 Calf Box, an open source musical instrument.
3 Copyright (C) 2010-2011 Krzysztof Foltman
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "app.h"
20 #include "engine.h"
21 #include "errors.h"
22 #include "song.h"
23 #include "track.h"
24 #include <assert.h>
25 #include <stdlib.h>
27 CBOX_CLASS_DEFINITION_ROOT(cbox_song)
29 /////////////////////////////////////////////////////////////////////////////////////////////////
31 void cbox_master_track_item_destroy(struct cbox_master_track_item *item)
33 free(item);
36 /////////////////////////////////////////////////////////////////////////////////////////////////
38 gboolean cbox_song_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
40 struct cbox_song *song = ct->user_data;
41 if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
43 if (!cbox_check_fb_channel(fb, cmd->command, error))
44 return FALSE;
46 for(GList *p = song->tracks; p; p = g_list_next(p))
48 struct cbox_track *trk = p->data;
49 if (!cbox_execute_on(fb, NULL, "/track", "sio", error, trk->name, g_list_length(trk->items), trk))
50 return FALSE;
52 for(GList *p = song->patterns; p; p = g_list_next(p))
54 struct cbox_midi_pattern *pat = p->data;
55 if (!cbox_execute_on(fb, NULL, "/pattern", "sio", error, pat->name, pat->loop_end, pat))
56 return FALSE;
58 uint32_t pos = 0;
59 for(GList *p = song->master_track_items; p; p = g_list_next(p))
61 struct cbox_master_track_item *mti = p->data;
62 if (!cbox_execute_on(fb, NULL, "/mti", "ifii", error, pos, mti->tempo, mti->timesig_nom, mti->timesig_denom))
63 return FALSE;
64 pos += mti->duration_ppqn;
66 return cbox_execute_on(fb, NULL, "/loop_start", "i", error, (int)song->loop_start_ppqn) &&
67 cbox_execute_on(fb, NULL, "/loop_end", "i", error, (int)song->loop_end_ppqn) &&
68 CBOX_OBJECT_DEFAULT_STATUS(song, fb, error);
70 else
71 if (!strcmp(cmd->command, "/set_loop") && !strcmp(cmd->arg_types, "ii"))
73 song->loop_start_ppqn = CBOX_ARG_I(cmd, 0);
74 song->loop_end_ppqn = CBOX_ARG_I(cmd, 1);
75 return TRUE;
77 else
78 if (!strcmp(cmd->command, "/set_mti") && !strcmp(cmd->arg_types, "ifii"))
80 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));
81 return TRUE;
83 else
84 if (!strcmp(cmd->command, "/clear") && !strcmp(cmd->arg_types, ""))
86 cbox_song_clear(song);
87 return TRUE;
89 else
90 if (!strcmp(cmd->command, "/add_track") && !strcmp(cmd->arg_types, ""))
92 if (!cbox_check_fb_channel(fb, cmd->command, error))
93 return FALSE;
95 struct cbox_track *track = cbox_track_new(CBOX_GET_DOCUMENT(song));
96 cbox_song_add_track(song, track);
97 if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, track))
99 CBOX_DELETE(track);
100 return FALSE;
103 return TRUE;
105 else
106 if (!strcmp(cmd->command, "/load_pattern") && !strcmp(cmd->arg_types, "si"))
108 if (!cbox_check_fb_channel(fb, cmd->command, error))
109 return FALSE;
111 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);
112 if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
114 CBOX_DELETE(pattern);
115 return FALSE;
118 return TRUE;
120 else
121 if (!strcmp(cmd->command, "/load_track") && !strcmp(cmd->arg_types, "si"))
123 if (!cbox_check_fb_channel(fb, cmd->command, error))
124 return FALSE;
126 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);
127 if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
129 CBOX_DELETE(pattern);
130 return FALSE;
133 return TRUE;
135 else
136 if (!strcmp(cmd->command, "/load_metronome") && !strcmp(cmd->arg_types, "i"))
138 if (!cbox_check_fb_channel(fb, cmd->command, error))
139 return FALSE;
141 struct cbox_midi_pattern *pattern = cbox_midi_pattern_new_metronome(song, CBOX_ARG_I(cmd, 0), app.engine->master->ppqn_factor);
142 if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
144 CBOX_DELETE(pattern);
145 return FALSE;
148 return TRUE;
150 else
151 if (!strcmp(cmd->command, "/load_blob") && !strcmp(cmd->arg_types, "bi"))
153 if (!cbox_check_fb_channel(fb, cmd->command, error))
154 return FALSE;
156 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);
157 if (!cbox_execute_on(fb, NULL, "/uuid", "o", error, pattern))
159 CBOX_DELETE(pattern);
160 return FALSE;
163 return TRUE;
165 else
166 return cbox_object_default_process_cmd(ct, fb, cmd, error);
167 return TRUE;
171 /////////////////////////////////////////////////////////////////////////////////////////////////
173 struct cbox_song *cbox_song_new(struct cbox_document *document)
175 struct cbox_song *p = calloc(1, sizeof(struct cbox_song));
176 CBOX_OBJECT_HEADER_INIT(p, cbox_song, document);
177 p->master_track_items = NULL;
178 p->tracks = NULL;
179 p->patterns = NULL;
180 p->lyrics_sheet = NULL;
181 p->chord_sheet = NULL;
182 p->loop_start_ppqn = 0;
183 p->loop_end_ppqn = 0;
184 cbox_command_target_init(&p->cmd_target, cbox_song_process_cmd, p);
185 CBOX_OBJECT_REGISTER(p);
187 return p;
190 void cbox_song_set_mti(struct cbox_song *song, uint32_t pos, double tempo, int timesig_nom, int timesig_denom)
192 uint32_t tstart = 0, tend = 0;
193 GList *prev = NULL;
194 // A full no-op
195 if (tempo < 0 && timesig_nom < 0)
196 return;
197 gboolean is_noop = tempo == 0 && timesig_nom == 0;
199 struct cbox_master_track_item *mti = NULL;
200 for(GList *p = song->master_track_items; p; p = g_list_next(p))
202 mti = p->data;
203 tend = tstart + mti->duration_ppqn;
204 // printf("range %d-%d %f %d\n", tstart, tend, mti->tempo, mti->timesig_nom);
205 if (pos == tstart)
207 double new_tempo = tempo >= 0 ? tempo : mti->tempo;
208 int new_timesig_nom = timesig_nom >= 0 ? timesig_nom : mti->timesig_nom;
209 // Is this operation going to become a no-op after the change?
210 gboolean is_noop_here = new_tempo <= 0 && new_timesig_nom <= 0;
211 // If the new item is a no-op and not the first item, delete it
212 // and extend the previous item by deleted item's duration
213 if (is_noop_here && prev)
215 uint32_t deleted_duration = mti->duration_ppqn;
216 song->master_track_items = g_list_remove(song->master_track_items, mti);
217 mti = prev->data;
218 mti->duration_ppqn += deleted_duration;
219 return;
221 goto set_values;
223 if (pos >= tstart && pos < tend)
225 if (is_noop || (tempo <= 0 && timesig_nom <= 0))
226 return;
227 // Split old item's duration
228 mti->duration_ppqn = pos - tstart;
229 mti = calloc(1, sizeof(struct cbox_master_track_item));
230 mti->duration_ppqn = tend - pos;
231 p = g_list_next(p);
232 song->master_track_items = g_list_insert_before(song->master_track_items, p, mti);
233 goto set_values;
235 prev = p;
236 tstart = tend;
238 // The new item is a no-op and it's not deleting any of the current MTIs.
239 // Ignore it then.
240 if (is_noop)
241 return;
242 // The add position is past the end of the current MTIs.
243 if (pos > tend)
245 // Either extend the previous item, if there's any
246 if (prev)
248 mti = prev->data;
249 mti->duration_ppqn += pos - tend;
251 else
253 // ... or add a dummy 'pad' item
254 mti = calloc(1, sizeof(struct cbox_master_track_item));
255 mti->duration_ppqn = pos;
256 assert(!song->master_track_items);
257 song->master_track_items = g_list_append(song->master_track_items, mti);
258 prev = song->master_track_items;
261 // Add the new item at the end
262 mti = calloc(1, sizeof(struct cbox_master_track_item));
263 song->master_track_items = g_list_append(song->master_track_items, mti);
264 set_values:
265 // No effect if -1
266 if (tempo >= 0)
267 mti->tempo = tempo;
268 if ((timesig_nom > 0 && timesig_denom > 0) ||
269 (timesig_nom == 0 && timesig_denom == 0))
271 mti->timesig_nom = timesig_nom;
272 mti->timesig_denom = timesig_denom;
276 void cbox_song_add_track(struct cbox_song *song, struct cbox_track *track)
278 track->owner = song;
279 song->tracks = g_list_append(song->tracks, track);
282 void cbox_song_remove_track(struct cbox_song *song, struct cbox_track *track)
284 assert(track->owner == song);
285 song->tracks = g_list_remove(song->tracks, track);
286 track->owner = NULL;
289 void cbox_song_add_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern)
291 pattern->owner = song;
292 song->patterns = g_list_append(song->patterns, pattern);
295 void cbox_song_remove_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern)
297 assert(pattern->owner == song);
298 pattern->owner = NULL;
299 song->patterns = g_list_remove(song->patterns, pattern);
302 void cbox_song_clear(struct cbox_song *song)
304 while(song->tracks)
305 cbox_object_destroy(song->tracks->data);
306 while(song->patterns)
307 cbox_object_destroy(song->patterns->data);
308 while(song->master_track_items)
310 struct cbox_master_track_item *mti = song->master_track_items->data;
311 song->master_track_items = g_list_remove(song->master_track_items, mti);
312 cbox_master_track_item_destroy(mti);
316 void cbox_song_use_looped_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern)
318 assert(pattern->owner == song);
319 song->patterns = g_list_remove(song->patterns, pattern);
320 pattern->owner = NULL;
322 cbox_song_clear(song);
323 struct cbox_track *trk = cbox_track_new(CBOX_GET_DOCUMENT(song));
324 cbox_song_add_track(song, trk);
325 cbox_song_add_pattern(song, pattern);
326 song->loop_start_ppqn = 0;
327 song->loop_end_ppqn = pattern->loop_end;
328 cbox_track_add_item(trk, 0, pattern, 0, pattern->loop_end);
329 cbox_engine_update_song_playback(app.engine);
332 void cbox_song_destroyfunc(struct cbox_objhdr *objhdr)
334 struct cbox_song *song = CBOX_H2O(objhdr);
335 cbox_song_clear(song);
336 free(song);