13
0
livetrax/libs/evoral/libsmf/smf_tempo.c
Robin Gareus c6b87972b1
Remove unused libsmf seconds/time API
This significantly speeds up parsing MIDI files with complex
tempo-maps. e.g. "Black MIDI Trilogy_2.mid" has 24134 Tempo
changes. Prior to this commit parsing that file took over 5 minutes.
now it loads in under one seconds (libsmf only; libardour still
add overhead, and now needs about 30-40 seconds, previously
it took about 10 mins).

The problem was that every call to `smf_track_add_event_pulses()`
calls `seconds_from_pulses()` which calls `smf_get_tempo_by_seconds()`
which iterates over the tempo-map:

  for every midi-event { for ever tempo until that midi-event {..} }

This does not scale to 3.5M events and 24k tempo-changes.
2020-07-16 18:38:03 +02:00

369 lines
9.0 KiB
C

/*-
* 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 *
new_tempo(smf_t *smf, size_t pulses)
{
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);
}
tempo = (smf_tempo_t*)malloc(sizeof(smf_tempo_t));
if (tempo == NULL) {
g_warning("Cannot allocate smf_tempo_t.");
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;
tempo->clocks_per_click = 24;
tempo->notes_per_note = 8;
}
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) {
if (event->midi_buffer_length < 6) {
g_warning("Ignoring incomplete tempo change event.");
return;
}
int ntempo = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5];
if (ntempo <= 0) {
g_warning("Ignoring invalid tempo change.");
return;
}
add_tempo(event->track->smf, event->time_pulses, ntempo);
}
/* Time Signature? */
if (event->midi_buffer[1] == 0x58) {
int numerator, denominator, clocks_per_click, notes_per_note;
if (event->midi_buffer_length < 7) {
g_warning("Time Signature event seems truncated.");
return;
}
numerator = event->midi_buffer[3];
denominator = (int)pow((double)2, event->midi_buffer[4]);
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
remove_last_tempo_with_pulses(smf_t *smf, size_t pulses)
{
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);
if (event == NULL)
return;
maybe_add_to_tempo_map(event);
}
/* Not reached. */
}
int
smf_get_tempo_count (const smf_t *smf)
{
if (!smf->tempo_array) {
return 0;
}
return smf->tempo_array->len;
}
smf_tempo_t *
smf_get_tempo_by_number(const smf_t *smf, size_t number)
{
if (number >= smf->tempo_array->len)
return (NULL);
return ((smf_tempo_t*)g_ptr_array_index(smf->tempo_array, number));
}
/**
* Return last tempo (i.e. tempo with greatest time_pulses) that happens before "pulses".
*/
smf_tempo_t *
smf_get_tempo_by_pulses(const smf_t *smf, size_t pulses)
{
size_t i;
smf_tempo_t *tempo;
if (pulses == 0)
return (smf_get_tempo_by_number(smf, 0));
assert(smf->tempo_array != NULL);
for (i = smf->tempo_array->len; i > 0; i--) {
tempo = smf_get_tempo_by_number(smf, i - 1);
assert(tempo);
if (tempo->time_pulses < pulses)
return (tempo);
}
return (NULL);
}
/**
* Return last tempo.
*/
smf_tempo_t *
smf_get_last_tempo(const smf_t *smf)
{
smf_tempo_t *tempo;
assert(smf->tempo_array->len > 0);
tempo = smf_get_tempo_by_number(smf, smf->tempo_array->len - 1);
assert(tempo);
return (tempo);
}
/**
* \internal
*
* 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) {
tempo = (smf_tempo_t*)g_ptr_array_index(smf->tempo_array, smf->tempo_array->len - 1);
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.
*
* \bug This will abort (by calling g_warning) if new_tempo() (memory allocation there) fails.
*/
void
smf_init_tempo(smf_t *smf)
{
smf_tempo_t *tempo;
smf_fini_tempo(smf);
tempo = new_tempo(smf, 0);
if (tempo == NULL) {
g_warning("tempo_init failed, sorry.");
}
}
/**
* 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
smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, uint32_t delta)
{
assert(track->smf != NULL);
if (!smf_event_is_valid(event)) {
g_warning("Added event is invalid");
}
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
smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, size_t pulses)
{
assert(track->smf != NULL);
event->time_pulses = pulses;
smf_track_add_event(track, event);
}