smflite

diff src/smf_save.c @ 0:4264abea8b06

smf-lite initial commit
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 26 Jan 2012 11:25:11 +0200
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/smf_save.c	Thu Jan 26 11:25:11 2012 +0200
     1.3 @@ -0,0 +1,660 @@
     1.4 +/*-
     1.5 + * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa <trasz@FreeBSD.org>
     1.6 + * All rights reserved.
     1.7 + *
     1.8 + * Redistribution and use in source and binary forms, with or without
     1.9 + * modification, are permitted provided that the following conditions
    1.10 + * are met:
    1.11 + * 1. Redistributions of source code must retain the above copyright
    1.12 + *    notice, this list of conditions and the following disclaimer.
    1.13 + * 2. Redistributions in binary form must reproduce the above copyright
    1.14 + *    notice, this list of conditions and the following disclaimer in the
    1.15 + *    documentation and/or other materials provided with the distribution.
    1.16 + *
    1.17 + * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
    1.18 + * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
    1.19 + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
    1.20 + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    1.21 + * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.22 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
    1.23 + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
    1.24 + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
    1.25 + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    1.26 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    1.27 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.28 + *
    1.29 + */
    1.30 +
    1.31 +/**
    1.32 + * \file
    1.33 + *
    1.34 + * Standard MIDI File writer.
    1.35 + *
    1.36 + */
    1.37 +
    1.38 +/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
    1.39 +
    1.40 +#include <stdlib.h>
    1.41 +#include <string.h>
    1.42 +#include <assert.h>
    1.43 +#include <math.h>
    1.44 +#include <errno.h>
    1.45 +#ifdef __MINGW32__
    1.46 +#include <windows.h>
    1.47 +#else /* ! __MINGW32__ */
    1.48 +#include <arpa/inet.h>
    1.49 +#endif /* ! __MINGW32__ */
    1.50 +#include "smf.h"
    1.51 +#include "smf_private.h"
    1.52 +
    1.53 +#define MAX_VLQ_LENGTH 128
    1.54 +
    1.55 +/**
    1.56 + * Extends (reallocates) smf->file_buffer and returns pointer to the newly added space,
    1.57 + * that is, pointer to the first byte after the previous buffer end.  Returns NULL in case
    1.58 + * of error.
    1.59 + */
    1.60 +static void *
    1.61 +smf_extend(smf_t *smf, const int length)
    1.62 +{
    1.63 +	int i, previous_file_buffer_length = smf->file_buffer_length;
    1.64 +	char *previous_file_buffer = smf->file_buffer;
    1.65 +
    1.66 +	/* XXX: Not terribly efficient. */
    1.67 +	smf->file_buffer_length += length;
    1.68 +	smf->file_buffer = realloc(smf->file_buffer, smf->file_buffer_length);
    1.69 +	if (smf->file_buffer == NULL) {
    1.70 +		fg_critical("realloc(3) failed: %s", strerror(errno));
    1.71 +		smf->file_buffer_length = 0;
    1.72 +		return (NULL);
    1.73 +	}
    1.74 +
    1.75 +	/* Fix up pointers.  XXX: omgwtf. */
    1.76 +	for (i = 1; i <= smf->number_of_tracks; i++) {
    1.77 +		smf_track_t *track;
    1.78 +		track = smf_get_track_by_number(smf, i);
    1.79 +		if (track->file_buffer != NULL)
    1.80 +			track->file_buffer = (char *)track->file_buffer + ((char *)smf->file_buffer - previous_file_buffer);
    1.81 +	}
    1.82 +
    1.83 +	return ((char *)smf->file_buffer + previous_file_buffer_length);
    1.84 +}
    1.85 +
    1.86 +/**
    1.87 + * Appends "buffer_length" bytes pointed to by "buffer" to the smf, reallocating storage as needed.  Returns 0
    1.88 + * if everything went ok, different value if there was any problem.
    1.89 + */
    1.90 +static int
    1.91 +smf_append(smf_t *smf, const void *buffer, const int buffer_length)
    1.92 +{
    1.93 +	void *dest;
    1.94 +
    1.95 +	dest = smf_extend(smf, buffer_length);
    1.96 +	if (dest == NULL) {
    1.97 +		fg_critical("Cannot extend track buffer.");
    1.98 +		return (-1);
    1.99 +	}
   1.100 +
   1.101 +	memcpy(dest, buffer, buffer_length);
   1.102 +
   1.103 +	return (0);
   1.104 +}
   1.105 +
   1.106 +/**
   1.107 + * Appends MThd header to the track.  Returns 0 if everything went ok, different value if not.
   1.108 + */
   1.109 +static int
   1.110 +write_mthd_header(smf_t *smf)
   1.111 +{
   1.112 +	struct mthd_chunk_struct mthd_chunk;
   1.113 +
   1.114 +	memcpy(mthd_chunk.mthd_header.id, "MThd", 4);
   1.115 +	mthd_chunk.mthd_header.length = htonl(6);
   1.116 +	mthd_chunk.format = htons(smf->format);
   1.117 +	mthd_chunk.number_of_tracks = htons(smf->number_of_tracks);
   1.118 +	mthd_chunk.division = htons(smf->ppqn);
   1.119 +
   1.120 +	return (smf_append(smf, &mthd_chunk, sizeof(mthd_chunk)));
   1.121 +}
   1.122 +
   1.123 +/**
   1.124 + * Extends (reallocates) track->file_buffer and returns pointer to the newly added space,
   1.125 + * that is, pointer to the first byte after the previous buffer end.  Returns NULL in case
   1.126 + * of error.
   1.127 + */
   1.128 +static void *
   1.129 +track_extend(smf_track_t *track, const int length)
   1.130 +{
   1.131 +	void *buf;
   1.132 +
   1.133 +	assert(track->smf);
   1.134 +
   1.135 +	buf = smf_extend(track->smf, length);
   1.136 +	if (buf == NULL)
   1.137 +		return (NULL);
   1.138 +
   1.139 +	track->file_buffer_length += length;
   1.140 +	if (track->file_buffer == NULL)
   1.141 +		track->file_buffer = buf;
   1.142 +
   1.143 +	return (buf);
   1.144 +}
   1.145 +
   1.146 +/**
   1.147 + * Appends "buffer_length" bytes pointed to by "buffer" to the track, reallocating storage as needed.  Returns 0
   1.148 + * if everything went ok, different value if there was any problem.
   1.149 + */
   1.150 +static int
   1.151 +track_append(smf_track_t *track, const void *buffer, const int buffer_length)
   1.152 +{
   1.153 +	void *dest;
   1.154 +
   1.155 +	dest = track_extend(track, buffer_length);
   1.156 +	if (dest == NULL) {
   1.157 +		fg_critical("Cannot extend track buffer.");
   1.158 +		return (-1);
   1.159 +	}
   1.160 +
   1.161 +	memcpy(dest, buffer, buffer_length);
   1.162 +
   1.163 +	return (0);
   1.164 +}
   1.165 +
   1.166 +static int
   1.167 +format_vlq(unsigned char *buf, int length, unsigned long value)
   1.168 +{
   1.169 +	int i;
   1.170 +	unsigned long buffer;
   1.171 +
   1.172 +	/* Taken from http://www.borg.com/~jglatt/tech/midifile/vari.htm */
   1.173 +	buffer = value & 0x7F;
   1.174 +
   1.175 +	while ((value >>= 7)) {
   1.176 +		buffer <<= 8;
   1.177 +		buffer |= ((value & 0x7F) | 0x80);
   1.178 +	}
   1.179 +
   1.180 +	for (i = 0;; i++) {
   1.181 +		buf[i] = buffer;
   1.182 +
   1.183 +		if (buffer & 0x80)
   1.184 +			buffer >>= 8;
   1.185 +		else
   1.186 +			break;
   1.187 +	}
   1.188 +
   1.189 +	assert(i <= length);
   1.190 +
   1.191 +	/* + 1, because "i" is an offset, not a count. */
   1.192 +	return (i + 1);
   1.193 +}
   1.194 +
   1.195 +smf_event_t *
   1.196 +smf_event_new_textual(int type, const char *text)
   1.197 +{
   1.198 +	int vlq_length, text_length, copied_length;
   1.199 +	smf_event_t *event;
   1.200 +
   1.201 +	assert(type >= 1 && type <= 9);
   1.202 +
   1.203 +	text_length = strlen(text);
   1.204 +
   1.205 +	event = smf_event_new();
   1.206 +	if (event == NULL)
   1.207 +		return (NULL);
   1.208 +
   1.209 +	/* "2 +" is for leading 0xFF 0xtype. */
   1.210 +	event->midi_buffer_length = 2 + text_length + MAX_VLQ_LENGTH;
   1.211 +	event->midi_buffer = malloc(event->midi_buffer_length);
   1.212 +	if (event->midi_buffer == NULL) {
   1.213 +		fg_critical("Cannot allocate MIDI buffer structure: %s", strerror(errno));
   1.214 +		smf_event_delete(event);
   1.215 +
   1.216 +		return (NULL); 
   1.217 +	}
   1.218 +
   1.219 +	event->midi_buffer[0] = 0xFF;
   1.220 +	event->midi_buffer[1] = type;
   1.221 +
   1.222 +	vlq_length = format_vlq(event->midi_buffer + 2, MAX_VLQ_LENGTH - 2, text_length);
   1.223 +	copied_length = snprintf((char *)event->midi_buffer + vlq_length + 2, event->midi_buffer_length - vlq_length - 2, "%s", text);
   1.224 +
   1.225 +	assert(copied_length == text_length);
   1.226 +
   1.227 +	event->midi_buffer_length = 2 + vlq_length + text_length;
   1.228 +
   1.229 +	return event;
   1.230 +}
   1.231 +
   1.232 +/**
   1.233 +  * Appends value, expressed as Variable Length Quantity, to event->track.
   1.234 +  */
   1.235 +static int
   1.236 +write_vlq(smf_event_t *event, unsigned long value)
   1.237 +{
   1.238 +	unsigned char buf[MAX_VLQ_LENGTH];
   1.239 +	int vlq_length;
   1.240 +
   1.241 +	vlq_length = format_vlq(buf, MAX_VLQ_LENGTH, value);
   1.242 +
   1.243 +	return (track_append(event->track, buf, vlq_length));
   1.244 +}
   1.245 +
   1.246 +/**
   1.247 + * Appends event time as Variable Length Quantity.  Returns 0 if everything went ok,
   1.248 + * different value in case of error.
   1.249 + */
   1.250 +static int
   1.251 +write_event_time(smf_event_t *event)
   1.252 +{
   1.253 +	assert(event->delta_time_pulses >= 0);
   1.254 +
   1.255 +	return (write_vlq(event, event->delta_time_pulses));
   1.256 +}
   1.257 +
   1.258 +static int
   1.259 +write_sysex_contents(smf_event_t *event)
   1.260 +{
   1.261 +	int ret;
   1.262 +	unsigned char sysex_status = 0xF0;
   1.263 +
   1.264 +	assert(smf_event_is_sysex(event));
   1.265 +
   1.266 +	ret = track_append(event->track, &sysex_status, 1);
   1.267 +	if (ret)
   1.268 +		return (ret);
   1.269 +
   1.270 +	/* -1, because length does not include status byte. */
   1.271 +	ret = write_vlq(event, event->midi_buffer_length - 1);
   1.272 +	if (ret)
   1.273 +		return (ret);
   1.274 +
   1.275 +	ret = track_append(event->track, event->midi_buffer + 1, event->midi_buffer_length - 1);
   1.276 +	if (ret)
   1.277 +		return (ret);
   1.278 +
   1.279 +	return (0);
   1.280 +}
   1.281 +
   1.282 +/**
   1.283 +  * Appends contents of event->midi_buffer wrapped into 0xF7 MIDI event.
   1.284 +  */
   1.285 +static int
   1.286 +write_escaped_event_contents(smf_event_t *event)
   1.287 +{
   1.288 +	int ret;
   1.289 +	unsigned char escape_status = 0xF7;
   1.290 +
   1.291 +	if (smf_event_is_sysex(event))
   1.292 +		return (write_sysex_contents(event));
   1.293 +
   1.294 +	ret = track_append(event->track, &escape_status, 1);
   1.295 +	if (ret)
   1.296 +		return (ret);
   1.297 +
   1.298 +	ret = write_vlq(event, event->midi_buffer_length);
   1.299 +	if (ret)
   1.300 +		return (ret);
   1.301 +
   1.302 +	ret = track_append(event->track, event->midi_buffer, event->midi_buffer_length);
   1.303 +	if (ret)
   1.304 +		return (ret);
   1.305 +
   1.306 +	return (0);
   1.307 +}
   1.308 +
   1.309 +/**
   1.310 + * Appends contents of event->midi_buffer.  Returns 0 if everything went 0,
   1.311 + * different value in case of error.
   1.312 + */
   1.313 +static int
   1.314 +write_event_contents(smf_event_t *event)
   1.315 +{
   1.316 +	if (smf_event_is_system_realtime(event) || smf_event_is_system_common(event))
   1.317 +		return (write_escaped_event_contents(event));
   1.318 +
   1.319 +	return (track_append(event->track, event->midi_buffer, event->midi_buffer_length));
   1.320 +}
   1.321 +
   1.322 +/**
   1.323 + * Writes out an event.
   1.324 + */
   1.325 +static int
   1.326 +write_event(smf_event_t *event)
   1.327 +{
   1.328 +	int ret;
   1.329 +
   1.330 +	ret = write_event_time(event);
   1.331 +	if (ret)
   1.332 +		return (ret);
   1.333 +
   1.334 +	ret = write_event_contents(event);
   1.335 +	if (ret)
   1.336 +		return (ret);
   1.337 +
   1.338 +	return (0);
   1.339 +}
   1.340 +
   1.341 +/**
   1.342 + * Writes out MTrk header, except of MTrk chunk length, which is written by write_mtrk_length().
   1.343 + */
   1.344 +static int
   1.345 +write_mtrk_header(smf_track_t *track)
   1.346 +{
   1.347 +	struct chunk_header_struct mtrk_header;
   1.348 +
   1.349 +	memcpy(mtrk_header.id, "MTrk", 4);
   1.350 +
   1.351 +	return (track_append(track, &mtrk_header, sizeof(mtrk_header)));
   1.352 +}
   1.353 +
   1.354 +/**
   1.355 + * Updates MTrk chunk length of a given track.
   1.356 + */
   1.357 +static int
   1.358 +write_mtrk_length(smf_track_t *track)
   1.359 +{
   1.360 +	struct chunk_header_struct *mtrk_header;
   1.361 +
   1.362 +	assert(track->file_buffer != NULL);
   1.363 +	assert(track->file_buffer_length >= 6);
   1.364 +
   1.365 +	mtrk_header = (struct chunk_header_struct *)track->file_buffer;
   1.366 +	mtrk_header->length = htonl(track->file_buffer_length - sizeof(struct chunk_header_struct));
   1.367 +
   1.368 +	return (0);
   1.369 +}
   1.370 +
   1.371 +/**
   1.372 + * Writes out the track.
   1.373 + */
   1.374 +static int
   1.375 +write_track(smf_track_t *track)
   1.376 +{
   1.377 +	int ret;
   1.378 +	smf_event_t *event;
   1.379 +
   1.380 +	ret = write_mtrk_header(track);
   1.381 +	if (ret)
   1.382 +		return (ret);
   1.383 +
   1.384 +	while ((event = smf_track_get_next_event(track)) != NULL) {
   1.385 +		ret = write_event(event);
   1.386 +		if (ret)
   1.387 +			return (ret);
   1.388 +	}
   1.389 +
   1.390 +	ret = write_mtrk_length(track);
   1.391 +	if (ret)
   1.392 +		return (ret);
   1.393 +
   1.394 +	return (0);
   1.395 +}
   1.396 +
   1.397 +/**
   1.398 + * Takes smf->file_buffer and saves it to the file.
   1.399 + */
   1.400 +static int
   1.401 +write_file(smf_t *smf, const char *file_name)
   1.402 +{
   1.403 +	FILE *stream;
   1.404 +
   1.405 +	stream = fopen(file_name, "wb+");
   1.406 +	if (stream == NULL) {
   1.407 +		fg_critical("Cannot open input file: %s", strerror(errno));
   1.408 +
   1.409 +		return (-1);
   1.410 +	}
   1.411 +
   1.412 +	if (fwrite(smf->file_buffer, 1, smf->file_buffer_length, stream) != smf->file_buffer_length) {
   1.413 +		fg_critical("fwrite(3) failed: %s", strerror(errno));
   1.414 +
   1.415 +		return (-2);
   1.416 +	}
   1.417 +
   1.418 +	if (fclose(stream)) {
   1.419 +		fg_critical("fclose(3) failed: %s", strerror(errno));
   1.420 +
   1.421 +		return (-3);
   1.422 +	}
   1.423 +
   1.424 +	return (0);
   1.425 +}
   1.426 +
   1.427 +static void
   1.428 +free_buffer(smf_t *smf)
   1.429 +{
   1.430 +	int i;
   1.431 +	smf_track_t *track;
   1.432 +
   1.433 +	/* Clear the pointers. */
   1.434 +	memset(smf->file_buffer, 0, smf->file_buffer_length);
   1.435 +	free(smf->file_buffer);
   1.436 +	smf->file_buffer = NULL;
   1.437 +	smf->file_buffer_length = 0;
   1.438 +
   1.439 +	for (i = 1; i <= smf->number_of_tracks; i++) {
   1.440 +		track = smf_get_track_by_number(smf, i);
   1.441 +		assert(track);
   1.442 +		track->file_buffer = NULL;
   1.443 +		track->file_buffer_length = 0;
   1.444 +	}
   1.445 +}
   1.446 +
   1.447 +#ifndef NDEBUG
   1.448 +
   1.449 +/**
   1.450 + * \return Nonzero, if all pointers supposed to be NULL are NULL.  Triggers assertion if not.
   1.451 + */
   1.452 +static int
   1.453 +pointers_are_clear(smf_t *smf)
   1.454 +{
   1.455 +	int i;
   1.456 +
   1.457 +	smf_track_t *track;
   1.458 +	assert(smf->file_buffer == NULL);
   1.459 +	assert(smf->file_buffer_length == 0);
   1.460 +
   1.461 +	for (i = 1; i <= smf->number_of_tracks; i++) {
   1.462 +		track = smf_get_track_by_number(smf, i);
   1.463 +
   1.464 +		assert(track != NULL);
   1.465 +		assert(track->file_buffer == NULL);
   1.466 +		assert(track->file_buffer_length == 0);
   1.467 +	}
   1.468 +
   1.469 +	return (1);
   1.470 +}
   1.471 +
   1.472 +#endif /* !NDEBUG */
   1.473 +
   1.474 +/**
   1.475 + * \return Nonzero, if event is End Of Track metaevent.
   1.476 + */
   1.477 +int
   1.478 +smf_event_is_eot(const smf_event_t *event)
   1.479 +{
   1.480 +	if (event->midi_buffer_length != 3)
   1.481 +		return (0);
   1.482 +
   1.483 +	if (event->midi_buffer[0] != 0xFF || event->midi_buffer[1] != 0x2F || event->midi_buffer[2] != 0x00)
   1.484 +		return (0);
   1.485 +
   1.486 +	return (1);
   1.487 +}
   1.488 +
   1.489 +/**
   1.490 + * Check if SMF is valid and add missing EOT events.
   1.491 + *
   1.492 + * \return 0, if SMF is valid.
   1.493 + */
   1.494 +static int
   1.495 +smf_validate(smf_t *smf)
   1.496 +{
   1.497 +	int trackno, eventno, eot_found;
   1.498 +	smf_track_t *track;
   1.499 +	smf_event_t *event;
   1.500 +
   1.501 +	if (smf->format < 0 || smf->format > 2) {
   1.502 +		fg_critical("SMF error: smf->format is less than zero of greater than two.");
   1.503 +		return (-1);
   1.504 +	}
   1.505 +
   1.506 +	if (smf->number_of_tracks < 1) {
   1.507 +		fg_critical("SMF error: number of tracks is less than one.");
   1.508 +		return (-2);
   1.509 +	}
   1.510 +
   1.511 +	if (smf->format == 0 && smf->number_of_tracks > 1) {
   1.512 +		fg_critical("SMF error: format is 0, but number of tracks is more than one.");
   1.513 +		return (-3);
   1.514 +	}
   1.515 +
   1.516 +	if (smf->ppqn <= 0) {
   1.517 +		fg_critical("SMF error: PPQN has to be > 0.");
   1.518 +		return (-4);
   1.519 +	}
   1.520 +
   1.521 +	for (trackno = 1; trackno <= smf->number_of_tracks; trackno++) {
   1.522 +		track = smf_get_track_by_number(smf, trackno);
   1.523 +		assert(track);
   1.524 +
   1.525 +		eot_found = 0;
   1.526 +
   1.527 +		for (eventno = 1; eventno <= track->number_of_events; eventno++) {
   1.528 +			event = smf_track_get_event_by_number(track, eventno);
   1.529 +			assert(event);
   1.530 +
   1.531 +			if (!smf_event_is_valid(event)) {
   1.532 +				fg_critical("Event #%d on track #%d is invalid.", eventno, trackno);
   1.533 +				return (-5);
   1.534 +			}
   1.535 +
   1.536 +			if (smf_event_is_eot(event)) {
   1.537 +				if (eot_found) {
   1.538 +					fg_critical("Duplicate End Of Track event on track #%d.", trackno);
   1.539 +					return (-6);
   1.540 +				}
   1.541 +
   1.542 +				eot_found = 1;
   1.543 +			}
   1.544 +		}
   1.545 +
   1.546 +		if (!eot_found) {
   1.547 +			if (smf_track_add_eot_delta_pulses(track, 0)) {
   1.548 +				fg_critical("smf_track_add_eot_delta_pulses failed.");
   1.549 +				return (-6);
   1.550 +			}
   1.551 +		}
   1.552 +
   1.553 +	}
   1.554 +
   1.555 +	return (0);
   1.556 +}
   1.557 +
   1.558 +#ifndef NDEBUG
   1.559 +
   1.560 +static void
   1.561 +assert_smf_event_is_identical(const smf_event_t *a, const smf_event_t *b)
   1.562 +{
   1.563 +	assert(a->event_number == b->event_number);
   1.564 +	assert(a->delta_time_pulses == b->delta_time_pulses);
   1.565 +	assert(abs(a->time_pulses - b->time_pulses) <= 2);
   1.566 +	assert(fabs(a->time_seconds - b->time_seconds) <= 0.01);
   1.567 +	assert(a->track_number == b->track_number);
   1.568 +	assert(a->midi_buffer_length == b->midi_buffer_length);
   1.569 +	assert(memcmp(a->midi_buffer, b->midi_buffer, a->midi_buffer_length) == 0);
   1.570 +}
   1.571 +
   1.572 +static void
   1.573 +assert_smf_track_is_identical(const smf_track_t *a, const smf_track_t *b)
   1.574 +{
   1.575 +	int i;
   1.576 +
   1.577 +	assert(a->track_number == b->track_number);
   1.578 +	assert(a->number_of_events == b->number_of_events);
   1.579 +
   1.580 +	for (i = 1; i <= a->number_of_events; i++)
   1.581 +		assert_smf_event_is_identical(smf_track_get_event_by_number(a, i), smf_track_get_event_by_number(b, i));
   1.582 +}
   1.583 +
   1.584 +static void
   1.585 +assert_smf_is_identical(const smf_t *a, const smf_t *b)
   1.586 +{
   1.587 +	int i;
   1.588 +
   1.589 +	assert(a->format == b->format);
   1.590 +	assert(a->ppqn == b->ppqn);
   1.591 +	assert(a->frames_per_second == b->frames_per_second);
   1.592 +	assert(a->resolution == b->resolution);
   1.593 +	assert(a->number_of_tracks == b->number_of_tracks);
   1.594 +
   1.595 +	for (i = 1; i <= a->number_of_tracks; i++)
   1.596 +		assert_smf_track_is_identical(smf_get_track_by_number(a, i), smf_get_track_by_number(b, i));
   1.597 +
   1.598 +	/* We do not need to compare tempos explicitly, as tempo is always computed from track contents. */
   1.599 +}
   1.600 +
   1.601 +static void
   1.602 +assert_smf_saved_correctly(const smf_t *smf, const char *file_name)
   1.603 +{
   1.604 +	smf_t *saved;
   1.605 +
   1.606 +	saved = smf_load(file_name);
   1.607 +	assert(saved != NULL);
   1.608 +
   1.609 +	assert_smf_is_identical(smf, saved);
   1.610 +
   1.611 +	smf_delete(saved);
   1.612 +}
   1.613 +
   1.614 +#endif /* !NDEBUG */
   1.615 +
   1.616 +/**
   1.617 +  * Writes the contents of SMF to the file given.
   1.618 +  * \param smf SMF.
   1.619 +  * \param file_name Path to the file.
   1.620 +  * \return 0, if saving was successfull.
   1.621 +  */
   1.622 +int
   1.623 +smf_save(smf_t *smf, const char *file_name)
   1.624 +{
   1.625 +	int i, error;
   1.626 +	smf_track_t *track;
   1.627 +
   1.628 +	smf_rewind(smf);
   1.629 +
   1.630 +	assert(pointers_are_clear(smf));
   1.631 +
   1.632 +	if (smf_validate(smf))
   1.633 +		return (-1);
   1.634 +
   1.635 +	if (write_mthd_header(smf))
   1.636 +		return (-2);
   1.637 +
   1.638 +	for (i = 1; i <= smf->number_of_tracks; i++) {
   1.639 +		track = smf_get_track_by_number(smf, i);
   1.640 +
   1.641 +		assert(track != NULL);
   1.642 +
   1.643 +		error = write_track(track);
   1.644 +		if (error) {
   1.645 +			free_buffer(smf);
   1.646 +			return (error);
   1.647 +		}
   1.648 +	}
   1.649 +
   1.650 +	error = write_file(smf, file_name);
   1.651 +
   1.652 +	free_buffer(smf);
   1.653 +
   1.654 +	if (error)
   1.655 +		return (error);
   1.656 +
   1.657 +#ifndef NDEBUG
   1.658 +	assert_smf_saved_correctly(smf, file_name);
   1.659 +#endif
   1.660 +
   1.661 +	return (0);
   1.662 +}
   1.663 +