2 Calf Box, an open source musical instrument.
3 Copyright (C) 2010-2013 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/>.
27 static inline void accumulate_event(struct cbox_midi_playback_active_notes
*notes
, const struct cbox_midi_event
*event
)
31 // this ignores poly aftertouch - which, I supposed, is OK for now
32 if (event
->data_inline
[0] < 0x80 || event
->data_inline
[0] > 0x9F)
34 int ch
= event
->data_inline
[0] & 0x0F;
35 if (event
->data_inline
[0] >= 0x90 && event
->data_inline
[2] > 0)
37 int note
= event
->data_inline
[1] & 0x7F;
38 if (!(notes
->channels_active
& (1 << ch
)))
40 for (int i
= 0; i
< 4; i
++)
41 notes
->notes
[ch
][i
] = 0;
42 notes
->channels_active
|= 1 << ch
;
44 notes
->notes
[ch
][note
>> 5] |= 1 << (note
& 0x1F);
48 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
)
50 struct cbox_track_playback
*pb
= malloc(sizeof(struct cbox_track_playback
));
52 pb
->old_state
= old_state
;
54 int len
= g_list_length(track
->items
);
55 pb
->items
= calloc(len
, sizeof(struct cbox_track_playback_item
));
56 pb
->external_merger
= NULL
;
58 pb
->state_copied
= FALSE
;
60 GList
*it
= track
->items
;
61 struct cbox_track_playback_item
*p
= pb
->items
;
65 struct cbox_track_item
*item
= it
->data
;
66 struct cbox_midi_pattern_playback
*mppb
= cbox_song_playback_get_pattern(spb
, item
->pattern
);
68 // if items overlap, the first one takes precedence
69 if (item
->time
< safe
)
71 // fully contained in previous item? skip all of it
72 // not fully contained - insert the fragment
73 if (item
->time
+ item
->length
>= safe
)
75 int cut
= safe
- item
->time
;
78 p
->offset
= item
->offset
+ cut
;
79 p
->length
= item
->length
- cut
;
87 p
->offset
= item
->offset
;
88 p
->length
= item
->length
;
89 safe
= item
->time
+ item
->length
;
95 // in case of full overlap, some items might have been skipped
96 pb
->items_count
= p
- pb
->items
;
98 cbox_midi_clip_playback_init(&pb
->playback
, &pb
->active_notes
, master
);
99 cbox_midi_playback_active_notes_init(&pb
->active_notes
);
100 cbox_midi_buffer_init(&pb
->output_buffer
);
101 cbox_track_playback_start_item(pb
, 0, FALSE
, 0);
103 if (track
->external_output_set
)
105 struct cbox_midi_merger
*merger
= cbox_rt_get_midi_output(spb
->engine
->rt
, &track
->external_output
);
108 cbox_midi_merger_connect(merger
, &pb
->output_buffer
, spb
->engine
->rt
);
109 pb
->external_merger
= merger
;
116 void cbox_track_playback_seek_ppqn(struct cbox_track_playback
*pb
, int time_ppqn
, int min_time_ppqn
)
119 while(pb
->pos
< pb
->items_count
&& pb
->items
[pb
->pos
].time
+ pb
->items
[pb
->pos
].length
< time_ppqn
)
121 cbox_track_playback_start_item(pb
, time_ppqn
, TRUE
, min_time_ppqn
);
124 void cbox_track_playback_seek_samples(struct cbox_track_playback
*pb
, int time_samples
)
127 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
)
129 cbox_track_playback_start_item(pb
, time_samples
, FALSE
, 0);
132 void cbox_track_playback_start_item(struct cbox_track_playback
*pb
, int time
, int is_ppqn
, int min_time_ppqn
)
134 if (pb
->pos
>= pb
->items_count
)
138 struct cbox_track_playback_item
*cur
= &pb
->items
[pb
->pos
];
139 int time_samples
, time_ppqn
;
144 time_samples
= cbox_master_ppqn_to_samples(pb
->master
, time_ppqn
);
149 time_ppqn
= cbox_master_samples_to_ppqn(pb
->master
, time_samples
);
151 int start_time_ppqn
= cur
->time
, end_time_ppqn
= cur
->time
+ cur
->length
;
152 int start_time_samples
= cbox_master_ppqn_to_samples(pb
->master
, start_time_ppqn
);
153 int end_time_samples
= cbox_master_ppqn_to_samples(pb
->master
, end_time_ppqn
);
154 cbox_midi_clip_playback_set_pattern(&pb
->playback
, cur
->pattern
, start_time_samples
, end_time_samples
, cur
->time
, cur
->offset
);
158 if (time_ppqn
< start_time_ppqn
)
159 cbox_midi_clip_playback_seek_ppqn(&pb
->playback
, 0, min_time_ppqn
);
161 cbox_midi_clip_playback_seek_ppqn(&pb
->playback
, time_ppqn
- start_time_ppqn
, min_time_ppqn
);
165 if (time_ppqn
< start_time_ppqn
)
166 cbox_midi_clip_playback_seek_samples(&pb
->playback
, 0);
168 cbox_midi_clip_playback_seek_samples(&pb
->playback
, time_samples
- start_time_samples
);
172 void cbox_track_playback_render(struct cbox_track_playback
*pb
, int offset
, int nsamples
)
174 struct cbox_song_playback
*spb
= pb
->master
->spb
;
176 while(rpos
< nsamples
&& pb
->pos
< pb
->items_count
)
179 struct cbox_track_playback_item
*cur
= &pb
->items
[pb
->pos
];
180 // a gap before the current item
181 if (spb
->song_pos_samples
+ rpos
< pb
->playback
.start_time_samples
)
183 int space_samples
= pb
->playback
.start_time_samples
- (spb
->song_pos_samples
+ rpos
);
184 if (space_samples
>= rend
- rpos
)
186 rpos
+= space_samples
;
187 offset
+= space_samples
;
189 // check if item finished
190 int cur_segment_end_samples
= cbox_master_ppqn_to_samples(pb
->master
, cur
->time
+ cur
->length
);
191 int render_end_samples
= spb
->song_pos_samples
+ rend
;
192 if (render_end_samples
> cur_segment_end_samples
)
194 rend
= cur_segment_end_samples
- spb
->song_pos_samples
;
195 cbox_midi_clip_playback_render(&pb
->playback
, &pb
->output_buffer
, offset
, rend
- rpos
);
197 cbox_track_playback_start_item(pb
, cur_segment_end_samples
, FALSE
, FALSE
);
200 cbox_midi_clip_playback_render(&pb
->playback
, &pb
->output_buffer
, offset
, rend
- rpos
);
201 offset
+= rend
- rpos
;
206 void cbox_track_playback_destroy(struct cbox_track_playback
*pb
)
208 if (pb
->external_merger
)
209 cbox_midi_merger_disconnect(pb
->external_merger
, &pb
->output_buffer
, pb
->spb
->engine
->rt
);
215 /////////////////////////////////////////////////////////////////////////////////////////////////////
217 void cbox_midi_pattern_playback_destroy(struct cbox_midi_pattern_playback
*mppb
)
223 /////////////////////////////////////////////////////////////////////////////////////////////////////
225 void cbox_midi_clip_playback_init(struct cbox_midi_clip_playback
*pb
, struct cbox_midi_playback_active_notes
*active_notes
, struct cbox_master
*master
)
230 pb
->rel_time_samples
= 0;
231 pb
->start_time_samples
= 0;
232 pb
->end_time_samples
= 0;
233 pb
->active_notes
= active_notes
;
234 pb
->min_time_ppqn
= 0;
235 // cbox_midi_playback_active_notes_init(active_notes);
238 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
)
240 pb
->pattern
= pattern
;
242 pb
->rel_time_samples
= 0;
243 pb
->start_time_samples
= start_time_samples
;
244 pb
->end_time_samples
= end_time_samples
;
245 pb
->item_start_ppqn
= item_start_ppqn
;
246 pb
->offset_ppqn
= offset_ppqn
;
247 pb
->min_time_ppqn
= offset_ppqn
;
250 void cbox_midi_clip_playback_render(struct cbox_midi_clip_playback
*pb
, struct cbox_midi_buffer
*buf
, int offset
, int nsamples
)
252 uint32_t end_time_samples
= pb
->end_time_samples
;
253 uint32_t cur_time_samples
= pb
->start_time_samples
+ pb
->rel_time_samples
;
255 if (end_time_samples
> cur_time_samples
+ nsamples
)
256 end_time_samples
= cur_time_samples
+ nsamples
;
258 while(pb
->pos
< pb
->pattern
->event_count
)
260 const struct cbox_midi_event
*src
= &pb
->pattern
->events
[pb
->pos
];
262 if (src
->time
- pb
->offset_ppqn
>= pb
->min_time_ppqn
)
264 int event_time_samples
= cbox_master_ppqn_to_samples(pb
->master
, src
->time
- pb
->offset_ppqn
) + pb
->start_time_samples
;
266 if (event_time_samples
>= end_time_samples
)
269 if (event_time_samples
>= cur_time_samples
) // convert negative relative time to 0 time
270 time
= event_time_samples
- cur_time_samples
;
272 cbox_midi_buffer_copy_event(buf
, src
, offset
+ time
);
273 if (pb
->active_notes
)
274 accumulate_event(pb
->active_notes
, src
);
278 pb
->rel_time_samples
+= nsamples
;
281 void cbox_midi_clip_playback_seek_ppqn(struct cbox_midi_clip_playback
*pb
, int time_ppqn
, int min_time_ppqn
)
284 int patrel_time_ppqn
= time_ppqn
+ pb
->offset_ppqn
;
285 while (pos
< pb
->pattern
->event_count
&& patrel_time_ppqn
> pb
->pattern
->events
[pos
].time
)
287 pb
->rel_time_samples
= cbox_master_ppqn_to_samples(pb
->master
, pb
->item_start_ppqn
+ time_ppqn
) - pb
->start_time_samples
;
288 pb
->min_time_ppqn
= min_time_ppqn
;
292 void cbox_midi_clip_playback_seek_samples(struct cbox_midi_clip_playback
*pb
, int time_samples
)
295 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
))
297 pb
->rel_time_samples
= time_samples
;
298 pb
->min_time_ppqn
= 0;
302 /////////////////////////////////////////////////////////////////////////////////////////////////////
304 void cbox_midi_playback_active_notes_init(struct cbox_midi_playback_active_notes
*notes
)
306 notes
->channels_active
= 0;
309 void cbox_midi_playback_active_notes_copy(struct cbox_midi_playback_active_notes
*dest
, const struct cbox_midi_playback_active_notes
*src
)
311 dest
->channels_active
= src
->channels_active
;
312 memcpy(dest
->notes
, src
->notes
, sizeof(dest
->notes
));
315 int cbox_midi_playback_active_notes_release(struct cbox_midi_playback_active_notes
*notes
, struct cbox_midi_buffer
*buf
)
317 if (!notes
->channels_active
)
320 for (int c
= 0; c
< 16; c
++)
322 if (!(notes
->channels_active
& (1 << c
)))
325 for (int g
= 0; g
< 4; g
++)
327 uint32_t group
= notes
->notes
[c
][g
];
330 for (int i
= 0; i
< 32; i
++)
333 if (!(group
& (1 << i
)))
335 if (!cbox_midi_buffer_can_store_msg(buf
, 3))
337 cbox_midi_buffer_write_inline(buf
, cbox_midi_buffer_get_last_event_time(buf
), 0x80 + c
, n
, 0);
339 notes
->notes
[c
][g
] = group
;
343 // all Note Offs emitted without buffer overflow - channel is no longer active
344 notes
->channels_active
&= ~(1 << c
);
349 /////////////////////////////////////////////////////////////////////////////////////////////////////
351 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
)
353 struct cbox_song_playback
*spb
= calloc(1, sizeof(struct cbox_song_playback
));
354 if (old_state
&& old_state
->song
!= song
)
357 spb
->engine
= engine
;
358 spb
->pattern_map
= g_hash_table_new_full(NULL
, NULL
, NULL
, (GDestroyNotify
)cbox_midi_pattern_playback_destroy
);
359 spb
->master
= master
;
360 spb
->track_count
= g_list_length(song
->tracks
);
361 spb
->tracks
= malloc(spb
->track_count
* sizeof(struct cbox_track_playback
*));
362 spb
->song_pos_samples
= 0;
363 spb
->song_pos_ppqn
= 0;
364 spb
->min_time_ppqn
= 0;
365 spb
->loop_start_ppqn
= song
->loop_start_ppqn
;
366 spb
->loop_end_ppqn
= song
->loop_end_ppqn
;
367 cbox_midi_merger_init(&spb
->track_merger
, NULL
);
369 for (GList
*p
= song
->tracks
; p
!= NULL
; p
= g_list_next(p
))
371 struct cbox_track
*trk
= p
->data
;
372 struct cbox_track_playback
*old_trk
= NULL
;
373 if (old_state
&& old_state
->track_count
)
375 for (int i
= 0; i
< old_state
->track_count
; i
++)
377 if (old_state
->tracks
[i
]->track
== trk
)
379 old_trk
= old_state
->tracks
[i
];
384 spb
->tracks
[pos
++] = cbox_track_playback_new_from_track(trk
, spb
->master
, spb
, old_trk
);
385 if (!trk
->external_output_set
)
386 cbox_midi_merger_connect(&spb
->track_merger
, &spb
->tracks
[pos
- 1]->output_buffer
, NULL
);
389 spb
->tempo_map_item_count
= g_list_length(song
->master_track_items
);
390 spb
->tempo_map_items
= malloc(spb
->tempo_map_item_count
* sizeof(struct cbox_tempo_map_item
));
394 double tempo
= master
->tempo
;
395 int timesig_nom
= master
->timesig_nom
;
396 int timesig_denom
= master
->timesig_denom
;
397 for (GList
*p
= song
->master_track_items
; p
!= NULL
; p
= g_list_next(p
))
399 struct cbox_master_track_item
*mti
= p
->data
;
402 if (mti
->timesig_nom
> 0)
403 timesig_nom
= mti
->timesig_nom
;
404 if (mti
->timesig_denom
> 0)
405 timesig_denom
= mti
->timesig_denom
;
406 struct cbox_tempo_map_item
*tmi
= &spb
->tempo_map_items
[pos
];
407 tmi
->time_ppqn
= pos_ppqn
;
408 tmi
->time_samples
= pos_samples
;
410 tmi
->timesig_nom
= timesig_nom
;
411 tmi
->timesig_denom
= timesig_denom
;
413 pos_ppqn
+= mti
->duration_ppqn
;
414 pos_samples
+= spb
->master
->srate
* 60.0 * mti
->duration_ppqn
/ (tempo
* master
->ppqn_factor
);
420 void cbox_song_playback_apply_old_state(struct cbox_song_playback
*spb
)
422 for (int i
= 0; i
< spb
->track_count
; i
++)
424 struct cbox_track_playback
*tpb
= spb
->tracks
[i
];
427 cbox_midi_playback_active_notes_copy(&tpb
->active_notes
, &tpb
->old_state
->active_notes
);
428 tpb
->old_state
->state_copied
= TRUE
;
429 tpb
->old_state
= NULL
;
435 static void cbox_song_playback_set_tempo(struct cbox_song_playback
*spb
, double tempo
)
437 int ppos
= spb
->song_pos_ppqn
;
438 int pos1
= cbox_master_ppqn_to_samples(spb
->master
, ppos
);
439 int pos2
= cbox_master_ppqn_to_samples(spb
->master
, ppos
+ 1);
442 relpos
= (spb
->song_pos_samples
- pos1
) * 1.0 / (pos2
- pos1
);
443 spb
->master
->tempo
= tempo
;
445 // This seek loses the fractional value of the PPQN song position.
446 // This needs to be compensated for by shifting the playback
447 // position by the fractional part.
448 cbox_song_playback_seek_ppqn(spb
, ppos
, spb
->min_time_ppqn
);
451 pos2
= cbox_master_ppqn_to_samples(spb
->master
, ppos
+ 1);
452 cbox_song_playback_seek_samples(spb
, spb
->song_pos_samples
+ (pos2
- spb
->song_pos_samples
) * relpos
+ 0.5);
456 int cbox_song_playback_get_next_tempo_change(struct cbox_song_playback
*spb
)
458 double new_tempo
= 0;
459 // Skip items at or already past the playback pointer
460 while (spb
->tempo_map_pos
+ 1 < spb
->tempo_map_item_count
&&
461 spb
->song_pos_samples
>= spb
->tempo_map_items
[spb
->tempo_map_pos
+ 1].time_samples
)
463 new_tempo
= spb
->tempo_map_items
[spb
->tempo_map_pos
+ 1].tempo
;
464 spb
->tempo_map_pos
++;
466 if (new_tempo
!= 0.0 && new_tempo
!= spb
->master
->tempo
)
467 cbox_song_playback_set_tempo(spb
, new_tempo
);
470 if (spb
->tempo_map_pos
+ 1 >= spb
->tempo_map_item_count
)
473 return spb
->tempo_map_items
[spb
->tempo_map_pos
+ 1].time_samples
;
476 void cbox_song_playback_render(struct cbox_song_playback
*spb
, struct cbox_midi_buffer
*output
, int nsamples
)
478 cbox_midi_buffer_clear(output
);
480 if (spb
->master
->new_tempo
!= 0 && spb
->master
->new_tempo
!= spb
->master
->tempo
)
482 cbox_song_playback_set_tempo(spb
, spb
->master
->new_tempo
);
483 spb
->master
->new_tempo
= 0;
485 for(int i
= 0; i
< spb
->track_count
; i
++)
487 cbox_midi_buffer_clear(&spb
->tracks
[i
]->output_buffer
);
489 if (spb
->master
->state
== CMTS_STOPPING
)
491 if (cbox_song_playback_active_notes_release(spb
, output
) > 0)
492 spb
->master
->state
= CMTS_STOP
;
495 if (spb
->master
->state
== CMTS_ROLLING
)
497 int end_samples
= cbox_master_ppqn_to_samples(spb
->master
, spb
->loop_end_ppqn
);
500 while (rpos
< nsamples
)
504 // 1. Shorten the period so that it doesn't go past a tempo change
505 int tmpos
= cbox_song_playback_get_next_tempo_change(spb
);
508 // Number of samples until the next tempo change
509 int stntc
= tmpos
- spb
->song_pos_samples
;
510 if (rend
- rpos
> stntc
)
514 // 2. Shorten the period so that it doesn't go past the song length
515 int end_pos
= spb
->song_pos_samples
+ (rend
- rpos
);
516 if (end_pos
>= end_samples
)
518 rend
= end_samples
- spb
->song_pos_samples
;
519 end_pos
= end_samples
;
524 for (int i
= 0; i
< spb
->track_count
; i
++)
525 cbox_track_playback_render(spb
->tracks
[i
], rpos
, rend
- rpos
);
528 if (end_pos
< end_samples
)
530 spb
->song_pos_samples
+= rend
- rpos
;
532 spb
->min_time_ppqn
= cbox_master_samples_to_ppqn(spb
->master
, spb
->song_pos_samples
- 1) + 1;
533 spb
->song_pos_ppqn
= cbox_master_samples_to_ppqn(spb
->master
, spb
->song_pos_samples
);
537 if (spb
->loop_start_ppqn
>= spb
->loop_end_ppqn
)
539 spb
->song_pos_samples
= end_samples
;
540 spb
->song_pos_ppqn
= spb
->loop_end_ppqn
;
541 spb
->master
->state
= CMTS_STOPPING
;
545 cbox_song_playback_seek_ppqn(spb
, spb
->loop_start_ppqn
, spb
->loop_start_ppqn
);
549 cbox_midi_merger_render_to(&spb
->track_merger
, output
);
553 int cbox_song_playback_active_notes_release(struct cbox_song_playback
*spb
, struct cbox_midi_buffer
*buf
)
555 for(int i
= 0; i
< spb
->track_count
; i
++)
557 struct cbox_track_playback
*trk
= spb
->tracks
[i
];
558 if (trk
->state_copied
)
560 struct cbox_midi_buffer
*output
= trk
->external_merger
? &trk
->output_buffer
: buf
;
561 if (cbox_midi_playback_active_notes_release(&trk
->active_notes
, output
) < 0)
567 void cbox_song_playback_seek_ppqn(struct cbox_song_playback
*spb
, int time_ppqn
, int min_time_ppqn
)
569 for(int i
= 0; i
< spb
->track_count
; i
++)
571 struct cbox_track_playback
*trk
= spb
->tracks
[i
];
572 cbox_track_playback_seek_ppqn(trk
, time_ppqn
, min_time_ppqn
);
574 spb
->song_pos_samples
= cbox_master_ppqn_to_samples(spb
->master
, time_ppqn
);
575 spb
->song_pos_ppqn
= time_ppqn
;
576 spb
->min_time_ppqn
= min_time_ppqn
;
577 spb
->tempo_map_pos
= cbox_song_playback_tmi_from_ppqn(spb
, time_ppqn
);
580 void cbox_song_playback_seek_samples(struct cbox_song_playback
*spb
, int time_samples
)
582 for(int i
= 0; i
< spb
->track_count
; i
++)
584 struct cbox_track_playback
*trk
= spb
->tracks
[i
];
585 cbox_track_playback_seek_samples(trk
, time_samples
);
587 spb
->song_pos_samples
= time_samples
;
588 spb
->song_pos_ppqn
= cbox_master_samples_to_ppqn(spb
->master
, time_samples
);
589 spb
->min_time_ppqn
= spb
->song_pos_ppqn
;
590 spb
->tempo_map_pos
= cbox_song_playback_tmi_from_samples(spb
, time_samples
);
593 int cbox_song_playback_tmi_from_ppqn(struct cbox_song_playback
*spb
, int time_ppqn
)
595 if (!spb
->tempo_map_item_count
)
597 assert(spb
->tempo_map_items
[0].time_samples
== 0);
598 assert(spb
->tempo_map_items
[0].time_ppqn
== 0);
599 // XXXKF should use binary search here really
600 for (int i
= 1; i
< spb
->tempo_map_item_count
; i
++)
602 if (time_ppqn
< spb
->tempo_map_items
[i
].time_ppqn
)
605 return spb
->tempo_map_item_count
- 1;
608 int cbox_song_playback_tmi_from_samples(struct cbox_song_playback
*spb
, int time_samples
)
610 if (!spb
->tempo_map_item_count
)
612 assert(spb
->tempo_map_items
[0].time_samples
== 0);
613 assert(spb
->tempo_map_items
[0].time_ppqn
== 0);
614 // XXXKF should use binary search here really
615 for (int i
= 1; i
< spb
->tempo_map_item_count
; i
++)
617 if (time_samples
< spb
->tempo_map_items
[i
].time_samples
)
620 return spb
->tempo_map_item_count
- 1;
623 struct cbox_midi_pattern_playback
*cbox_midi_pattern_playback_new(struct cbox_midi_pattern
*pattern
)
625 struct cbox_midi_pattern_playback
*mppb
= calloc(1, sizeof(struct cbox_midi_pattern_playback
));
626 mppb
->events
= malloc(sizeof(struct cbox_midi_event
) * pattern
->event_count
);
627 memcpy(mppb
->events
, pattern
->events
, sizeof(struct cbox_midi_event
) * pattern
->event_count
);
628 mppb
->event_count
= pattern
->event_count
;
632 struct cbox_midi_pattern_playback
*cbox_song_playback_get_pattern(struct cbox_song_playback
*spb
, struct cbox_midi_pattern
*pattern
)
634 struct cbox_midi_pattern_playback
*mppb
= g_hash_table_lookup(spb
->pattern_map
, pattern
);
638 mppb
= cbox_midi_pattern_playback_new(pattern
);
639 g_hash_table_insert(spb
->pattern_map
, pattern
, mppb
);
644 void cbox_song_playback_destroy(struct cbox_song_playback
*spb
)
646 for (int i
= 0; i
< spb
->track_count
; i
++)
648 cbox_track_playback_destroy(spb
->tracks
[i
]);
650 free(spb
->tempo_map_items
);
652 g_hash_table_destroy(spb
->pattern_map
);
653 cbox_midi_merger_close(&spb
->track_merger
);