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