smflite

diff src/smf.h @ 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.h	Thu Jan 26 11:25:11 2012 +0200
     1.3 @@ -0,0 +1,417 @@
     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 + * Public interface declaration for libsmf, Standard MIDI File format library.
    1.35 + */
    1.36 +
    1.37 +/**
    1.38 + *
    1.39 + * \mainpage libsmf - general usage instructions
    1.40 + *
    1.41 + * An smf_t structure represents a "song".  Every valid smf contains one or more tracks.
    1.42 + * Tracks contain zero or more events.  Libsmf doesn't care about actual MIDI data, as long
    1.43 + * as it is valid from the MIDI specification point of view - it may be realtime message,
    1.44 + * SysEx, whatever.
    1.45 + * 
    1.46 + * The only field in smf_t, smf_track_t, smf_event_t and smf_tempo_t structures your
    1.47 + * code may modify is event->midi_buffer and event->midi_buffer_length.  Do not modify
    1.48 + * other fields, _ever_.  You may read them, though.  Do not declare static instances
    1.49 + * of these types, i.e. never do something like this:  "smf_t smf;".  Always use
    1.50 + * "smf_t *smf = smf_new();".  The same applies to smf_track_t and smf_event_t.
    1.51 + * 
    1.52 + * Say you want to load a Standard MIDI File (.mid) file and play it back somehow.  This is (roughly)
    1.53 + * how you do this:
    1.54 + * 
    1.55 + * \code
    1.56 + * 	smf_t *smf;
    1.57 + * 	smf_event_t *event;
    1.58 + *
    1.59 + * 	smf = smf_load(file_name);
    1.60 + * 	if (smf == NULL) {
    1.61 + * 		Whoops, something went wrong.
    1.62 + * 		return;
    1.63 + * 	}
    1.64 + * 
    1.65 + * 	while ((event = smf_get_next_event(smf)) != NULL) {
    1.66 + *		if (smf_event_is_metadata(event))
    1.67 + *			continue;
    1.68 + * 
    1.69 + * 		wait until event->time_seconds.
    1.70 + * 		feed_to_midi_output(event->midi_buffer, event->midi_buffer_length);
    1.71 + * 	}
    1.72 + *
    1.73 + *	smf_delete(smf);
    1.74 + *
    1.75 + * \endcode
    1.76 + * 
    1.77 + * Saving works like this:
    1.78 + * 
    1.79 + * \code
    1.80 + *
    1.81 + * 	smf_t *smf;
    1.82 + *	smf_track_t *track;
    1.83 + *	smf_event_t *event;
    1.84 + *
    1.85 + * 	smf = smf_new();
    1.86 + * 	if (smf == NULL) {
    1.87 + * 		Whoops.
    1.88 + * 		return;
    1.89 + * 	}
    1.90 + * 
    1.91 + * 	for (int i = 1; i <= number of tracks; i++) {
    1.92 + * 		track = smf_track_new();
    1.93 + * 		if (track == NULL) {
    1.94 + * 			Whoops.
    1.95 + * 			return;
    1.96 + * 		}
    1.97 + * 
    1.98 + * 		smf_add_track(smf, track);
    1.99 + * 
   1.100 + * 		for (int j = 1; j <= number of events you want to put into this track; j++) {
   1.101 + * 			event = smf_event_new_from_pointer(your MIDI message, message length);
   1.102 + * 			if (event == NULL) {
   1.103 + * 				Whoops.
   1.104 + * 				return;
   1.105 + * 			}
   1.106 + * 
   1.107 + * 			smf_track_add_event_seconds(track, event, seconds since start of the song);
   1.108 + * 		}
   1.109 + * 	}
   1.110 + * 
   1.111 + * 	ret = smf_save(smf, file_name);
   1.112 + * 	if (ret) {
   1.113 + * 		Whoops, saving failed for some reason.
   1.114 + * 		return;
   1.115 + * 	}
   1.116 + *
   1.117 + *	smf_delete(smf);
   1.118 + *
   1.119 + * \endcode
   1.120 + *
   1.121 + * There are two basic ways of getting MIDI data out of smf - sequential or by track/event number.  You may
   1.122 + * mix them if you need to.  First one is used in the example above - seek to the point from which you want
   1.123 + * the playback to start (using smf_seek_to_seconds(), smf_seek_to_pulses() or smf_seek_to_event()) and then
   1.124 + * do smf_get_next_event() in loop, until it returns NULL.  Calling smf_load() causes the smf to be rewound
   1.125 + * to the start of the song.
   1.126 + *
   1.127 + * Getting events by number works like this:
   1.128 + *
   1.129 + * \code
   1.130 + *
   1.131 + * smf_track_t *track = smf_get_track_by_number(smf, track_number);
   1.132 + * smf_event_t *event = smf_track_get_event_by_number(track, event_number);
   1.133 + *
   1.134 + * \endcode
   1.135 + *
   1.136 + * To create new event, use smf_event_new(), smf_event_new_from_pointer() or smf_event_new_from_bytes().
   1.137 + * First one creates an empty event - you need to manually allocate (using malloc(3)) buffer for
   1.138 + * MIDI data, write MIDI data into it, put the address of that buffer into event->midi_buffer,
   1.139 + * and the length of MIDI data into event->midi_buffer_length.  Note that deleting the event
   1.140 + * (using smf_event_delete()) will free the buffer.
   1.141 + *
   1.142 + * Second form does most of this for you: it takes an address of the buffer containing MIDI data,
   1.143 + * allocates storage and copies MIDI data into it.
   1.144 + *
   1.145 + * Third form is useful for manually creating short events, up to three bytes in length, for
   1.146 + * example Note On or Note Off events.  It simply takes three bytes and creates MIDI event containing
   1.147 + * them.  If you need to create MIDI message that takes only two bytes, pass -1 as the third byte.
   1.148 + * For one byte message (System Realtime), pass -1 as second and third byte.
   1.149 + *
   1.150 + * To free an event, use smf_event_delete().
   1.151 + *
   1.152 + * To add event to the track, use smf_track_add_event_delta_pulses(), smf_track_add_event_pulses(),
   1.153 + * or smf_track_add_event_seconds().  The difference between them is in the way you specify the time of
   1.154 + * the event - with the first one, you specify it as an interval, in pulses, from the previous event
   1.155 + * in this track; with the second one, you specify it as pulses from the start of the song, and with the
   1.156 + * last one, you specify it as seconds from the start of the song.  Obviously, the first version can
   1.157 + * only append events at the end of the track.
   1.158 + *
   1.159 + * To remove an event from the track it's attached to, use smf_event_remove_from_track().  You may
   1.160 + * want to free the event (using smf_event_delete()) afterwards.
   1.161 + *
   1.162 + * To create new track, use smf_track_new().  To add track to the smf, use smf_add_track().
   1.163 + * To remove track from its smf, use smf_track_remove_from_smf().  To free the track structure,
   1.164 + * use smf_track_delete().
   1.165 + *
   1.166 + * Note that libsmf keeps things consistent.  If you free (using smf_track_delete()) a track that
   1.167 + * is attached to an smf and contains events, libsmf will detach the events, free them, detach
   1.168 + * the track, free it etc.
   1.169 + *
   1.170 + * Tracks and events are numbered consecutively, starting from one.  If you remove a track or event,
   1.171 + * the rest of tracks/events will get renumbered.  To get the number of a given event in its track, use event->event_number.
   1.172 + * To get the number of track in its smf, use track->track_number.  To get the number of events in the track,
   1.173 + * use track->number_of_events.  To get the number of tracks in the smf, use smf->number_of_tracks.
   1.174 + *
   1.175 + * In SMF File Format, each track has to end with End Of Track metaevent.  If you load SMF file using smf_load(),
   1.176 + * that will be the case.  If you want to create or edit an SMF, you don't need to worry about EOT events;
   1.177 + * libsmf automatically takes care of them for you.  If you try to save an SMF with tracks that do not end
   1.178 + * with EOTs, smf_save() will append them.  If you try to add event that happens after EOT metaevent, libsmf
   1.179 + * will remove the EOT.  If you want to add EOT manually, you can, of course, using smf_track_add_eot_seconds()
   1.180 + * or smf_track_add_eot_pulses().
   1.181 + *
   1.182 + * Each event carries three time values - event->time_seconds, which is seconds since the start of the song,
   1.183 + * event->time_pulses, which is PPQN clocks since the start of the song, and event->delta_pulses, which is PPQN clocks
   1.184 + * since the previous event in that track.  These values are invalid if the event is not attached to the track.
   1.185 + * If event is attached, all three values are valid.  Time of the event is specified when adding the event
   1.186 + * (using smf_track_add_event_seconds(), smf_track_add_event_pulses() or smf_track_add_event_delta_pulses()); the remaining
   1.187 + * two values are computed from that.
   1.188 + *
   1.189 + * Tempo related stuff happens automatically - when you add a metaevent that
   1.190 + * is Tempo Change or Time Signature, libsmf adds that event to the tempo map.  If you remove
   1.191 + * Tempo Change event that is in the middle of the song, the rest of the events will have their
   1.192 + * event->time_seconds recomputed from event->time_pulses before smf_event_remove_from_track() function returns.
   1.193 + * Adding Tempo Change in the middle of the song works in a similar way.
   1.194 + * 	
   1.195 + * MIDI data (event->midi_buffer) is always kept in normalized form - it always begins with status byte
   1.196 + * (no running status), there are no System Realtime events embedded in them etc.  Events like SysExes
   1.197 + * are in "on the wire" form, without embedded length that is used in SMF file format.  Obviously
   1.198 + * libsmf "normalizes" MIDI data during loading and "denormalizes" (adding length to SysExes, escaping
   1.199 + * System Common and System Realtime messages etc) during writing.
   1.200 + *
   1.201 + * Note that you always have to first add the track to smf, and then add events to the track.
   1.202 + * Doing it the other way around will trip asserts.  Also, try to add events at the end of the track and remove
   1.203 + * them from the end of the track, that's much more efficient.
   1.204 + * 
   1.205 + * All the libsmf functions have prefix "smf_".  First argument for routines whose names start with
   1.206 + * "smf_event_" is "smf_event_t *", for routines whose names start with "smf_track_" - "smf_track_t *",
   1.207 + * and for plain "smf_" - "smf_t *".  The only exception are smf_whatever_new routines.
   1.208 + * Library does not use any global variables and is thread-safe,
   1.209 + * as long as you don't try to work on the same SMF (smf_t and its descendant tracks and events) from several
   1.210 + * threads at once without protecting it with mutex.  Library depends on glib and nothing else.  License is
   1.211 + * BSD, two clause, which basically means you can use it freely in your software, both Open Source (including
   1.212 + * GPL) and closed source.
   1.213 + *
   1.214 + */
   1.215 +
   1.216 +#ifndef SMF_H
   1.217 +#define SMF_H
   1.218 +
   1.219 +#ifdef __cplusplus
   1.220 +extern "C" {
   1.221 +#endif
   1.222 +
   1.223 +#include <stdio.h>
   1.224 +
   1.225 +#if defined(__GNUC__) && __GNUC__ >= 4
   1.226 +#define WARN_UNUSED_RESULT __attribute__ ((warn_unused_result))
   1.227 +#else
   1.228 +#define WARN_UNUSED_RESULT
   1.229 +#endif
   1.230 +
   1.231 +struct FakeGPtrArray;
   1.232 +
   1.233 +/** Represents a "song", that is, collection of one or more tracks. */
   1.234 +struct smf_struct {
   1.235 +	int		format;
   1.236 +
   1.237 +	/** These fields are extracted from "division" field of MThd header.  Valid is _either_ ppqn or frames_per_second/resolution. */
   1.238 +	int		ppqn;
   1.239 +	int		frames_per_second;
   1.240 +	int		resolution;
   1.241 +	int		number_of_tracks;
   1.242 +
   1.243 +	/** These are private fields using only by loading and saving routines. */
   1.244 +	FILE		*stream;
   1.245 +	void		*file_buffer;
   1.246 +	int		file_buffer_length;
   1.247 +	int		next_chunk_offset;
   1.248 +	int		expected_number_of_tracks;
   1.249 +
   1.250 +	/** Private, used by smf.c. */
   1.251 +	struct FakeGPtrArray	*tracks_array;
   1.252 +	double		last_seek_position;
   1.253 +
   1.254 +	/** Private, used by smf_tempo.c. */
   1.255 +	/** Array of pointers to smf_tempo_struct. */
   1.256 +	struct FakeGPtrArray	*tempo_array;
   1.257 +};
   1.258 +
   1.259 +typedef struct smf_struct smf_t;
   1.260 +
   1.261 +/** Describes a single tempo or time signature change. */
   1.262 +struct smf_tempo_struct {
   1.263 +	int time_pulses;
   1.264 +	double time_seconds;
   1.265 +	int microseconds_per_quarter_note;
   1.266 +	int numerator;
   1.267 +	int denominator;
   1.268 +	int clocks_per_click;
   1.269 +	int notes_per_note;
   1.270 +};
   1.271 +
   1.272 +typedef struct smf_tempo_struct smf_tempo_t;
   1.273 +
   1.274 +/** Represents a single track. */
   1.275 +struct smf_track_struct {
   1.276 +	smf_t		*smf;
   1.277 +
   1.278 +	int		track_number;
   1.279 +	int		number_of_events;
   1.280 +
   1.281 +	/** These are private fields using only by loading and saving routines. */
   1.282 +	void		*file_buffer;
   1.283 +	int		file_buffer_length;
   1.284 +	int		last_status; /* Used for "running status". */
   1.285 +
   1.286 +	/** Private, used by smf.c. */
   1.287 +	/** Offset into buffer, used in parse_next_event(). */
   1.288 +	int		next_event_offset;
   1.289 +	int		next_event_number;
   1.290 +
   1.291 +	/** Absolute time of next event on events_queue. */
   1.292 +	int		time_of_next_event;
   1.293 +	struct FakeGPtrArray	*events_array;
   1.294 +
   1.295 +	/** API consumer is free to use this for whatever purpose.  NULL in freshly allocated track.
   1.296 +	    Note that tracks might be deallocated not only explicitly, by calling smf_track_delete(),
   1.297 +	    but also implicitly, e.g. when calling smf_delete() with tracks still added to
   1.298 +	    the smf; there is no mechanism for libsmf to notify you about removal of the track. */
   1.299 +	void		*user_pointer;
   1.300 +};
   1.301 +
   1.302 +typedef struct smf_track_struct smf_track_t;
   1.303 +
   1.304 +/** Represents a single MIDI event or metaevent. */
   1.305 +struct smf_event_struct {
   1.306 +	/** Pointer to the track, or NULL if event is not attached. */
   1.307 +	smf_track_t	*track;
   1.308 +
   1.309 +	/** Number of this event in the track.  Events are numbered consecutively, starting from one. */
   1.310 +	int		event_number;
   1.311 +
   1.312 +	/** Note that the time fields are invalid, if event is not attached to a track. */
   1.313 +	/** Time, in pulses, since the previous event on this track. */
   1.314 +	int		delta_time_pulses;
   1.315 +
   1.316 +	/** Time, in pulses, since the start of the song. */
   1.317 +	int		time_pulses;
   1.318 +
   1.319 +	/** Time, in seconds, since the start of the song. */
   1.320 +	double		time_seconds;
   1.321 +
   1.322 +	/** Tracks are numbered consecutively, starting from 1. */
   1.323 +	int		track_number;
   1.324 +
   1.325 +	/** Pointer to the buffer containing MIDI message.  This is freed by smf_event_delete. */
   1.326 +	unsigned char	*midi_buffer;
   1.327 +
   1.328 +	/** Length of the MIDI message in the buffer, in bytes. */
   1.329 +	int		midi_buffer_length; 
   1.330 +
   1.331 +	/** API consumer is free to use this for whatever purpose.  NULL in freshly allocated event.
   1.332 +	    Note that events might be deallocated not only explicitly, by calling smf_event_delete(),
   1.333 +	    but also implicitly, e.g. when calling smf_track_delete() with events still added to
   1.334 +	    the track; there is no mechanism for libsmf to notify you about removal of the event. */
   1.335 +	void		*user_pointer;
   1.336 +};
   1.337 +
   1.338 +typedef struct smf_event_struct smf_event_t;
   1.339 +
   1.340 +/* Routines for manipulating smf_t. */
   1.341 +smf_t *smf_new(void) WARN_UNUSED_RESULT;
   1.342 +void smf_delete(smf_t *smf);
   1.343 +
   1.344 +int smf_set_format(smf_t *smf, int format) WARN_UNUSED_RESULT;
   1.345 +int smf_set_ppqn(smf_t *smf, int format) WARN_UNUSED_RESULT;
   1.346 +
   1.347 +char *smf_decode(const smf_t *smf) WARN_UNUSED_RESULT;
   1.348 +
   1.349 +smf_track_t *smf_get_track_by_number(const smf_t *smf, int track_number) WARN_UNUSED_RESULT;
   1.350 +
   1.351 +smf_event_t *smf_peek_next_event(smf_t *smf) WARN_UNUSED_RESULT;
   1.352 +smf_event_t *smf_get_next_event(smf_t *smf) WARN_UNUSED_RESULT;
   1.353 +void smf_skip_next_event(smf_t *smf);
   1.354 +
   1.355 +void smf_rewind(smf_t *smf);
   1.356 +int smf_seek_to_seconds(smf_t *smf, double seconds) WARN_UNUSED_RESULT;
   1.357 +int smf_seek_to_pulses(smf_t *smf, int pulses) WARN_UNUSED_RESULT;
   1.358 +int smf_seek_to_event(smf_t *smf, const smf_event_t *event) WARN_UNUSED_RESULT;
   1.359 +
   1.360 +int smf_get_length_pulses(const smf_t *smf) WARN_UNUSED_RESULT;
   1.361 +double smf_get_length_seconds(const smf_t *smf) WARN_UNUSED_RESULT;
   1.362 +int smf_event_is_last(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.363 +
   1.364 +void smf_add_track(smf_t *smf, smf_track_t *track);
   1.365 +void smf_track_remove_from_smf(smf_track_t *track);
   1.366 +
   1.367 +/* Routines for manipulating smf_track_t. */
   1.368 +smf_track_t *smf_track_new(void) WARN_UNUSED_RESULT;
   1.369 +void smf_track_delete(smf_track_t *track);
   1.370 +
   1.371 +smf_event_t *smf_track_get_next_event(smf_track_t *track) WARN_UNUSED_RESULT;
   1.372 +smf_event_t *smf_track_get_event_by_number(const smf_track_t *track, int event_number) WARN_UNUSED_RESULT;
   1.373 +smf_event_t *smf_track_get_last_event(const smf_track_t *track) WARN_UNUSED_RESULT;
   1.374 +
   1.375 +void smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, int pulses);
   1.376 +void smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, int pulses);
   1.377 +void smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds);
   1.378 +int smf_track_add_eot_delta_pulses(smf_track_t *track, int delta) WARN_UNUSED_RESULT;
   1.379 +int smf_track_add_eot_pulses(smf_track_t *track, int pulses) WARN_UNUSED_RESULT;
   1.380 +int smf_track_add_eot_seconds(smf_track_t *track, double seconds) WARN_UNUSED_RESULT;
   1.381 +void smf_event_remove_from_track(smf_event_t *event);
   1.382 +
   1.383 +/* Routines for manipulating smf_event_t. */
   1.384 +smf_event_t *smf_event_new(void) WARN_UNUSED_RESULT;
   1.385 +smf_event_t *smf_event_new_from_pointer(void *midi_data, int len) WARN_UNUSED_RESULT;
   1.386 +smf_event_t *smf_event_new_from_bytes(int first_byte, int second_byte, int third_byte) WARN_UNUSED_RESULT;
   1.387 +smf_event_t *smf_event_new_textual(int type, const char *text);
   1.388 +void smf_event_delete(smf_event_t *event);
   1.389 +
   1.390 +int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.391 +int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.392 +int smf_event_is_system_realtime(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.393 +int smf_event_is_system_common(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.394 +int smf_event_is_sysex(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.395 +int smf_event_is_eot(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.396 +int smf_event_is_textual(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.397 +char *smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.398 +char *smf_event_extract_text(const smf_event_t *event) WARN_UNUSED_RESULT;
   1.399 +
   1.400 +/* Routines for loading SMF files. */
   1.401 +smf_t *smf_load(const char *file_name) WARN_UNUSED_RESULT;
   1.402 +smf_t *smf_load_from_memory(const void *buffer, const int buffer_length) WARN_UNUSED_RESULT;
   1.403 +
   1.404 +/* Routine for writing SMF files. */
   1.405 +int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT;
   1.406 +
   1.407 +/* Routines for manipulating smf_tempo_t. */
   1.408 +smf_tempo_t *smf_get_tempo_by_pulses(const smf_t *smf, int pulses) WARN_UNUSED_RESULT;
   1.409 +smf_tempo_t *smf_get_tempo_by_seconds(const smf_t *smf, double seconds) WARN_UNUSED_RESULT;
   1.410 +smf_tempo_t *smf_get_tempo_by_number(const smf_t *smf, int number) WARN_UNUSED_RESULT;
   1.411 +smf_tempo_t *smf_get_last_tempo(const smf_t *smf) WARN_UNUSED_RESULT;
   1.412 +
   1.413 +const char *smf_get_version(void) WARN_UNUSED_RESULT;
   1.414 +
   1.415 +#ifdef __cplusplus
   1.416 +}
   1.417 +#endif
   1.418 +
   1.419 +#endif /* SMF_H */
   1.420 +