/* Calf Box, an open source musical instrument. Copyright (C) 2010-2013 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 "engine.h" #include "pattern.h" #include "rt.h" #include "seq.h" #include "song.h" #include "track.h" #include static inline void accumulate_event(struct cbox_midi_playback_active_notes *notes, const struct cbox_midi_event *event) { if (event->size != 3) return; // this ignores poly aftertouch - which, I supposed, is OK for now if (event->data_inline[0] < 0x90 || event->data_inline[0] > 0x9F) return; if (event->data_inline[2] > 0) { int ch = event->data_inline[0] & 0x0F; int note = event->data_inline[1] & 0x7F; if (!(notes->channels_active & (1 << ch))) { for (int i = 0; i < 4; i++) notes->notes[ch][i] = 0; notes->channels_active |= 1 << ch; } notes->notes[ch][note >> 5] |= 1 << (note & 0x1F); } } // this releases a note on note off (accumulate_event is 'sticky') static inline void accumulate_event2(struct cbox_midi_playback_active_notes *notes, const struct cbox_midi_event *event) { if (event->size != 3) return; // this ignores poly aftertouch - which, I supposed, is OK for now if (event->data_inline[0] < 0x80 || event->data_inline[0] > 0x9F) return; int ch = event->data_inline[0] & 0x0F; int note = event->data_inline[1] & 0x7F; uint32_t mask = 1 << (note & 0x1F); if (event->data_inline[0] >= 0x90 && event->data_inline[2] > 0) { if (!(notes->channels_active & (1 << ch))) { for (int i = 0; i < 4; i++) notes->notes[ch][i] = 0; notes->channels_active |= 1 << ch; } notes->notes[ch][note >> 5] |= mask; } else { if (notes->notes[ch][note >> 5] & mask) { notes->notes[ch][note >> 5] &= ~mask; if (!notes->notes[ch][0] && !notes->notes[ch][1] && !notes->notes[ch][2] && !notes->notes[ch][3]) { notes->channels_active &= ~(1 << ch); } } } } struct cbox_track_playback *cbox_track_playback_new_from_track(struct cbox_track *track, struct cbox_master *master, struct cbox_song_playback *spb, struct cbox_track_playback *old_state) { struct cbox_track_playback *pb = malloc(sizeof(struct cbox_track_playback)); cbox_uuid_copy(&pb->track_uuid, &CBOX_O2H(track)->instance_uuid); pb->old_state = old_state; pb->generation = track->generation; pb->ref_count = 1; pb->master = master; int len = g_list_length(track->items); pb->items = calloc(len, sizeof(struct cbox_track_playback_item)); pb->external_merger = NULL; pb->spb = spb; pb->state_copied = FALSE; pb->mute = track->mute; GList *it = track->items; struct cbox_track_playback_item *p = pb->items; uint32_t safe = 0; while(it != NULL) { struct cbox_track_item *item = it->data; struct cbox_midi_pattern_playback *mppb = cbox_song_playback_get_pattern(spb, item->pattern); // if items overlap, the first one takes precedence if (item->time < safe) { // fully contained in previous item? skip all of it // not fully contained - insert the fragment if (item->time + item->length >= safe) { int cut = safe - item->time; p->time = safe; p->pattern = mppb; p->offset = item->offset + cut; p->length = item->length - cut; p++; } } else { p->time = item->time; p->pattern = mppb; p->offset = item->offset; p->length = item->length; safe = item->time + item->length; p++; } it = g_list_next(it); } // in case of full overlap, some items might have been skipped pb->items_count = p - pb->items; pb->pos = 0; cbox_midi_clip_playback_init(&pb->playback, &pb->active_notes, master); cbox_midi_playback_active_notes_init(&pb->active_notes); cbox_midi_buffer_init(&pb->output_buffer); cbox_track_playback_start_item(pb, 0, FALSE, 0); if (track->external_output_set) { struct cbox_midi_merger *merger = cbox_rt_get_midi_output(spb->engine->rt, &track->external_output); if (merger) cbox_midi_merger_connect(merger, &pb->output_buffer, spb->engine->rt, &pb->external_merger); } return pb; } void cbox_track_confirm_stuck_notes(struct cbox_track_playback *pb, struct cbox_midi_playback_active_notes *stuck_notes, uint32_t new_pos_ppqn) { // Check if no notes are stuck if (!stuck_notes->channels_active) return; uint32_t pos = 0; while(pos < pb->items_count && pb->items[pos].time + pb->items[pos].length < new_pos_ppqn) pos++; if (pos >= pb->items_count) // past the end of the track - all notes are stuck return; const struct cbox_track_playback_item *tpi = &pb->items[pos]; uint32_t rel_time_ppqn = new_pos_ppqn - tpi->time; if (rel_time_ppqn < tpi->length) { // inside the clip rel_time_ppqn += tpi->offset; for (unsigned c = 0; c < 16; c++) { if (!(stuck_notes->channels_active & (1 << c))) continue; gboolean any_left = FALSE; for (unsigned g = 0; g < 4; g++) { uint32_t group = stuck_notes->notes[c][g]; if (!group) continue; for (unsigned i = 0; i < 32; i++) { if (!(group & (1 << i))) continue; uint8_t n = i + g * 32; if (cbox_midi_pattern_playback_is_note_active_at(tpi->pattern, rel_time_ppqn, c, n)) { // That note is not stuck group &= ~(1 << i); } else { // It is stuck, so keep the channel as containing stuck notes any_left = TRUE; } } stuck_notes->notes[c][g] = group; } if (!any_left) { stuck_notes->channels_active &= ~(1 << c); } } return; } } void cbox_track_playback_seek_ppqn(struct cbox_track_playback *pb, uint32_t time_ppqn, uint32_t min_time_ppqn) { pb->pos = 0; while(pb->pos < pb->items_count && pb->items[pb->pos].time + pb->items[pb->pos].length < time_ppqn) pb->pos++; cbox_track_playback_start_item(pb, time_ppqn, TRUE, min_time_ppqn); } void cbox_track_playback_seek_samples(struct cbox_track_playback *pb, uint32_t time_samples) { pb->pos = 0; while(pb->pos < pb->items_count && cbox_master_ppqn_to_samples(pb->master, pb->items[pb->pos].time + pb->items[pb->pos].length) < time_samples) pb->pos++; if (pb->pos < pb->items_count) { int min_time_ppqn = cbox_master_samples_to_ppqn(pb->master, time_samples); cbox_track_playback_start_item(pb, time_samples, FALSE, min_time_ppqn); } } void cbox_track_playback_start_item(struct cbox_track_playback *pb, int time, int is_ppqn, int min_time_ppqn) { if (pb->pos >= pb->items_count) { return; } struct cbox_track_playback_item *cur = &pb->items[pb->pos]; int time_samples, time_ppqn; if (is_ppqn) { time_ppqn = time; time_samples = cbox_master_ppqn_to_samples(pb->master, time_ppqn); } else { time_samples = time; time_ppqn = cbox_master_samples_to_ppqn(pb->master, time_samples); } int start_time_ppqn = cur->time, end_time_ppqn = cur->time + cur->length; int start_time_samples = cbox_master_ppqn_to_samples(pb->master, start_time_ppqn); int end_time_samples = cbox_master_ppqn_to_samples(pb->master, end_time_ppqn); cbox_midi_clip_playback_set_pattern(&pb->playback, cur->pattern, start_time_samples, end_time_samples, cur->time, cur->offset); if (is_ppqn) { if (time_ppqn < start_time_ppqn) cbox_midi_clip_playback_seek_ppqn(&pb->playback, 0, min_time_ppqn); else cbox_midi_clip_playback_seek_ppqn(&pb->playback, time_ppqn - start_time_ppqn, min_time_ppqn); } else { if (time_ppqn < start_time_ppqn) cbox_midi_clip_playback_seek_samples(&pb->playback, 0, min_time_ppqn); else cbox_midi_clip_playback_seek_samples(&pb->playback, time_samples - start_time_samples, min_time_ppqn); } } void cbox_track_playback_render(struct cbox_track_playback *pb, uint32_t offset, uint32_t nsamples) { struct cbox_song_playback *spb = pb->master->spb; if (pb->mute) { cbox_midi_playback_active_notes_release(&pb->active_notes, &pb->output_buffer, NULL); } uint32_t rpos = 0; while(rpos < nsamples && pb->pos < pb->items_count) { uint32_t rend = nsamples; struct cbox_track_playback_item *cur = &pb->items[pb->pos]; // a gap before the current item if (spb->song_pos_samples + rpos < pb->playback.start_time_samples) { uint32_t space_samples = pb->playback.start_time_samples - (spb->song_pos_samples + rpos); if (space_samples >= rend - rpos) return; rpos += space_samples; offset += space_samples; } // check if item finished int cur_segment_end_samples = cbox_master_ppqn_to_samples(pb->master, cur->time + cur->length); int render_end_samples = spb->song_pos_samples + rend; if (render_end_samples > cur_segment_end_samples) { rend = cur_segment_end_samples - spb->song_pos_samples; cbox_midi_clip_playback_render(&pb->playback, &pb->output_buffer, offset, rend - rpos, pb->mute); pb->pos++; cbox_track_playback_start_item(pb, cur_segment_end_samples, FALSE, FALSE); } else cbox_midi_clip_playback_render(&pb->playback, &pb->output_buffer, offset, rend - rpos, pb->mute); offset += rend - rpos; rpos = rend; } } void cbox_track_playback_ref(struct cbox_track_playback *pb) { ++pb->ref_count; } void cbox_track_playback_destroy(struct cbox_track_playback *pb) { if (pb->external_merger) cbox_midi_merger_disconnect(pb->external_merger, &pb->output_buffer, pb->spb->engine->rt); for (uint32_t i = 0; i < pb->items_count; ++i) cbox_midi_pattern_playback_unref(pb->items[i].pattern); free(pb->items); free(pb); } ///////////////////////////////////////////////////////////////////////////////////////////////////// static gint note_compare_fn(const void *p1, const void *p2, void *user_data) { const struct cbox_midi_event *e1 = p1, *e2 = p2; int cn1 = ((e1->data_inline[0] & 0x0F) << 8) | e1->data_inline[1]; int cn2 = ((e2->data_inline[0] & 0x0F) << 8) | e2->data_inline[1]; if (cn1 < cn2) return -1; if (cn2 < cn1) return +1; if (e1->time < e2->time) return -1; if (e1->time > e2->time) return +1; if (p1 < p2) return -1; if (p1 > p2) return +1; return 0; } ///////////////////////////////////////////////////////////////////////////////////////////////////// struct cbox_midi_pattern_playback *cbox_midi_pattern_playback_new(struct cbox_midi_pattern *pattern) { struct cbox_midi_pattern_playback *mppb = calloc(1, sizeof(struct cbox_midi_pattern_playback)); mppb->events = malloc(sizeof(struct cbox_midi_event) * pattern->event_count); memcpy(mppb->events, pattern->events, sizeof(struct cbox_midi_event) * pattern->event_count); mppb->event_count = pattern->event_count; mppb->ref_count = 1; cbox_midi_playback_active_notes_init(&mppb->note_bitmask); mppb->note_lookup = g_sequence_new(NULL); for (uint32_t i = 0; i < mppb->event_count; ++i) { struct cbox_midi_event *event = &mppb->events[i]; if (event->size == 3 && (event->data_inline[0] & 0xE0) == 0x80) { g_sequence_insert_sorted(mppb->note_lookup, event, note_compare_fn, NULL); if (event->data_inline[0] >= 0x90) accumulate_event(&mppb->note_bitmask, event); } } return mppb; } void cbox_midi_pattern_playback_unref(struct cbox_midi_pattern_playback *mppb) { if (!(--mppb->ref_count)) cbox_midi_pattern_playback_destroy(mppb); } void cbox_midi_pattern_playback_ref(struct cbox_midi_pattern_playback *mppb) { ++mppb->ref_count; } void cbox_midi_pattern_playback_destroy(struct cbox_midi_pattern_playback *mppb) { g_sequence_free(mppb->note_lookup); free(mppb->events); free(mppb); } gboolean cbox_midi_pattern_playback_is_note_active_at(struct cbox_midi_pattern_playback *mppb, uint32_t time_ppqn, uint32_t channel, uint32_t note) { struct cbox_midi_event event; event.time = time_ppqn; event.size = 3; event.data_inline[0] = 0x90 | channel; event.data_inline[1] = note; event.data_inline[2] = 127; // printf("checking stuck note ch %d note %d at %d\n", channel, note, time_ppqn); GSequenceIter *i = g_sequence_search(mppb->note_lookup, &event, note_compare_fn, NULL); if (g_sequence_iter_is_begin(i)) // before first note { // printf("before first note\n"); return FALSE; } i = g_sequence_iter_prev(i); // A preceding note with the same channel and note number struct cbox_midi_event *pevent = g_sequence_get(i); // If it's an event for a different note, channel or not a note on event, then the note hasn't been active at the time // XXXKF what about notes that start before clip offset? if (pevent->size != 3 || pevent->data_inline[0] != event.data_inline[0] || pevent->data_inline[1] != event.data_inline[1] || !pevent->data_inline[2]) { // printf("pevent wrong %d %d %d %d\n", pevent->time, pevent->data_inline[0], pevent->data_inline[1], pevent->data_inline[2]); return FALSE; } // printf("confirmed note ch %d note %d\n", channel, note); return TRUE; } ///////////////////////////////////////////////////////////////////////////////////////////////////// void cbox_midi_clip_playback_init(struct cbox_midi_clip_playback *pb, struct cbox_midi_playback_active_notes *active_notes, struct cbox_master *master) { pb->pattern = NULL; pb->master = master; pb->pos = 0; pb->rel_time_samples = 0; pb->start_time_samples = 0; pb->end_time_samples = 0; pb->active_notes = active_notes; pb->min_time_ppqn = 0; // cbox_midi_playback_active_notes_init(active_notes); } void cbox_midi_clip_playback_set_pattern(struct cbox_midi_clip_playback *pb, struct cbox_midi_pattern_playback *pattern, int start_time_samples, int end_time_samples, int item_start_ppqn, int offset_ppqn) { pb->pattern = pattern; pb->pos = 0; pb->rel_time_samples = 0; pb->start_time_samples = start_time_samples; pb->end_time_samples = end_time_samples; pb->item_start_ppqn = item_start_ppqn; pb->offset_ppqn = offset_ppqn; pb->min_time_ppqn = offset_ppqn; } void cbox_midi_clip_playback_render(struct cbox_midi_clip_playback *pb, struct cbox_midi_buffer *buf, uint32_t offset, uint32_t nsamples, gboolean mute) { uint32_t end_time_samples = pb->end_time_samples; uint32_t cur_time_samples = pb->start_time_samples + pb->rel_time_samples; if (end_time_samples > cur_time_samples + nsamples) end_time_samples = cur_time_samples + nsamples; while(pb->pos < pb->pattern->event_count) { const struct cbox_midi_event *src = &pb->pattern->events[pb->pos]; if (src->time - pb->offset_ppqn + pb->item_start_ppqn >= pb->min_time_ppqn) { uint32_t event_time_samples = cbox_master_ppqn_to_samples(pb->master, src->time - pb->offset_ppqn + pb->item_start_ppqn); if (event_time_samples >= end_time_samples) break; int32_t time = 0; if (event_time_samples >= cur_time_samples) // convert negative relative time to 0 time time = event_time_samples - cur_time_samples; if (!mute) { cbox_midi_buffer_copy_event(buf, src, offset + time); if (pb->active_notes) accumulate_event2(pb->active_notes, src); } } pb->pos++; } pb->rel_time_samples += nsamples; } void cbox_midi_clip_playback_seek_ppqn(struct cbox_midi_clip_playback *pb, uint32_t time_ppqn, uint32_t min_time_ppqn) { uint32_t patrel_time_ppqn = time_ppqn + pb->offset_ppqn; uint32_t L = 0, U = pb->pattern->event_count; if (patrel_time_ppqn > 0) { while (U > L + 2) { uint32_t M = (L >> 1) + (U >> 1) + (L & U & 1); uint32_t time = pb->pattern->events[M].time; if (time < patrel_time_ppqn) L = M + 1; else if (time >= patrel_time_ppqn) U = M + 1; // this might still be the event we're looking for } } uint32_t pos = L; while (pos < U && pb->pattern->events[pos].time < patrel_time_ppqn) pos++; pb->rel_time_samples = cbox_master_ppqn_to_samples(pb->master, pb->item_start_ppqn + time_ppqn) - pb->start_time_samples; pb->min_time_ppqn = min_time_ppqn; pb->pos = pos; } void cbox_midi_clip_playback_seek_samples(struct cbox_midi_clip_playback *pb, uint32_t time_samples, uint32_t min_time_ppqn) { uint32_t pos = 0; while (pos < pb->pattern->event_count && time_samples > cbox_master_ppqn_to_samples(pb->master, pb->item_start_ppqn + pb->pattern->events[pos].time - pb->offset_ppqn)) pos++; pb->rel_time_samples = time_samples; pb->min_time_ppqn = min_time_ppqn; pb->pos = pos; } ///////////////////////////////////////////////////////////////////////////////////////////////////// void cbox_midi_playback_active_notes_init(struct cbox_midi_playback_active_notes *notes) { notes->channels_active = 0; } void cbox_midi_playback_active_notes_copy(struct cbox_midi_playback_active_notes *dest, const struct cbox_midi_playback_active_notes *src) { dest->channels_active = src->channels_active; memcpy(dest->notes, src->notes, sizeof(dest->notes)); } int cbox_midi_playback_active_notes_release(struct cbox_midi_playback_active_notes *notes, struct cbox_midi_buffer *buf, struct cbox_midi_playback_active_notes *leftover_notes) { if (!notes->channels_active) return 0; int note_offs = 0; for (int c = 0; c < 16; c++) { if (!(notes->channels_active & (1 << c))) continue; for (int g = 0; g < 4; g++) { uint32_t group = notes->notes[c][g]; if (!group) continue; for (int i = 0; i < 32; i++) { int n = i + g * 32; if (!(group & (1 << i))) continue; if (!cbox_midi_buffer_can_store_msg(buf, 3)) return -1; cbox_midi_buffer_write_inline(buf, cbox_midi_buffer_get_last_event_time(buf), 0x80 + c, n, 0); group &= ~(1 << i); notes->notes[c][g] = group; if (leftover_notes) leftover_notes->notes[c][g] &= ~(1 << i); note_offs++; } } // all Note Offs emitted without buffer overflow - channel is no longer active notes->channels_active &= ~(1 << c); } return note_offs; } ///////////////////////////////////////////////////////////////////////////////////////////////////// struct cbox_song_playback *cbox_song_playback_new(struct cbox_song *song, struct cbox_master *master, struct cbox_engine *engine, struct cbox_song_playback *old_state) { struct cbox_song_playback *spb = calloc(1, sizeof(struct cbox_song_playback)); if (old_state && old_state->song != song) old_state = NULL; spb->song = song; spb->engine = engine; spb->pattern_map = g_hash_table_new(NULL, NULL); spb->master = master; spb->track_count = g_list_length(song->tracks); spb->tracks = malloc(spb->track_count * sizeof(struct cbox_track_playback *)); spb->song_pos_samples = 0; spb->song_pos_ppqn = 0; spb->min_time_ppqn = 0; spb->loop_start_ppqn = song->loop_start_ppqn; spb->loop_end_ppqn = song->loop_end_ppqn; cbox_midi_merger_init(&spb->track_merger, NULL); int pos = 0; for (GList *p = song->tracks; p != NULL; p = g_list_next(p)) { struct cbox_track *trk = p->data; struct cbox_track_playback *old_trk = NULL; if (old_state && old_state->track_count) { for (uint32_t i = 0; i < old_state->track_count; i++) { if (cbox_uuid_equal(&old_state->tracks[i]->track_uuid, &CBOX_O2H(trk)->instance_uuid)) { old_trk = old_state->tracks[i]; break; } } } if (old_trk && trk->generation == old_trk->generation) { old_trk->state_copied = TRUE; cbox_track_playback_ref(old_trk); spb->tracks[pos++] = old_trk; } else { if (old_trk) old_trk->state_copied = FALSE; spb->tracks[pos++] = cbox_track_playback_new_from_track(trk, spb->master, spb, old_trk); } if (!trk->external_output_set) cbox_midi_merger_connect(&spb->track_merger, &spb->tracks[pos - 1]->output_buffer, NULL, NULL); } spb->tempo_map_item_count = g_list_length(song->master_track_items); spb->tempo_map_items = malloc(spb->tempo_map_item_count * sizeof(struct cbox_tempo_map_item)); pos = 0; int pos_ppqn = 0; int pos_samples = 0; double tempo = master->tempo; int timesig_num = master->timesig_num; int timesig_denom = master->timesig_denom; struct cbox_bbt cur_bbt = {0, 0, 0, 0}; for (GList *p = song->master_track_items; p != NULL; p = g_list_next(p)) { struct cbox_master_track_item *mti = p->data; if (mti->tempo == 0 && mti->timesig_num == 0 && mti->timesig_denom == 0 && p == song->master_track_items) { spb->tempo_map_item_count--; continue; } if (mti->tempo > 0) tempo = mti->tempo; if (mti->timesig_num > 0) timesig_num = mti->timesig_num; if (mti->timesig_denom > 0) timesig_denom = mti->timesig_denom; struct cbox_tempo_map_item *tmi = &spb->tempo_map_items[pos]; tmi->time_ppqn = pos_ppqn; tmi->time_samples = pos_samples; tmi->tempo = tempo; tmi->timesig_num = timesig_num; tmi->timesig_denom = timesig_denom; memcpy(&tmi->bbt, &cur_bbt, sizeof(cur_bbt)); cbox_bbt_add(&cur_bbt, mti->duration_ppqn, master->ppqn_factor, timesig_num, timesig_denom); pos_ppqn += mti->duration_ppqn; pos_samples += master->srate * 60.0 * mti->duration_ppqn / (tempo * master->ppqn_factor); pos++; } return spb; } void cbox_song_playback_apply_old_state(struct cbox_song_playback *spb) { for (uint32_t i = 0; i < spb->track_count; i++) { struct cbox_track_playback *tpb = spb->tracks[i]; tpb->spb = spb; if (tpb->old_state) { cbox_midi_playback_active_notes_copy(&tpb->active_notes, &tpb->old_state->active_notes); tpb->old_state->state_copied = TRUE; tpb->old_state = NULL; } } } static void cbox_song_playback_set_tempo(struct cbox_song_playback *spb, double tempo) { int ppos = spb->song_pos_ppqn; int pos1 = cbox_master_ppqn_to_samples(spb->master, ppos); int pos2 = cbox_master_ppqn_to_samples(spb->master, ppos + 1); double relpos = 0.0; if (pos1 != pos2) relpos = (spb->song_pos_samples - pos1) * 1.0 / (pos2 - pos1); spb->master->tempo = tempo; // This seek loses the fractional value of the PPQN song position. // This needs to be compensated for by shifting the playback // position by the fractional part. cbox_song_playback_seek_ppqn(spb, ppos, spb->min_time_ppqn); if (relpos > 0) { pos2 = cbox_master_ppqn_to_samples(spb->master, ppos + 1); cbox_song_playback_seek_samples(spb, spb->song_pos_samples + (pos2 - spb->song_pos_samples) * relpos + 0.5); } } int cbox_song_playback_get_next_tempo_change(struct cbox_song_playback *spb) { double new_tempo = 0; // Skip items at or already past the playback pointer while (spb->tempo_map_pos + 1 < spb->tempo_map_item_count && spb->song_pos_samples >= spb->tempo_map_items[spb->tempo_map_pos + 1].time_samples) { new_tempo = spb->tempo_map_items[spb->tempo_map_pos + 1].tempo; spb->tempo_map_pos++; } if (new_tempo != 0.0 && new_tempo != spb->master->tempo) { cbox_song_playback_set_tempo(spb, new_tempo); } // No more items? if (spb->tempo_map_pos + 1 >= spb->tempo_map_item_count) return -1; return spb->tempo_map_items[spb->tempo_map_pos + 1].time_samples; } void cbox_song_playback_prepare_render(struct cbox_song_playback *spb) { for(uint32_t i = 0; i < spb->track_count; i++) { cbox_midi_buffer_clear(&spb->tracks[i]->output_buffer); } } void cbox_song_playback_render(struct cbox_song_playback *spb, struct cbox_midi_buffer *output, uint32_t nsamples) { cbox_midi_buffer_clear(output); if (spb->master->new_tempo != 0) { if (spb->master->new_tempo != spb->master->tempo) cbox_song_playback_set_tempo(spb, spb->master->new_tempo); spb->master->new_tempo = 0; } if (spb->master->state == CMTS_STOPPING) { if (cbox_song_playback_active_notes_release(spb, NULL, 0, output) > 0) spb->master->state = CMTS_STOP; } else if (spb->master->state == CMTS_ROLLING) { uint32_t end_samples = cbox_master_ppqn_to_samples(spb->master, spb->loop_end_ppqn); uint32_t rpos = 0; while (rpos < nsamples) { uint32_t rend = nsamples; // 1. Shorten the period so that it doesn't go past a tempo change int tmpos = cbox_song_playback_get_next_tempo_change(spb); if (tmpos != -1) { // Number of samples until the next tempo change uint32_t stntc = tmpos - spb->song_pos_samples; if (rend - rpos > stntc) rend = rpos + stntc; } // 2. Shorten the period so that it doesn't go past the song length uint32_t end_pos = spb->song_pos_samples + (rend - rpos); if (end_pos >= end_samples) { rend = end_samples - spb->song_pos_samples; end_pos = end_samples; } if (rend > rpos) { for (uint32_t i = 0; i < spb->track_count; i++) cbox_track_playback_render(spb->tracks[i], rpos, rend - rpos); } if (end_pos < end_samples) { spb->song_pos_samples += rend - rpos; // XXXKF optimize spb->min_time_ppqn = cbox_master_samples_to_ppqn(spb->master, spb->song_pos_samples - 1) + 1; spb->song_pos_ppqn = cbox_master_samples_to_ppqn(spb->master, spb->song_pos_samples); } else { if (spb->loop_start_ppqn >= spb->loop_end_ppqn) { spb->song_pos_samples = end_samples; spb->song_pos_ppqn = spb->loop_end_ppqn; spb->master->state = CMTS_STOPPING; break; } cbox_song_playback_seek_ppqn(spb, spb->loop_start_ppqn, spb->loop_start_ppqn); } rpos = rend; } cbox_midi_merger_render_to(&spb->track_merger, output); } } int cbox_song_playback_active_notes_release(struct cbox_song_playback *spb, struct cbox_song_playback *new_spb, uint32_t new_pos, struct cbox_midi_buffer *buf) { // Release notes from deleted tracks for(uint32_t i = 0; i < spb->track_count; i++) { struct cbox_track_playback *trk = spb->tracks[i]; if (new_spb && trk->state_copied) continue; struct cbox_midi_buffer *output = trk->external_merger ? &trk->output_buffer : buf; if (cbox_midi_playback_active_notes_release(&trk->active_notes, output, NULL) < 0) return 0; } // Release notes from removed/modified clips if (new_spb) { for(uint32_t i = 0; i < new_spb->track_count; i++) { struct cbox_track_playback *new_trk = new_spb->tracks[i]; if (!new_trk->active_notes.channels_active) continue; // struct cbox_track_playback *old_trk = new_trk->old_state; // if (!old_trk) // continue; struct cbox_midi_buffer *output = new_trk->external_merger ? &new_trk->output_buffer : buf; struct cbox_midi_playback_active_notes stuck_notes; cbox_midi_playback_active_notes_copy(&stuck_notes, &new_trk->active_notes); cbox_track_confirm_stuck_notes(new_trk, &stuck_notes, new_pos); if (cbox_midi_playback_active_notes_release(&stuck_notes, output, &new_trk->active_notes) < 0) return 0; } } return 1; } void cbox_song_playback_seek_ppqn(struct cbox_song_playback *spb, int time_ppqn, int min_time_ppqn) { for(uint32_t i = 0; i < spb->track_count; i++) { struct cbox_track_playback *trk = spb->tracks[i]; cbox_track_playback_seek_ppqn(trk, time_ppqn, min_time_ppqn); } spb->song_pos_samples = cbox_master_ppqn_to_samples(spb->master, time_ppqn); spb->song_pos_ppqn = time_ppqn; spb->min_time_ppqn = min_time_ppqn; spb->tempo_map_pos = cbox_song_playback_tmi_from_ppqn(spb, time_ppqn); } void cbox_song_playback_seek_samples(struct cbox_song_playback *spb, uint32_t time_samples) { for(uint32_t i = 0; i < spb->track_count; i++) { struct cbox_track_playback *trk = spb->tracks[i]; cbox_track_playback_seek_samples(trk, time_samples); } spb->song_pos_samples = time_samples; spb->song_pos_ppqn = cbox_master_samples_to_ppqn(spb->master, time_samples); spb->min_time_ppqn = spb->song_pos_ppqn; spb->tempo_map_pos = cbox_song_playback_tmi_from_samples(spb, time_samples); } int cbox_song_playback_tmi_from_ppqn(struct cbox_song_playback *spb, uint32_t time_ppqn) { if (!spb->tempo_map_item_count) return -1; assert(spb->tempo_map_items[0].time_samples == 0); assert(spb->tempo_map_items[0].time_ppqn == 0); // XXXKF should use binary search here really for (int i = 1; i < spb->tempo_map_item_count; i++) { if (time_ppqn < spb->tempo_map_items[i].time_ppqn) return i - 1; } return spb->tempo_map_item_count - 1; } int cbox_song_playback_tmi_from_samples(struct cbox_song_playback *spb, uint32_t time_samples) { if (!spb->tempo_map_item_count) return -1; assert(spb->tempo_map_items[0].time_samples == 0); assert(spb->tempo_map_items[0].time_ppqn == 0); // XXXKF should use binary search here really for (int i = 1; i < spb->tempo_map_item_count; i++) { if (time_samples < spb->tempo_map_items[i].time_samples) return i - 1; } return spb->tempo_map_item_count - 1; } struct cbox_midi_pattern_playback *cbox_song_playback_get_pattern(struct cbox_song_playback *spb, struct cbox_midi_pattern *pattern) { struct cbox_midi_pattern_playback *mppb = g_hash_table_lookup(spb->pattern_map, pattern); if (mppb) { cbox_midi_pattern_playback_ref(mppb); return mppb; } mppb = cbox_midi_pattern_playback_new(pattern); g_hash_table_insert(spb->pattern_map, pattern, mppb); return mppb; } uint32_t cbox_song_playback_correct_for_looping(struct cbox_song_playback *spb, uint32_t abs_samples) { struct cbox_engine *engine = spb->engine; // This is rather expensive, the start/end of the loop expressed in samples should be cached. uint32_t loop_end_samples = cbox_master_ppqn_to_samples(engine->master, spb->loop_end_ppqn); if (abs_samples >= loop_end_samples) { // Correct for looping uint32_t loop_start_samples = cbox_master_ppqn_to_samples(engine->master, spb->loop_start_ppqn); if (loop_start_samples < loop_end_samples) { uint32_t loop_length = loop_end_samples - loop_start_samples; abs_samples = loop_start_samples + (abs_samples - loop_start_samples) % loop_length; } } return abs_samples; } void cbox_song_playback_destroy(struct cbox_song_playback *spb) { cbox_midi_merger_close(&spb->track_merger, spb->engine->rt); for (uint32_t i = 0; i < spb->track_count; i++) { if (!(--spb->tracks[i]->ref_count)) cbox_track_playback_destroy(spb->tracks[i]); } free(spb->tempo_map_items); free(spb->tracks); g_hash_table_destroy(spb->pattern_map); free(spb); } ///////////////////////////////////////////////////////////////////////////////////////////////////// uint32_t cbox_song_time_mapper_map_time(struct cbox_time_mapper *tmap, uint32_t free_running_counter) { struct cbox_song_time_mapper *stmap = (struct cbox_song_time_mapper *)tmap; struct cbox_engine *engine = stmap->engine; if (!engine->spb || engine->master->state != CMTS_ROLLING) return free_running_counter & 0x7FFFFFFF; int32_t rel_samples = free_running_counter - engine->rt->io->free_running_frame_counter; if (rel_samples < 0 || rel_samples >= 1048576) return (uint32_t)-1; uint32_t abs_samples = engine->spb->song_pos_samples + rel_samples; abs_samples = cbox_song_playback_correct_for_looping(engine->spb, abs_samples); uint32_t abs_ppqn = cbox_master_samples_to_ppqn(engine->master, abs_samples); return abs_ppqn | 0x80000000; } void cbox_song_time_mapper_init(struct cbox_song_time_mapper *tmap, struct cbox_engine *engine) { tmap->tmap.map_time = cbox_song_time_mapper_map_time; tmap->engine = engine; }