2009-02-11 12:38:40 -05:00
|
|
|
/*-
|
|
|
|
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
|
|
|
|
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
|
|
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
|
|
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \file
|
|
|
|
*
|
|
|
|
* Tempo map related part.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "smf.h"
|
|
|
|
#include "smf_private.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If there is tempo starting at "pulses" already, return it. Otherwise,
|
|
|
|
* allocate new one, fill it with values from previous one (or default ones,
|
|
|
|
* if there is no previous one) and attach it to "smf".
|
|
|
|
*/
|
|
|
|
static smf_tempo_t *
|
2009-02-14 17:22:55 -05:00
|
|
|
new_tempo(smf_t *smf, size_t pulses)
|
2009-02-11 12:38:40 -05:00
|
|
|
{
|
|
|
|
smf_tempo_t *tempo, *previous_tempo = NULL;
|
|
|
|
|
|
|
|
if (smf->tempo_array->len > 0) {
|
|
|
|
previous_tempo = smf_get_last_tempo(smf);
|
|
|
|
|
|
|
|
/* If previous tempo starts at the same time as new one, reuse it, updating in place. */
|
|
|
|
if (previous_tempo->time_pulses == pulses)
|
|
|
|
return (previous_tempo);
|
|
|
|
}
|
|
|
|
|
2013-07-23 02:47:10 -04:00
|
|
|
tempo = (smf_tempo_t*)malloc(sizeof(smf_tempo_t));
|
2009-02-11 12:38:40 -05:00
|
|
|
if (tempo == NULL) {
|
2018-07-05 13:33:46 -04:00
|
|
|
g_warning("Cannot allocate smf_tempo_t.");
|
2009-02-11 12:38:40 -05:00
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
tempo->time_pulses = pulses;
|
|
|
|
|
|
|
|
if (previous_tempo != NULL) {
|
|
|
|
tempo->microseconds_per_quarter_note = previous_tempo->microseconds_per_quarter_note;
|
|
|
|
tempo->numerator = previous_tempo->numerator;
|
|
|
|
tempo->denominator = previous_tempo->denominator;
|
|
|
|
tempo->clocks_per_click = previous_tempo->clocks_per_click;
|
|
|
|
tempo->notes_per_note = previous_tempo->notes_per_note;
|
|
|
|
} else {
|
|
|
|
tempo->microseconds_per_quarter_note = 500000; /* Initial tempo is 120 BPM. */
|
|
|
|
tempo->numerator = 4;
|
|
|
|
tempo->denominator = 4;
|
2020-04-21 23:16:39 -04:00
|
|
|
tempo->clocks_per_click = 24;
|
|
|
|
tempo->notes_per_note = 8;
|
2009-02-11 12:38:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
g_ptr_array_add(smf->tempo_array, tempo);
|
|
|
|
|
|
|
|
return (tempo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
add_tempo(smf_t *smf, int pulses, int tempo)
|
|
|
|
{
|
|
|
|
smf_tempo_t *smf_tempo = new_tempo(smf, pulses);
|
|
|
|
if (smf_tempo == NULL)
|
|
|
|
return (-1);
|
|
|
|
|
|
|
|
smf_tempo->microseconds_per_quarter_note = tempo;
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
add_time_signature(smf_t *smf, int pulses, int numerator, int denominator, int clocks_per_click, int notes_per_note)
|
|
|
|
{
|
|
|
|
smf_tempo_t *smf_tempo = new_tempo(smf, pulses);
|
|
|
|
if (smf_tempo == NULL)
|
|
|
|
return (-1);
|
|
|
|
|
|
|
|
smf_tempo->numerator = numerator;
|
|
|
|
smf_tempo->denominator = denominator;
|
|
|
|
smf_tempo->clocks_per_click = clocks_per_click;
|
|
|
|
smf_tempo->notes_per_note = notes_per_note;
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \internal
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
maybe_add_to_tempo_map(smf_event_t *event)
|
|
|
|
{
|
|
|
|
if (!smf_event_is_metadata(event))
|
|
|
|
return;
|
|
|
|
|
|
|
|
assert(event->track != NULL);
|
|
|
|
assert(event->track->smf != NULL);
|
|
|
|
assert(event->midi_buffer_length >= 1);
|
|
|
|
|
|
|
|
/* Tempo Change? */
|
|
|
|
if (event->midi_buffer[1] == 0x51) {
|
2020-07-16 12:00:40 -04:00
|
|
|
if (event->midi_buffer_length < 6) {
|
|
|
|
g_warning("Ignoring incomplete tempo change event.");
|
|
|
|
return;
|
|
|
|
}
|
2013-11-04 21:32:41 -05:00
|
|
|
int ntempo = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5];
|
|
|
|
if (ntempo <= 0) {
|
2018-07-05 13:33:46 -04:00
|
|
|
g_warning("Ignoring invalid tempo change.");
|
2009-02-11 12:38:40 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-04 21:32:41 -05:00
|
|
|
add_tempo(event->track->smf, event->time_pulses, ntempo);
|
2009-02-11 12:38:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Time Signature? */
|
|
|
|
if (event->midi_buffer[1] == 0x58) {
|
|
|
|
int numerator, denominator, clocks_per_click, notes_per_note;
|
|
|
|
|
|
|
|
if (event->midi_buffer_length < 7) {
|
2018-07-05 13:33:46 -04:00
|
|
|
g_warning("Time Signature event seems truncated.");
|
2009-02-11 12:38:40 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
numerator = event->midi_buffer[3];
|
2013-07-23 02:47:10 -04:00
|
|
|
denominator = (int)pow((double)2, event->midi_buffer[4]);
|
2009-02-11 12:38:40 -05:00
|
|
|
clocks_per_click = event->midi_buffer[5];
|
|
|
|
notes_per_note = event->midi_buffer[6];
|
|
|
|
|
|
|
|
add_time_signature(event->track->smf, event->time_pulses, numerator, denominator, clocks_per_click, notes_per_note);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* This is an internal function, called from smf_track_remove_event when tempo-related
|
|
|
|
* event being removed does not require recreation of tempo map, i.e. there are no events
|
|
|
|
* after that one.
|
|
|
|
*/
|
|
|
|
void
|
2009-02-14 17:22:55 -05:00
|
|
|
remove_last_tempo_with_pulses(smf_t *smf, size_t pulses)
|
2009-02-11 12:38:40 -05:00
|
|
|
{
|
|
|
|
smf_tempo_t *tempo;
|
|
|
|
|
|
|
|
/* XXX: This is a partial workaround for the following problem: we have two tempo-related
|
|
|
|
events, A and B, that occur at the same time. We remove B, then try to remove
|
|
|
|
A. However, both tempo changes got coalesced in new_tempo(), so it is impossible
|
|
|
|
to remove B. */
|
|
|
|
if (smf->tempo_array->len == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
tempo = smf_get_last_tempo(smf);
|
|
|
|
|
|
|
|
/* Workaround part two. */
|
|
|
|
if (tempo->time_pulses != pulses)
|
|
|
|
return;
|
|
|
|
|
|
|
|
memset(tempo, 0, sizeof(smf_tempo_t));
|
|
|
|
free(tempo);
|
|
|
|
|
|
|
|
g_ptr_array_remove_index(smf->tempo_array, smf->tempo_array->len - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Computes value of event->time_seconds for all events in smf.
|
|
|
|
* Warning: rewinds the smf.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
smf_create_tempo_map_and_compute_seconds(smf_t *smf)
|
|
|
|
{
|
|
|
|
smf_event_t *event;
|
|
|
|
|
|
|
|
smf_rewind(smf);
|
|
|
|
smf_init_tempo(smf);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
event = smf_get_next_event(smf);
|
2015-10-05 10:17:49 -04:00
|
|
|
|
2009-02-11 12:38:40 -05:00
|
|
|
if (event == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
maybe_add_to_tempo_map(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Not reached. */
|
|
|
|
}
|
|
|
|
|
2016-12-16 05:43:41 -05:00
|
|
|
int
|
|
|
|
smf_get_tempo_count (const smf_t *smf)
|
|
|
|
{
|
|
|
|
if (!smf->tempo_array) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return smf->tempo_array->len;
|
|
|
|
}
|
|
|
|
|
2009-02-11 12:38:40 -05:00
|
|
|
smf_tempo_t *
|
2009-02-14 17:22:55 -05:00
|
|
|
smf_get_tempo_by_number(const smf_t *smf, size_t number)
|
2009-02-11 12:38:40 -05:00
|
|
|
{
|
|
|
|
if (number >= smf->tempo_array->len)
|
|
|
|
return (NULL);
|
|
|
|
|
2013-07-23 02:47:10 -04:00
|
|
|
return ((smf_tempo_t*)g_ptr_array_index(smf->tempo_array, number));
|
2009-02-11 12:38:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return last tempo (i.e. tempo with greatest time_pulses) that happens before "pulses".
|
|
|
|
*/
|
|
|
|
smf_tempo_t *
|
2009-02-14 17:22:55 -05:00
|
|
|
smf_get_tempo_by_pulses(const smf_t *smf, size_t pulses)
|
2009-02-11 12:38:40 -05:00
|
|
|
{
|
2009-02-14 17:22:55 -05:00
|
|
|
size_t i;
|
2009-02-11 12:38:40 -05:00
|
|
|
smf_tempo_t *tempo;
|
|
|
|
|
|
|
|
if (pulses == 0)
|
|
|
|
return (smf_get_tempo_by_number(smf, 0));
|
|
|
|
|
|
|
|
assert(smf->tempo_array != NULL);
|
2015-10-05 10:17:49 -04:00
|
|
|
|
2009-02-14 17:22:55 -05:00
|
|
|
for (i = smf->tempo_array->len; i > 0; i--) {
|
|
|
|
tempo = smf_get_tempo_by_number(smf, i - 1);
|
2009-02-11 12:38:40 -05:00
|
|
|
|
|
|
|
assert(tempo);
|
|
|
|
if (tempo->time_pulses < pulses)
|
|
|
|
return (tempo);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return last tempo.
|
2020-07-16 12:34:36 -04:00
|
|
|
*/
|
2009-02-11 12:38:40 -05:00
|
|
|
smf_tempo_t *
|
|
|
|
smf_get_last_tempo(const smf_t *smf)
|
|
|
|
{
|
|
|
|
smf_tempo_t *tempo;
|
|
|
|
|
2009-02-14 17:22:55 -05:00
|
|
|
assert(smf->tempo_array->len > 0);
|
2009-02-11 12:38:40 -05:00
|
|
|
tempo = smf_get_tempo_by_number(smf, smf->tempo_array->len - 1);
|
|
|
|
assert(tempo);
|
|
|
|
|
|
|
|
return (tempo);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-10-04 14:51:05 -04:00
|
|
|
* \internal
|
2009-02-11 12:38:40 -05:00
|
|
|
*
|
|
|
|
* Remove all smf_tempo_t structures from SMF.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
smf_fini_tempo(smf_t *smf)
|
|
|
|
{
|
|
|
|
smf_tempo_t *tempo;
|
|
|
|
|
|
|
|
while (smf->tempo_array->len > 0) {
|
2013-07-23 02:47:10 -04:00
|
|
|
tempo = (smf_tempo_t*)g_ptr_array_index(smf->tempo_array, smf->tempo_array->len - 1);
|
2009-02-11 12:38:40 -05:00
|
|
|
assert(tempo);
|
|
|
|
|
|
|
|
memset(tempo, 0, sizeof(smf_tempo_t));
|
|
|
|
free(tempo);
|
|
|
|
|
|
|
|
g_ptr_array_remove_index(smf->tempo_array, smf->tempo_array->len - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(smf->tempo_array->len == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Remove any existing tempos and add default one.
|
|
|
|
*
|
2018-07-05 13:33:46 -04:00
|
|
|
* \bug This will abort (by calling g_warning) if new_tempo() (memory allocation there) fails.
|
2009-02-11 12:38:40 -05:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
smf_init_tempo(smf_t *smf)
|
|
|
|
{
|
|
|
|
smf_tempo_t *tempo;
|
|
|
|
|
|
|
|
smf_fini_tempo(smf);
|
|
|
|
|
|
|
|
tempo = new_tempo(smf, 0);
|
2009-02-14 17:22:55 -05:00
|
|
|
if (tempo == NULL) {
|
2018-07-05 13:33:46 -04:00
|
|
|
g_warning("tempo_init failed, sorry.");
|
2009-02-14 17:22:55 -05:00
|
|
|
}
|
2009-02-11 12:38:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns ->time_pulses of last event on the given track, or 0, if track is empty.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
last_event_pulses(const smf_track_t *track)
|
|
|
|
{
|
|
|
|
/* Get time of last event on this track. */
|
|
|
|
if (track->number_of_events > 0) {
|
|
|
|
smf_event_t *previous_event = smf_track_get_last_event(track);
|
|
|
|
assert(previous_event);
|
|
|
|
|
|
|
|
return (previous_event->time_pulses);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds event to the track at the time "pulses" clocks from the previous event in this track.
|
|
|
|
* The remaining two time fields will be computed automatically based on the third argument
|
|
|
|
* and current tempo map. Note that ->delta_pulses is computed by smf.c:smf_track_add_event,
|
|
|
|
* not here.
|
|
|
|
*/
|
|
|
|
void
|
2009-02-14 17:22:55 -05:00
|
|
|
smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, uint32_t delta)
|
2009-02-11 12:38:40 -05:00
|
|
|
{
|
|
|
|
assert(track->smf != NULL);
|
|
|
|
|
2009-02-14 15:52:15 -05:00
|
|
|
if (!smf_event_is_valid(event)) {
|
2018-07-05 13:33:46 -04:00
|
|
|
g_warning("Added event is invalid");
|
2009-02-14 15:52:15 -05:00
|
|
|
}
|
|
|
|
|
2009-02-11 12:38:40 -05:00
|
|
|
smf_track_add_event_pulses(track, event, last_event_pulses(track) + delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds event to the track at the time "pulses" clocks from the start of song.
|
|
|
|
* The remaining two time fields will be computed automatically based on the third argument
|
|
|
|
* and current tempo map.
|
|
|
|
*/
|
|
|
|
void
|
2009-02-14 17:22:55 -05:00
|
|
|
smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, size_t pulses)
|
2009-02-11 12:38:40 -05:00
|
|
|
{
|
|
|
|
assert(track->smf != NULL);
|
|
|
|
|
|
|
|
event->time_pulses = pulses;
|
|
|
|
smf_track_add_event(track, event);
|
|
|
|
}
|