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.
931 lines
34 KiB
931 lines
34 KiB
3 years ago
|
/*
|
||
|
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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "engine.h"
|
||
|
#include "pattern.h"
|
||
|
#include "rt.h"
|
||
|
#include "seq.h"
|
||
|
#include "song.h"
|
||
|
#include "track.h"
|
||
|
#include <assert.h>
|
||
|
|
||
|
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;
|
||
|
}
|