From f979ef6a144219c2eed967b36352acaa0f55ca23 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 5 Jul 2022 20:24:12 +0000 Subject: [PATCH] Sound font reader TSf --- source/engine/tml.h | 531 ++++++++++++ source/engine/tsf.h | 1898 ++++++++++++++++++++++++++++++++++++++++++ source/engine/util.c | 21 + source/engine/util.h | 7 + 4 files changed, 2457 insertions(+) create mode 100644 source/engine/tml.h create mode 100644 source/engine/tsf.h create mode 100644 source/engine/util.c create mode 100644 source/engine/util.h diff --git a/source/engine/tml.h b/source/engine/tml.h new file mode 100644 index 0000000..cd64994 --- /dev/null +++ b/source/engine/tml.h @@ -0,0 +1,531 @@ +/* TinyMidiLoader - v0.7 - Minimalistic midi parsing library - https://github.com/schellingb/TinySoundFont + no warranty implied; use at your own risk + Do this: + #define TML_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + // i.e. it should look like this: + #include ... + #include ... + #define TML_IMPLEMENTATION + #include "tml.h" + + [OPTIONAL] #define TML_NO_STDIO to remove stdio dependency + [OPTIONAL] #define TML_MALLOC, TML_REALLOC, and TML_FREE to avoid stdlib.h + [OPTIONAL] #define TML_MEMCPY to avoid string.h + + LICENSE (ZLIB) + + Copyright (C) 2017, 2018, 2020 Bernhard Schelling + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#ifndef TML_INCLUDE_TML_INL +#define TML_INCLUDE_TML_INL + +#ifdef __cplusplus +extern "C" { +#endif + +// Define this if you want the API functions to be static +#ifdef TML_STATIC +#define TMLDEF static +#else +#define TMLDEF extern +#endif + +// Channel message type +enum TMLMessageType +{ + TML_NOTE_OFF = 0x80, TML_NOTE_ON = 0x90, TML_KEY_PRESSURE = 0xA0, TML_CONTROL_CHANGE = 0xB0, TML_PROGRAM_CHANGE = 0xC0, TML_CHANNEL_PRESSURE = 0xD0, TML_PITCH_BEND = 0xE0, TML_SET_TEMPO = 0x51 +}; + +// Midi controller numbers +enum TMLController +{ + TML_BANK_SELECT_MSB, TML_MODULATIONWHEEL_MSB, TML_BREATH_MSB, TML_FOOT_MSB = 4, TML_PORTAMENTO_TIME_MSB, TML_DATA_ENTRY_MSB, TML_VOLUME_MSB, + TML_BALANCE_MSB, TML_PAN_MSB = 10, TML_EXPRESSION_MSB, TML_EFFECTS1_MSB, TML_EFFECTS2_MSB, TML_GPC1_MSB = 16, TML_GPC2_MSB, TML_GPC3_MSB, TML_GPC4_MSB, + TML_BANK_SELECT_LSB = 32, TML_MODULATIONWHEEL_LSB, TML_BREATH_LSB, TML_FOOT_LSB = 36, TML_PORTAMENTO_TIME_LSB, TML_DATA_ENTRY_LSB, TML_VOLUME_LSB, + TML_BALANCE_LSB, TML_PAN_LSB = 42, TML_EXPRESSION_LSB, TML_EFFECTS1_LSB, TML_EFFECTS2_LSB, TML_GPC1_LSB = 48, TML_GPC2_LSB, TML_GPC3_LSB, TML_GPC4_LSB, + TML_SUSTAIN_SWITCH = 64, TML_PORTAMENTO_SWITCH, TML_SOSTENUTO_SWITCH, TML_SOFT_PEDAL_SWITCH, TML_LEGATO_SWITCH, TML_HOLD2_SWITCH, + TML_SOUND_CTRL1, TML_SOUND_CTRL2, TML_SOUND_CTRL3, TML_SOUND_CTRL4, TML_SOUND_CTRL5, TML_SOUND_CTRL6, + TML_SOUND_CTRL7, TML_SOUND_CTRL8, TML_SOUND_CTRL9, TML_SOUND_CTRL10, TML_GPC5, TML_GPC6, TML_GPC7, TML_GPC8, + TML_PORTAMENTO_CTRL, TML_FX_REVERB = 91, TML_FX_TREMOLO, TML_FX_CHORUS, TML_FX_CELESTE_DETUNE, TML_FX_PHASER, + TML_DATA_ENTRY_INCR, TML_DATA_ENTRY_DECR, TML_NRPN_LSB, TML_NRPN_MSB, TML_RPN_LSB, TML_RPN_MSB, + TML_ALL_SOUND_OFF = 120, TML_ALL_CTRL_OFF, TML_LOCAL_CONTROL, TML_ALL_NOTES_OFF, TML_OMNI_OFF, TML_OMNI_ON, TML_POLY_OFF, TML_POLY_ON +}; + +// A single MIDI message linked to the next message in time +typedef struct tml_message +{ + // Time of the message in milliseconds + unsigned int time; + + // Type (see TMLMessageType) and channel number + unsigned char type, channel; + + // 2 byte of parameter data based on the type: + // - key, velocity for TML_NOTE_ON and TML_NOTE_OFF messages + // - key, key_pressure for TML_KEY_PRESSURE messages + // - control, control_value for TML_CONTROL_CHANGE messages (see TMLController) + // - program for TML_PROGRAM_CHANGE messages + // - channel_pressure for TML_CHANNEL_PRESSURE messages + // - pitch_bend for TML_PITCH_BEND messages + union + { + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) //nonstandard extension used: nameless struct/union + #elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" //ISO C++ prohibits anonymous structs + #endif + + struct { union { char key, control, program, channel_pressure; }; union { char velocity, key_pressure, control_value; }; }; + struct { unsigned short pitch_bend; }; + + #ifdef _MSC_VER + #pragma warning( pop ) + #elif defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + }; + + // The pointer to the next message in time following this event + struct tml_message* next; +} tml_message; + +// The load functions will return a pointer to a struct tml_message. +// Normally the linked list gets traversed by following the next pointers. +// Make sure to keep the pointer to the first message to free the memory. +// On error the tml_load* functions will return NULL most likely due to an +// invalid MIDI stream (or if the file did not exist in tml_load_filename). + +#ifndef TML_NO_STDIO +// Directly load a MIDI file from a .mid file path +TMLDEF tml_message* tml_load_filename(const char* filename); +#endif + +// Load a MIDI file from a block of memory +TMLDEF tml_message* tml_load_memory(const void* buffer, int size); + +// Get infos about this loaded MIDI file, returns the note count +// NULL can be passed for any output value pointer if not needed. +// used_channels: Will be set to how many channels play notes +// (i.e. 1 if channel 15 is used but no other) +// used_programs: Will be set to how many different programs are used +// total_notes: Will be set to the total number of note on messages +// time_first_note: Will be set to the time of the first note on message +// time_length: Will be set to the total time in milliseconds +TMLDEF int tml_get_info(tml_message* first_message, int* used_channels, int* used_programs, int* total_notes, unsigned int* time_first_note, unsigned int* time_length); + +// Read the tempo (microseconds per quarter note) value from a message with the type TML_SET_TEMPO +TMLDEF int tml_get_tempo_value(tml_message* set_tempo_message); + +// Free all the memory of the linked message list (can also call free() manually) +TMLDEF void tml_free(tml_message* f); + +// Stream structure for the generic loading +struct tml_stream +{ + // Custom data given to the functions as the first parameter + void* data; + + // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes) + int (*read)(void* data, void* ptr, unsigned int size); +}; + +// Generic Midi loading method using the stream structure above +TMLDEF tml_message* tml_load(struct tml_stream* stream); + +// If this library is used together with TinySoundFont, tsf_stream (equivalent to tml_stream) can also be used +struct tsf_stream; +TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream); + +#ifdef __cplusplus +} +#endif + +// end header +// --------------------------------------------------------------------------------------------------------- +#endif //TML_INCLUDE_TML_INL + +#ifdef TML_IMPLEMENTATION + +#if !defined(TML_MALLOC) || !defined(TML_FREE) || !defined(TML_REALLOC) +# include +# define TML_MALLOC malloc +# define TML_FREE free +# define TML_REALLOC realloc +#endif + +#if !defined(TML_MEMCPY) +# include +# define TML_MEMCPY memcpy +#endif + +#ifndef TML_NO_STDIO +# include +#endif + +#define TML_NULL 0 + +////crash on errors and warnings to find broken midi files while debugging +//#define TML_ERROR(msg) *(int*)0 = 0xbad; +//#define TML_WARN(msg) *(int*)0 = 0xf00d; + +////print errors and warnings +//#define TML_ERROR(msg) printf("ERROR: %s\n", msg); +//#define TML_WARN(msg) printf("WARNING: %s\n", msg); + +#ifndef TML_ERROR +#define TML_ERROR(msg) +#endif + +#ifndef TML_WARN +#define TML_WARN(msg) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef TML_NO_STDIO +static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } +TMLDEF tml_message* tml_load_filename(const char* filename) +{ + struct tml_message* res; + struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_stdio_read }; + #if __STDC_WANT_SECURE_LIB__ + FILE* f = TML_NULL; fopen_s(&f, filename, "rb"); + #else + FILE* f = fopen(filename, "rb"); + #endif + if (!f) { TML_ERROR("File not found"); return 0; } + stream.data = f; + res = tml_load(&stream); + fclose(f); + return res; +} +#endif + +struct tml_stream_memory { const char* buffer; unsigned int total, pos; }; +static int tml_stream_memory_read(struct tml_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TML_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; } +TMLDEF struct tml_message* tml_load_memory(const void* buffer, int size) +{ + struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_memory_read }; + struct tml_stream_memory f = { 0, 0, 0 }; + f.buffer = (const char*)buffer; + f.total = size; + stream.data = &f; + return tml_load(&stream); +} + +struct tml_track +{ + unsigned int Idx, End, Ticks; +}; + +struct tml_tempomsg +{ + unsigned int time; + unsigned char type, Tempo[3]; + tml_message* next; +}; + +struct tml_parser +{ + unsigned char *buf, *buf_end; + int last_status, message_array_size, message_count; +}; + +enum TMLSystemType +{ + TML_TEXT = 0x01, TML_COPYRIGHT = 0x02, TML_TRACK_NAME = 0x03, TML_INST_NAME = 0x04, TML_LYRIC = 0x05, TML_MARKER = 0x06, TML_CUE_POINT = 0x07, + TML_EOT = 0x2f, TML_SMPTE_OFFSET = 0x54, TML_TIME_SIGNATURE = 0x58, TML_KEY_SIGNATURE = 0x59, TML_SEQUENCER_EVENT = 0x7f, + TML_SYSEX = 0xf0, TML_TIME_CODE = 0xf1, TML_SONG_POSITION = 0xf2, TML_SONG_SELECT = 0xf3, TML_TUNE_REQUEST = 0xf6, TML_EOX = 0xf7, TML_SYNC = 0xf8, + TML_TICK = 0xf9, TML_START = 0xfa, TML_CONTINUE = 0xfb, TML_STOP = 0xfc, TML_ACTIVE_SENSING = 0xfe, TML_SYSTEM_RESET = 0xff +}; + +static int tml_readbyte(struct tml_parser* p) +{ + return (p->buf == p->buf_end ? -1 : *(p->buf++)); +} + +static int tml_readvariablelength(struct tml_parser* p) +{ + unsigned int res = 0, i = 0; + unsigned char c; + for (; i != 4; i++) + { + if (p->buf == p->buf_end) { TML_WARN("Unexpected end of file"); return -1; } + c = *(p->buf++); + if (c & 0x80) res = ((res | (c & 0x7F)) << 7); + else return (int)(res | c); + } + TML_WARN("Invalid variable length byte count"); return -1; +} + +static int tml_parsemessage(tml_message** f, struct tml_parser* p) +{ + int deltatime = tml_readvariablelength(p), status = tml_readbyte(p); + tml_message* evt; + + if (deltatime & 0xFFF00000) deltatime = 0; //throw away delays that are insanely high for malformatted midis + if (status < 0) { TML_WARN("Unexpected end of file"); return -1; } + if ((status & 0x80) == 0) + { + // Invalid, use same status as before + if ((p->last_status & 0x80) == 0) { TML_WARN("Undefined status and invalid running status"); return -1; } + p->buf--; + status = p->last_status; + } + else p->last_status = status; + + if (p->message_array_size == p->message_count) + { + //start allocated memory size of message array at 64, double each time until 8192, then add 1024 entries until done + p->message_array_size += (!p->message_array_size ? 64 : (p->message_array_size > 4096 ? 1024 : p->message_array_size)); + *f = (tml_message*)TML_REALLOC(*f, p->message_array_size * sizeof(tml_message)); + if (!*f) { TML_ERROR("Out of memory"); return -1; } + } + evt = *f + p->message_count; + + //check what message we have + if ((status == TML_SYSEX) || (status == TML_EOX)) //sysex + { + //sysex messages are not handled + p->buf += tml_readvariablelength(p); + if (p->buf > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; } + evt->type = 0; + } + else if (status == 0xFF) //meta events + { + int meta_type = tml_readbyte(p), buflen = tml_readvariablelength(p); + unsigned char* metadata = p->buf; + if (meta_type < 0) { TML_WARN("Unexpected end of file"); return -1; } + if (buflen > 0 && (p->buf += buflen) > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; } + + switch (meta_type) + { + case TML_EOT: + if (buflen != 0) { TML_WARN("Invalid length for EndOfTrack event"); return -1; } + if (!deltatime) return TML_EOT; //no need to store this message + evt->type = TML_EOT; + break; + + case TML_SET_TEMPO: + if (buflen != 3) { TML_WARN("Invalid length for SetTempo meta event"); return -1; } + evt->type = TML_SET_TEMPO; + ((struct tml_tempomsg*)evt)->Tempo[0] = metadata[0]; + ((struct tml_tempomsg*)evt)->Tempo[1] = metadata[1]; + ((struct tml_tempomsg*)evt)->Tempo[2] = metadata[2]; + break; + + default: + evt->type = 0; + } + } + else //channel message + { + int param; + if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } + evt->key = (param & 0x7f); + evt->channel = (status & 0x0f); + switch (evt->type = (status & 0xf0)) + { + case TML_NOTE_OFF: + case TML_NOTE_ON: + case TML_KEY_PRESSURE: + case TML_CONTROL_CHANGE: + if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } + evt->velocity = (param & 0x7f); + break; + + case TML_PITCH_BEND: + if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } + evt->pitch_bend = ((param & 0x7f) << 7) | evt->key; + break; + + case TML_PROGRAM_CHANGE: + case TML_CHANNEL_PRESSURE: + evt->velocity = 0; + break; + + default: //ignore system/manufacture messages + evt->type = 0; + break; + } + } + + if (deltatime || evt->type) + { + evt->time = deltatime; + p->message_count++; + } + return evt->type; +} + +TMLDEF tml_message* tml_load(struct tml_stream* stream) +{ + int num_tracks, division, trackbufsize = 0; + unsigned char midi_header[14], *trackbuf = TML_NULL; + struct tml_message* messages = TML_NULL; + struct tml_track *tracks, *t, *tracksEnd; + struct tml_parser p = { TML_NULL, TML_NULL, 0, 0, 0 }; + + // Parse MIDI header + if (stream->read(stream->data, midi_header, 14) != 14) { TML_ERROR("Unexpected end of file"); return messages; } + if (midi_header[0] != 'M' || midi_header[1] != 'T' || midi_header[2] != 'h' || midi_header[3] != 'd' || + midi_header[7] != 6 || midi_header[9] > 2) { TML_ERROR("Doesn't look like a MIDI file: invalid MThd header"); return messages; } + if (midi_header[12] & 0x80) { TML_ERROR("File uses unsupported SMPTE timing"); return messages; } + num_tracks = (int)(midi_header[10] << 8) | midi_header[11]; + division = (int)(midi_header[12] << 8) | midi_header[13]; //division is ticks per beat (quarter-note) + if (num_tracks <= 0 && division <= 0) { TML_ERROR("Doesn't look like a MIDI file: invalid track or division values"); return messages; } + + // Allocate temporary tracks array for parsing + tracks = (struct tml_track*)TML_MALLOC(sizeof(struct tml_track) * num_tracks); + tracksEnd = &tracks[num_tracks]; + for (t = tracks; t != tracksEnd; t++) t->Idx = t->End = t->Ticks = 0; + + // Read all messages for all tracks + for (t = tracks; t != tracksEnd; t++) + { + unsigned char track_header[8]; + int track_length; + if (stream->read(stream->data, track_header, 8) != 8) { TML_WARN("Unexpected end of file"); break; } + if (track_header[0] != 'M' || track_header[1] != 'T' || track_header[2] != 'r' || track_header[3] != 'k') + { TML_WARN("Invalid MTrk header"); break; } + + // Get size of track data and read into buffer (allocate bigger buffer if needed) + track_length = track_header[7] | (track_header[6] << 8) | (track_header[5] << 16) | (track_header[4] << 24); + if (track_length < 0) { TML_WARN("Invalid MTrk header"); break; } + if (trackbufsize < track_length) { TML_FREE(trackbuf); trackbuf = (unsigned char*)TML_MALLOC(trackbufsize = track_length); } + if (stream->read(stream->data, trackbuf, track_length) != track_length) { TML_WARN("Unexpected end of file"); break; } + + t->Idx = p.message_count; + for (p.buf_end = (p.buf = trackbuf) + track_length; p.buf != p.buf_end;) + { + int type = tml_parsemessage(&messages, &p); + if (type == TML_EOT || type < 0) break; //file end or illegal data encountered + } + if (p.buf != p.buf_end) { TML_WARN( "Track length did not match data length"); } + t->End = p.message_count; + } + TML_FREE(trackbuf); + + // Change message time signature from delta ticks to actual msec values and link messages ordered by time + if (p.message_count) + { + tml_message *PrevMessage = TML_NULL, *Msg, *MsgEnd, Swap; + unsigned int ticks = 0, tempo_ticks = 0; //tick counter and value at last tempo change + int step_smallest, msec, tempo_msec = 0; //msec value at last tempo change + double ticks2time = 500000 / (1000.0 * division); //milliseconds per tick + + // Loop through all messages over all tracks ordered by time + for (step_smallest = 0; step_smallest != 0x7fffffff; ticks += step_smallest) + { + step_smallest = 0x7fffffff; + msec = tempo_msec + (int)((ticks - tempo_ticks) * ticks2time); + for (t = tracks; t != tracksEnd; t++) + { + if (t->Idx == t->End) continue; + for (Msg = &messages[t->Idx], MsgEnd = &messages[t->End]; Msg != MsgEnd && t->Ticks + Msg->time == ticks; Msg++, t->Idx++) + { + t->Ticks += Msg->time; + if (Msg->type == TML_SET_TEMPO) + { + unsigned char* Tempo = ((struct tml_tempomsg*)Msg)->Tempo; + ticks2time = ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2])/(1000.0 * division); + tempo_msec = msec; + tempo_ticks = ticks; + } + if (Msg->type) + { + Msg->time = msec; + if (PrevMessage) { PrevMessage->next = Msg; PrevMessage = Msg; } + else { Swap = *Msg; *Msg = *messages; *messages = Swap; PrevMessage = messages; } + } + } + if (Msg != MsgEnd && t->Ticks + Msg->time > ticks) + { + int step = (int)(t->Ticks + Msg->time - ticks); + if (step < step_smallest) step_smallest = step; + } + } + } + if (PrevMessage) PrevMessage->next = TML_NULL; + else p.message_count = 0; + } + TML_FREE(tracks); + + if (p.message_count == 0) + { + TML_FREE(messages); + messages = TML_NULL; + } + + return messages; +} + +TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream) +{ + return tml_load((struct tml_stream*)stream); +} + +TMLDEF int tml_get_info(tml_message* Msg, int* out_used_channels, int* out_used_programs, int* out_total_notes, unsigned int* out_time_first_note, unsigned int* out_time_length) +{ + int used_programs = 0, used_channels = 0, total_notes = 0; + unsigned int time_first_note = 0xffffffff, time_length = 0; + unsigned char channels[16] = { 0 }, programs[128] = { 0 }; + for (;Msg; Msg = Msg->next) + { + time_length = Msg->time; + if (Msg->type == TML_PROGRAM_CHANGE && !programs[(int)Msg->program]) { programs[(int)Msg->program] = 1; used_programs++; } + if (Msg->type != TML_NOTE_ON) continue; + if (time_first_note == 0xffffffff) time_first_note = time_length; + if (!channels[Msg->channel]) { channels[Msg->channel] = 1; used_channels++; } + total_notes++; + } + if (time_first_note == 0xffffffff) time_first_note = 0; + if (out_used_channels ) *out_used_channels = used_channels; + if (out_used_programs ) *out_used_programs = used_programs; + if (out_total_notes ) *out_total_notes = total_notes; + if (out_time_first_note) *out_time_first_note = time_first_note; + if (out_time_length ) *out_time_length = time_length; + return total_notes; +} + +TMLDEF int tml_get_tempo_value(tml_message* msg) +{ + unsigned char* Tempo; + if (!msg || msg->type != TML_SET_TEMPO) return 0; + Tempo = ((struct tml_tempomsg*)msg)->Tempo; + return ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2]); +} + +TMLDEF void tml_free(tml_message* f) +{ + TML_FREE(f); +} + +#ifdef __cplusplus +} +#endif + +#endif //TML_IMPLEMENTATION diff --git a/source/engine/tsf.h b/source/engine/tsf.h new file mode 100644 index 0000000..c94ba0d --- /dev/null +++ b/source/engine/tsf.h @@ -0,0 +1,1898 @@ +/* TinySoundFont - v0.9 - SoundFont2 synthesizer - https://github.com/schellingb/TinySoundFont + no warranty implied; use at your own risk + Do this: + #define TSF_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + // i.e. it should look like this: + #include ... + #include ... + #define TSF_IMPLEMENTATION + #include "tsf.h" + + [OPTIONAL] #define TSF_NO_STDIO to remove stdio dependency + [OPTIONAL] #define TSF_MALLOC, TSF_REALLOC, and TSF_FREE to avoid stdlib.h + [OPTIONAL] #define TSF_MEMCPY, TSF_MEMSET to avoid string.h + [OPTIONAL] #define TSF_POW, TSF_POWF, TSF_EXPF, TSF_LOG, TSF_TAN, TSF_LOG10, TSF_SQRT to avoid math.h + + NOT YET IMPLEMENTED + - Support for ChorusEffectsSend and ReverbEffectsSend generators + - Better low-pass filter without lowering performance too much + - Support for modulators + + LICENSE (MIT) + + Copyright (C) 2017, 2018 Bernhard Schelling + Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + + Permission is hereby granted, free of charge, to any person obtaining a copy of this + software and associated documentation files (the "Software"), to deal in the Software + without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef TSF_INCLUDE_TSF_INL +#define TSF_INCLUDE_TSF_INL + +#ifdef __cplusplus +extern "C" { +# define CPP_DEFAULT0 = 0 +#else +# define CPP_DEFAULT0 +#endif + +//define this if you want the API functions to be static +#ifdef TSF_STATIC +#define TSFDEF static +#else +#define TSFDEF extern +#endif + +// The load functions will return a pointer to a struct tsf which all functions +// thereafter take as the first parameter. +// On error the tsf_load* functions will return NULL most likely due to invalid +// data (or if the file did not exist in tsf_load_filename). +typedef struct tsf tsf; + +#ifndef TSF_NO_STDIO +// Directly load a SoundFont from a .sf2 file path +TSFDEF tsf* tsf_load_filename(const char* filename); +#endif + +// Load a SoundFont from a block of memory +TSFDEF tsf* tsf_load_memory(const void* buffer, int size); + +// Stream structure for the generic loading +struct tsf_stream +{ + // Custom data given to the functions as the first parameter + void* data; + + // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes) + int (*read)(void* data, void* ptr, unsigned int size); + + // Function pointer will be called to skip ahead over 'count' bytes (returns 1 on success, 0 on error) + int (*skip)(void* data, unsigned int count); +}; + +// Generic SoundFont loading method using the stream structure above +TSFDEF tsf* tsf_load(struct tsf_stream* stream); + +// Copy a tsf instance from an existing one, use tsf_close to close it as well. +// All copied tsf instances and their original instance are linked, and share the underlying soundfont. +// This allows loading a soundfont only once, but using it for multiple independent playbacks. +// (This function isn't thread-safe without locking.) +TSFDEF tsf* tsf_copy(tsf* f); + +// Free the memory related to this tsf instance +TSFDEF void tsf_close(tsf* f); + +// Stop all playing notes immediately and reset all channel parameters +TSFDEF void tsf_reset(tsf* f); + +// Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont +TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number); + +// Returns the number of presets in the loaded SoundFont +TSFDEF int tsf_get_presetcount(const tsf* f); + +// Returns the name of a preset index >= 0 and < tsf_get_presetcount() +TSFDEF const char* tsf_get_presetname(const tsf* f, int preset_index); + +// Returns the name of a preset by bank and preset number +TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number); + +// Supported output modes by the render methods +enum TSFOutputMode +{ + // Two channels with single left/right samples one after another + TSF_STEREO_INTERLEAVED, + // Two channels with all samples for the left channel first then right + TSF_STEREO_UNWEAVED, + // A single channel (stereo instruments are mixed into center) + TSF_MONO, +}; + +// Thread safety: +// +// 1. Rendering / voices: +// +// Your audio output which calls the tsf_render* functions will most likely +// run on a different thread than where the playback tsf_note* functions +// are called. In which case some sort of concurrency control like a +// mutex needs to be used so they are not called at the same time. +// Alternatively, you can pre-allocate a maximum number of voices that can +// play simultaneously by calling tsf_set_max_voices after loading. +// That way memory re-allocation will not happen during tsf_note_on and +// TSF should become mostly thread safe. +// There is a theoretical chance that ending notes would negatively influence +// a voice that is rendering at the time but it is hard to say. +// Also be aware, this has not been tested much. +// +// 2. Channels: +// +// Calls to tsf_channel_set_... functions may allocate new channels +// if no channel with that number was previously used. Make sure to +// create all channels at the beginning as required if you call tsf_render* +// from a different thread. + +// Setup the parameters for the voice render methods +// outputmode: if mono or stereo and how stereo channel data is ordered +// samplerate: the number of samples per second (output frequency) +// global_gain_db: volume gain in decibels (>0 means higher, <0 means lower) +TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db CPP_DEFAULT0); + +// Set the global gain as a volume factor +// global_gain: the desired volume where 1.0 is 100% +TSFDEF void tsf_set_volume(tsf* f, float global_gain); + +// Set the maximum number of voices to play simultaneously +// Depending on the soundfond, one note can cause many new voices to be started, +// so don't keep this number too low or otherwise sounds may not play. +// max_voices: maximum number to pre-allocate and set the limit to +// (tsf_set_max_voices returns 0 if allocation failed, otherwise 1) +TSFDEF int tsf_set_max_voices(tsf* f, int max_voices); + +// Start playing a note +// preset_index: preset index >= 0 and < tsf_get_presetcount() +// key: note value between 0 and 127 (60 being middle C) +// vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full) +// bank: instrument bank number (alternative to preset_index) +// preset_number: preset number (alternative to preset_index) +// (tsf_note_on returns 0 if the allocation of a new voice failed, otherwise 1) +// (tsf_bank_note_on returns 0 if preset does not exist or allocation failed, otherwise 1) +TSFDEF int tsf_note_on(tsf* f, int preset_index, int key, float vel); +TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel); + +// Stop playing a note +// (bank_note_off returns 0 if preset does not exist, otherwise 1) +TSFDEF void tsf_note_off(tsf* f, int preset_index, int key); +TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key); + +// Stop playing all notes (end with sustain and release) +TSFDEF void tsf_note_off_all(tsf* f); + +// Returns the number of active voices +TSFDEF int tsf_active_voice_count(tsf* f); + +// Render output samples into a buffer +// You can either render as signed 16-bit values (tsf_render_short) or +// as 32-bit float values (tsf_render_float) +// buffer: target buffer of size samples * output_channels * sizeof(type) +// samples: number of samples to render +// flag_mixing: if 0 clear the buffer first, otherwise mix into existing data +TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing CPP_DEFAULT0); +TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing CPP_DEFAULT0); + +// Higher level channel based functions, set up channel parameters +// channel: channel number +// preset_index: preset index >= 0 and < tsf_get_presetcount() +// preset_number: preset number (alternative to preset_index) +// flag_mididrums: 0 for normal channels, otherwise apply MIDI drum channel rules +// bank: instrument bank number (alternative to preset_index) +// pan: stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center) +// volume: linear volume scale factor (default 1.0 full) +// pitch_wheel: pitch wheel position 0 to 16383 (default 8192 unpitched) +// pitch_range: range of the pitch wheel in semitones (default 2.0, total +/- 2 semitones) +// tuning: tuning of all playing voices in semitones (default 0.0, standard (A440) tuning) +// (tsf_set_preset_number and set_bank_preset return 0 if preset does not exist, otherwise 1) +// (tsf_channel_set_... return 0 if a new channel needed allocation and that failed, otherwise 1) +TSFDEF int tsf_channel_set_presetindex(tsf* f, int channel, int preset_index); +TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums CPP_DEFAULT0); +TSFDEF int tsf_channel_set_bank(tsf* f, int channel, int bank); +TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number); +TSFDEF int tsf_channel_set_pan(tsf* f, int channel, float pan); +TSFDEF int tsf_channel_set_volume(tsf* f, int channel, float volume); +TSFDEF int tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel); +TSFDEF int tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range); +TSFDEF int tsf_channel_set_tuning(tsf* f, int channel, float tuning); + +// Start or stop playing notes on a channel (needs channel preset to be set) +// channel: channel number +// key: note value between 0 and 127 (60 being middle C) +// vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full) +// (tsf_channel_note_on returns 0 on allocation failure of new voice, otherwise 1) +TSFDEF int tsf_channel_note_on(tsf* f, int channel, int key, float vel); +TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key); +TSFDEF void tsf_channel_note_off_all(tsf* f, int channel); //end with sustain and release +TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediately + +// Apply a MIDI control change to the channel (not all controllers are supported!) +// (tsf_channel_midi_control returns 0 on allocation failure of new channel, otherwise 1) +TSFDEF int tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value); + +// Get current values set on the channels +TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel); +TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel); +TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel); +TSFDEF float tsf_channel_get_pan(tsf* f, int channel); +TSFDEF float tsf_channel_get_volume(tsf* f, int channel); +TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel); +TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel); +TSFDEF float tsf_channel_get_tuning(tsf* f, int channel); + +#ifdef __cplusplus +# undef CPP_DEFAULT0 +} +#endif + +// end header +// --------------------------------------------------------------------------------------------------------- +#endif //TSF_INCLUDE_TSF_INL + +#ifdef TSF_IMPLEMENTATION +#undef TSF_IMPLEMENTATION + +// The lower this block size is the more accurate the effects are. +// Increasing the value significantly lowers the CPU usage of the voice rendering. +// If LFO affects the low-pass filter it can be hearable even as low as 8. +#ifndef TSF_RENDER_EFFECTSAMPLEBLOCK +#define TSF_RENDER_EFFECTSAMPLEBLOCK 64 +#endif + +// When using tsf_render_short, to do the conversion a buffer of a fixed size is +// allocated on the stack. On low memory platforms this could be made smaller. +// Increasing this above 512 should not have a significant impact on performance. +// The value should be a multiple of TSF_RENDER_EFFECTSAMPLEBLOCK. +#ifndef TSF_RENDER_SHORTBUFFERBLOCK +#define TSF_RENDER_SHORTBUFFERBLOCK 512 +#endif + +// Grace release time for quick voice off (avoid clicking noise) +#define TSF_FASTRELEASETIME 0.01f + +#if !defined(TSF_MALLOC) || !defined(TSF_FREE) || !defined(TSF_REALLOC) +# include +# define TSF_MALLOC malloc +# define TSF_FREE free +# define TSF_REALLOC realloc +#endif + +#if !defined(TSF_MEMCPY) || !defined(TSF_MEMSET) +# include +# define TSF_MEMCPY memcpy +# define TSF_MEMSET memset +#endif + +#if !defined(TSF_POW) || !defined(TSF_POWF) || !defined(TSF_EXPF) || !defined(TSF_LOG) || !defined(TSF_TAN) || !defined(TSF_LOG10) || !defined(TSF_SQRT) +# include +# if !defined(__cplusplus) && !defined(NAN) && !defined(powf) && !defined(expf) && !defined(sqrtf) +# define powf (float)pow // deal with old math.h +# define expf (float)exp // files that come without +# define sqrtf (float)sqrt // powf, expf and sqrtf +# endif +# define TSF_POW pow +# define TSF_POWF powf +# define TSF_EXPF expf +# define TSF_LOG log +# define TSF_TAN tan +# define TSF_LOG10 log10 +# define TSF_SQRTF sqrtf +#endif + +#ifndef TSF_NO_STDIO +# include +#endif + +#define TSF_TRUE 1 +#define TSF_FALSE 0 +#define TSF_BOOL char +#define TSF_PI 3.14159265358979323846264338327950288 +#define TSF_NULL 0 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char tsf_fourcc[4]; +typedef signed char tsf_s8; +typedef unsigned char tsf_u8; +typedef unsigned short tsf_u16; +typedef signed short tsf_s16; +typedef unsigned int tsf_u32; +typedef char tsf_char20[20]; + +#define TSF_FourCCEquals(value1, value2) (value1[0] == value2[0] && value1[1] == value2[1] && value1[2] == value2[2] && value1[3] == value2[3]) + +struct tsf +{ + struct tsf_preset* presets; + float* fontSamples; + struct tsf_voice* voices; + struct tsf_channels* channels; + + int presetNum; + int voiceNum; + int maxVoiceNum; + unsigned int voicePlayIndex; + + enum TSFOutputMode outputmode; + float outSampleRate; + float globalGainDB; + int* refCount; +}; + +#ifndef TSF_NO_STDIO +static int tsf_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } +static int tsf_stream_stdio_skip(FILE* f, unsigned int count) { return !fseek(f, count, SEEK_CUR); } +TSFDEF tsf* tsf_load_filename(const char* filename) +{ + tsf* res; + struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_stdio_read, (int(*)(void*,unsigned int))&tsf_stream_stdio_skip }; + #if __STDC_WANT_SECURE_LIB__ + FILE* f = TSF_NULL; fopen_s(&f, filename, "rb"); + #else + FILE* f = fopen(filename, "rb"); + #endif + if (!f) + { + //if (e) *e = TSF_FILENOTFOUND; + return TSF_NULL; + } + stream.data = f; + res = tsf_load(&stream); + fclose(f); + return res; +} +#endif + +struct tsf_stream_memory { const char* buffer; unsigned int total, pos; }; +static int tsf_stream_memory_read(struct tsf_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TSF_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; } +static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) return 0; m->pos += count; return 1; } +TSFDEF tsf* tsf_load_memory(const void* buffer, int size) +{ + struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_memory_read, (int(*)(void*,unsigned int))&tsf_stream_memory_skip }; + struct tsf_stream_memory f = { 0, 0, 0 }; + f.buffer = (const char*)buffer; + f.total = size; + stream.data = &f; + return tsf_load(&stream); +} + +enum { TSF_LOOPMODE_NONE, TSF_LOOPMODE_CONTINUOUS, TSF_LOOPMODE_SUSTAIN }; + +enum { TSF_SEGMENT_NONE, TSF_SEGMENT_DELAY, TSF_SEGMENT_ATTACK, TSF_SEGMENT_HOLD, TSF_SEGMENT_DECAY, TSF_SEGMENT_SUSTAIN, TSF_SEGMENT_RELEASE, TSF_SEGMENT_DONE }; + +struct tsf_hydra +{ + struct tsf_hydra_phdr *phdrs; struct tsf_hydra_pbag *pbags; struct tsf_hydra_pmod *pmods; + struct tsf_hydra_pgen *pgens; struct tsf_hydra_inst *insts; struct tsf_hydra_ibag *ibags; + struct tsf_hydra_imod *imods; struct tsf_hydra_igen *igens; struct tsf_hydra_shdr *shdrs; + int phdrNum, pbagNum, pmodNum, pgenNum, instNum, ibagNum, imodNum, igenNum, shdrNum; +}; + +union tsf_hydra_genamount { struct { tsf_u8 lo, hi; } range; tsf_s16 shortAmount; tsf_u16 wordAmount; }; +struct tsf_hydra_phdr { tsf_char20 presetName; tsf_u16 preset, bank, presetBagNdx; tsf_u32 library, genre, morphology; }; +struct tsf_hydra_pbag { tsf_u16 genNdx, modNdx; }; +struct tsf_hydra_pmod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; }; +struct tsf_hydra_pgen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; }; +struct tsf_hydra_inst { tsf_char20 instName; tsf_u16 instBagNdx; }; +struct tsf_hydra_ibag { tsf_u16 instGenNdx, instModNdx; }; +struct tsf_hydra_imod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; }; +struct tsf_hydra_igen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; }; +struct tsf_hydra_shdr { tsf_char20 sampleName; tsf_u32 start, end, startLoop, endLoop, sampleRate; tsf_u8 originalPitch; tsf_s8 pitchCorrection; tsf_u16 sampleLink, sampleType; }; + +#define TSFR(FIELD) stream->read(stream->data, &i->FIELD, sizeof(i->FIELD)); +static void tsf_hydra_read_phdr(struct tsf_hydra_phdr* i, struct tsf_stream* stream) { TSFR(presetName) TSFR(preset) TSFR(bank) TSFR(presetBagNdx) TSFR(library) TSFR(genre) TSFR(morphology) } +static void tsf_hydra_read_pbag(struct tsf_hydra_pbag* i, struct tsf_stream* stream) { TSFR(genNdx) TSFR(modNdx) } +static void tsf_hydra_read_pmod(struct tsf_hydra_pmod* i, struct tsf_stream* stream) { TSFR(modSrcOper) TSFR(modDestOper) TSFR(modAmount) TSFR(modAmtSrcOper) TSFR(modTransOper) } +static void tsf_hydra_read_pgen(struct tsf_hydra_pgen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) } +static void tsf_hydra_read_inst(struct tsf_hydra_inst* i, struct tsf_stream* stream) { TSFR(instName) TSFR(instBagNdx) } +static void tsf_hydra_read_ibag(struct tsf_hydra_ibag* i, struct tsf_stream* stream) { TSFR(instGenNdx) TSFR(instModNdx) } +static void tsf_hydra_read_imod(struct tsf_hydra_imod* i, struct tsf_stream* stream) { TSFR(modSrcOper) TSFR(modDestOper) TSFR(modAmount) TSFR(modAmtSrcOper) TSFR(modTransOper) } +static void tsf_hydra_read_igen(struct tsf_hydra_igen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) } +static void tsf_hydra_read_shdr(struct tsf_hydra_shdr* i, struct tsf_stream* stream) { TSFR(sampleName) TSFR(start) TSFR(end) TSFR(startLoop) TSFR(endLoop) TSFR(sampleRate) TSFR(originalPitch) TSFR(pitchCorrection) TSFR(sampleLink) TSFR(sampleType) } +#undef TSFR + +struct tsf_riffchunk { tsf_fourcc id; tsf_u32 size; }; +struct tsf_envelope { float delay, attack, hold, decay, sustain, release, keynumToHold, keynumToDecay; }; +struct tsf_voice_envelope { float level, slope; int samplesUntilNextSegment; short segment, midiVelocity; struct tsf_envelope parameters; TSF_BOOL segmentIsExponential, isAmpEnv; }; +struct tsf_voice_lowpass { double QInv, a0, a1, b1, b2, z1, z2; TSF_BOOL active; }; +struct tsf_voice_lfo { int samplesUntil; float level, delta; }; + +struct tsf_region +{ + int loop_mode; + unsigned int sample_rate; + unsigned char lokey, hikey, lovel, hivel; + unsigned int group, offset, end, loop_start, loop_end; + int transpose, tune, pitch_keycenter, pitch_keytrack; + float attenuation, pan; + struct tsf_envelope ampenv, modenv; + int initialFilterQ, initialFilterFc; + int modEnvToPitch, modEnvToFilterFc, modLfoToFilterFc, modLfoToVolume; + float delayModLFO; + int freqModLFO, modLfoToPitch; + float delayVibLFO; + int freqVibLFO, vibLfoToPitch; +}; + +struct tsf_preset +{ + tsf_char20 presetName; + tsf_u16 preset, bank; + struct tsf_region* regions; + int regionNum; +}; + +struct tsf_voice +{ + int playingPreset, playingKey, playingChannel; + struct tsf_region* region; + double pitchInputTimecents, pitchOutputFactor; + double sourceSamplePosition; + float noteGainDB, panFactorLeft, panFactorRight; + unsigned int playIndex, loopStart, loopEnd; + struct tsf_voice_envelope ampenv, modenv; + struct tsf_voice_lowpass lowpass; + struct tsf_voice_lfo modlfo, viblfo; +}; + +struct tsf_channel +{ + unsigned short presetIndex, bank, pitchWheel, midiPan, midiVolume, midiExpression, midiRPN, midiData; + float panOffset, gainDB, pitchRange, tuning; +}; + +struct tsf_channels +{ + void (*setupVoice)(tsf* f, struct tsf_voice* voice); + int channelNum, activeChannel; + struct tsf_channel channels[1]; +}; + +static double tsf_timecents2Secsd(double timecents) { return TSF_POW(2.0, timecents / 1200.0); } +static float tsf_timecents2Secsf(float timecents) { return TSF_POWF(2.0f, timecents / 1200.0f); } +static float tsf_cents2Hertz(float cents) { return 8.176f * TSF_POWF(2.0f, cents / 1200.0f); } +static float tsf_decibelsToGain(float db) { return (db > -100.f ? TSF_POWF(10.0f, db * 0.05f) : 0); } +static float tsf_gainToDecibels(float gain) { return (gain <= .00001f ? -100.f : (float)(20.0 * TSF_LOG10(gain))); } + +static TSF_BOOL tsf_riffchunk_read(struct tsf_riffchunk* parent, struct tsf_riffchunk* chunk, struct tsf_stream* stream) +{ + TSF_BOOL IsRiff, IsList; + if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) > parent->size) return TSF_FALSE; + if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE; + if (!stream->read(stream->data, &chunk->size, sizeof(tsf_u32))) return TSF_FALSE; + if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size > parent->size) return TSF_FALSE; + if (parent) parent->size -= sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size; + IsRiff = TSF_FourCCEquals(chunk->id, "RIFF"), IsList = TSF_FourCCEquals(chunk->id, "LIST"); + if (IsRiff && parent) return TSF_FALSE; //not allowed + if (!IsRiff && !IsList) return TSF_TRUE; //custom type without sub type + if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE; + chunk->size -= sizeof(tsf_fourcc); + return TSF_TRUE; +} + +static void tsf_region_clear(struct tsf_region* i, TSF_BOOL for_relative) +{ + TSF_MEMSET(i, 0, sizeof(struct tsf_region)); + i->hikey = i->hivel = 127; + i->pitch_keycenter = 60; // C4 + if (for_relative) return; + + i->pitch_keytrack = 100; + + i->pitch_keycenter = -1; + + // SF2 defaults in timecents. + i->ampenv.delay = i->ampenv.attack = i->ampenv.hold = i->ampenv.decay = i->ampenv.release = -12000.0f; + i->modenv.delay = i->modenv.attack = i->modenv.hold = i->modenv.decay = i->modenv.release = -12000.0f; + + i->initialFilterFc = 13500; + + i->delayModLFO = -12000.0f; + i->delayVibLFO = -12000.0f; +} + +static void tsf_region_operator(struct tsf_region* region, tsf_u16 genOper, union tsf_hydra_genamount* amount, struct tsf_region* merge_region) +{ + enum + { + _GEN_TYPE_MASK = 0x0F, + GEN_FLOAT = 0x01, + GEN_INT = 0x02, + GEN_UINT_ADD = 0x03, + GEN_UINT_ADD15 = 0x04, + GEN_KEYRANGE = 0x05, + GEN_VELRANGE = 0x06, + GEN_LOOPMODE = 0x07, + GEN_GROUP = 0x08, + GEN_KEYCENTER = 0x09, + + _GEN_LIMIT_MASK = 0xF0, + GEN_INT_LIMIT12K = 0x10, //min -12000, max 12000 + GEN_INT_LIMITFC = 0x20, //min 1500, max 13500 + GEN_INT_LIMITQ = 0x30, //min 0, max 960 + GEN_INT_LIMIT960 = 0x40, //min -960, max 960 + GEN_INT_LIMIT16K4500 = 0x50, //min -16000, max 4500 + GEN_FLOAT_LIMIT12K5K = 0x60, //min -12000, max 5000 + GEN_FLOAT_LIMIT12K8K = 0x70, //min -12000, max 8000 + GEN_FLOAT_LIMIT1200 = 0x80, //min -1200, max 1200 + GEN_FLOAT_LIMITPAN = 0x90, //* .001f, min -.5f, max .5f, + GEN_FLOAT_LIMITATTN = 0xA0, //* .1f, min 0, max 144.0 + GEN_FLOAT_MAX1000 = 0xB0, //min 0, max 1000 + GEN_FLOAT_MAX1440 = 0xC0, //min 0, max 1440 + + _GEN_MAX = 59, + }; + #define _TSFREGIONOFFSET(TYPE, FIELD) (unsigned char)(((TYPE*)&((struct tsf_region*)0)->FIELD) - (TYPE*)0) + #define _TSFREGIONENVOFFSET(TYPE, ENV, FIELD) (unsigned char)(((TYPE*)&((&(((struct tsf_region*)0)->ENV))->FIELD)) - (TYPE*)0) + static const struct { unsigned char mode, offset; } genMetas[_GEN_MAX] = + { + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, offset ) }, // 0 StartAddrsOffset + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, end ) }, // 1 EndAddrsOffset + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_start ) }, // 2 StartloopAddrsOffset + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_end ) }, // 3 EndloopAddrsOffset + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, offset ) }, // 4 StartAddrsCoarseOffset + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToPitch ) }, // 5 ModLfoToPitch + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, vibLfoToPitch ) }, // 6 VibLfoToPitch + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToPitch ) }, // 7 ModEnvToPitch + { GEN_INT | GEN_INT_LIMITFC , _TSFREGIONOFFSET( int, initialFilterFc ) }, // 8 InitialFilterFc + { GEN_INT | GEN_INT_LIMITQ , _TSFREGIONOFFSET( int, initialFilterQ ) }, // 9 InitialFilterQ + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToFilterFc ) }, //10 ModLfoToFilterFc + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToFilterFc ) }, //11 ModEnvToFilterFc + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, end ) }, //12 EndAddrsCoarseOffset + { GEN_INT | GEN_INT_LIMIT960 , _TSFREGIONOFFSET( int, modLfoToVolume ) }, //13 ModLfoToVolume + { 0 , (0 ) }, // Unused + { 0 , (0 ) }, //15 ChorusEffectsSend (unsupported) + { 0 , (0 ) }, //16 ReverbEffectsSend (unsupported) + { GEN_FLOAT | GEN_FLOAT_LIMITPAN , _TSFREGIONOFFSET( float, pan ) }, //17 Pan + { 0 , (0 ) }, // Unused + { 0 , (0 ) }, // Unused + { 0 , (0 ) }, // Unused + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayModLFO ) }, //21 DelayModLFO + { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqModLFO ) }, //22 FreqModLFO + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayVibLFO ) }, //23 DelayVibLFO + { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqVibLFO ) }, //24 FreqVibLFO + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, delay ) }, //25 DelayModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, attack ) }, //26 AttackModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, hold ) }, //27 HoldModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, decay ) }, //28 DecayModEnv + { GEN_FLOAT | GEN_FLOAT_MAX1000 , _TSFREGIONENVOFFSET( float, modenv, sustain ) }, //29 SustainModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, release ) }, //30 ReleaseModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToHold ) }, //31 KeynumToModEnvHold + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToDecay) }, //32 KeynumToModEnvDecay + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, delay ) }, //33 DelayVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, attack ) }, //34 AttackVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, hold ) }, //35 HoldVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, decay ) }, //36 DecayVolEnv + { GEN_FLOAT | GEN_FLOAT_MAX1440 , _TSFREGIONENVOFFSET( float, ampenv, sustain ) }, //37 SustainVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, release ) }, //38 ReleaseVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToHold ) }, //39 KeynumToVolEnvHold + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToDecay) }, //40 KeynumToVolEnvDecay + { 0 , (0 ) }, // Instrument (special) + { 0 , (0 ) }, // Reserved + { GEN_KEYRANGE , (0 ) }, //43 KeyRange + { GEN_VELRANGE , (0 ) }, //44 VelRange + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_start ) }, //45 StartloopAddrsCoarseOffset + { 0 , (0 ) }, //46 Keynum (special) + { 0 , (0 ) }, //47 Velocity (special) + { GEN_FLOAT | GEN_FLOAT_LIMITATTN , _TSFREGIONOFFSET( float, attenuation ) }, //48 InitialAttenuation + { 0 , (0 ) }, // Reserved + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_end ) }, //50 EndloopAddrsCoarseOffset + { GEN_INT , _TSFREGIONOFFSET( int, transpose ) }, //51 CoarseTune + { GEN_INT , _TSFREGIONOFFSET( int, tune ) }, //52 FineTune + { 0 , (0 ) }, // SampleID (special) + { GEN_LOOPMODE , _TSFREGIONOFFSET( int, loop_mode ) }, //54 SampleModes + { 0 , (0 ) }, // Reserved + { GEN_INT , _TSFREGIONOFFSET( int, pitch_keytrack ) }, //56 ScaleTuning + { GEN_GROUP , _TSFREGIONOFFSET(unsigned int, group ) }, //57 ExclusiveClass + { GEN_KEYCENTER , _TSFREGIONOFFSET( int, pitch_keycenter ) }, //58 OverridingRootKey + }; + #undef _TSFREGIONOFFSET + #undef _TSFREGIONENVOFFSET + if (amount) + { + int offset; + if (genOper >= _GEN_MAX) return; + offset = genMetas[genOper].offset; + switch (genMetas[genOper].mode & _GEN_TYPE_MASK) + { + case GEN_FLOAT: (( float*)region)[offset] = amount->shortAmount; return; + case GEN_INT: (( int*)region)[offset] = amount->shortAmount; return; + case GEN_UINT_ADD: ((unsigned int*)region)[offset] += amount->shortAmount; return; + case GEN_UINT_ADD15: ((unsigned int*)region)[offset] += amount->shortAmount<<15; return; + case GEN_KEYRANGE: region->lokey = amount->range.lo; region->hikey = amount->range.hi; return; + case GEN_VELRANGE: region->lovel = amount->range.lo; region->hivel = amount->range.hi; return; + case GEN_LOOPMODE: region->loop_mode = ((amount->wordAmount&3) == 3 ? TSF_LOOPMODE_SUSTAIN : ((amount->wordAmount&3) == 1 ? TSF_LOOPMODE_CONTINUOUS : TSF_LOOPMODE_NONE)); return; + case GEN_GROUP: region->group = amount->wordAmount; return; + case GEN_KEYCENTER: region->pitch_keycenter = amount->shortAmount; return; + } + } + else //merge regions and clamp values + { + for (genOper = 0; genOper != _GEN_MAX; genOper++) + { + int offset = genMetas[genOper].offset; + switch (genMetas[genOper].mode & _GEN_TYPE_MASK) + { + case GEN_FLOAT: + { + float *val = &((float*)region)[offset], vfactor, vmin, vmax; + *val += ((float*)merge_region)[offset]; + switch (genMetas[genOper].mode & _GEN_LIMIT_MASK) + { + case GEN_FLOAT_LIMIT12K5K: vfactor = 1.0f; vmin = -12000.0f; vmax = 5000.0f; break; + case GEN_FLOAT_LIMIT12K8K: vfactor = 1.0f; vmin = -12000.0f; vmax = 8000.0f; break; + case GEN_FLOAT_LIMIT1200: vfactor = 1.0f; vmin = -1200.0f; vmax = 1200.0f; break; + case GEN_FLOAT_LIMITPAN: vfactor = 0.001f; vmin = -0.5f; vmax = 0.5f; break; + case GEN_FLOAT_LIMITATTN: vfactor = 0.1f; vmin = 0.0f; vmax = 144.0f; break; + case GEN_FLOAT_MAX1000: vfactor = 1.0f; vmin = 0.0f; vmax = 1000.0f; break; + case GEN_FLOAT_MAX1440: vfactor = 1.0f; vmin = 0.0f; vmax = 1440.0f; break; + default: continue; + } + *val *= vfactor; + if (*val < vmin) *val = vmin; + else if (*val > vmax) *val = vmax; + continue; + } + case GEN_INT: + { + int *val = &((int*)region)[offset], vmin, vmax; + *val += ((int*)merge_region)[offset]; + switch (genMetas[genOper].mode & _GEN_LIMIT_MASK) + { + case GEN_INT_LIMIT12K: vmin = -12000; vmax = 12000; break; + case GEN_INT_LIMITFC: vmin = 1500; vmax = 13500; break; + case GEN_INT_LIMITQ: vmin = 0; vmax = 960; break; + case GEN_INT_LIMIT960: vmin = -960; vmax = 960; break; + case GEN_INT_LIMIT16K4500: vmin = -16000; vmax = 4500; break; + default: continue; + } + if (*val < vmin) *val = vmin; + else if (*val > vmax) *val = vmax; + continue; + } + case GEN_UINT_ADD: + { + ((unsigned int*)region)[offset] += ((unsigned int*)merge_region)[offset]; + continue; + } + } + } + } +} + +static void tsf_region_envtosecs(struct tsf_envelope* p, TSF_BOOL sustainIsGain) +{ + // EG times need to be converted from timecents to seconds. + // Pin very short EG segments. Timecents don't get to zero, and our EG is + // happier with zero values. + p->delay = (p->delay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->delay)); + p->attack = (p->attack < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->attack)); + p->release = (p->release < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->release)); + + // If we have dynamic hold or decay times depending on key number we need + // to keep the values in timecents so we can calculate it during startNote + if (!p->keynumToHold) p->hold = (p->hold < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->hold)); + if (!p->keynumToDecay) p->decay = (p->decay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->decay)); + + if (p->sustain < 0.0f) p->sustain = 0.0f; + else if (sustainIsGain) p->sustain = tsf_decibelsToGain(-p->sustain / 10.0f); + else p->sustain = 1.0f - (p->sustain / 1000.0f); +} + +static int tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int fontSampleCount) +{ + enum { GenInstrument = 41, GenKeyRange = 43, GenVelRange = 44, GenSampleID = 53 }; + // Read each preset. + struct tsf_hydra_phdr *pphdr, *pphdrMax; + res->presetNum = hydra->phdrNum - 1; + res->presets = (struct tsf_preset*)TSF_MALLOC(res->presetNum * sizeof(struct tsf_preset)); + if (!res->presets) return 0; + else { int i; for (i = 0; i != res->presetNum; i++) res->presets[i].regions = TSF_NULL; } + for (pphdr = hydra->phdrs, pphdrMax = pphdr + hydra->phdrNum - 1; pphdr != pphdrMax; pphdr++) + { + int sortedIndex = 0, region_index = 0; + struct tsf_hydra_phdr *otherphdr; + struct tsf_preset* preset; + struct tsf_hydra_pbag *ppbag, *ppbagEnd; + struct tsf_region globalRegion; + for (otherphdr = hydra->phdrs; otherphdr != pphdrMax; otherphdr++) + { + if (otherphdr == pphdr || otherphdr->bank > pphdr->bank) continue; + else if (otherphdr->bank < pphdr->bank) sortedIndex++; + else if (otherphdr->preset > pphdr->preset) continue; + else if (otherphdr->preset < pphdr->preset) sortedIndex++; + else if (otherphdr < pphdr) sortedIndex++; + } + + preset = &res->presets[sortedIndex]; + TSF_MEMCPY(preset->presetName, pphdr->presetName, sizeof(preset->presetName)); + preset->presetName[sizeof(preset->presetName)-1] = '\0'; //should be zero terminated in source file but make sure + preset->bank = pphdr->bank; + preset->preset = pphdr->preset; + preset->regionNum = 0; + + //count regions covered by this preset + for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++) + { + unsigned char plokey = 0, phikey = 127, plovel = 0, phivel = 127; + struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd; + for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++) + { + if (ppgen->genOper == GenKeyRange) { plokey = ppgen->genAmount.range.lo; phikey = ppgen->genAmount.range.hi; continue; } + if (ppgen->genOper == GenVelRange) { plovel = ppgen->genAmount.range.lo; phivel = ppgen->genAmount.range.hi; continue; } + if (ppgen->genOper != GenInstrument) continue; + if (ppgen->genAmount.wordAmount >= hydra->instNum) continue; + pinst = hydra->insts + ppgen->genAmount.wordAmount; + for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++) + { + unsigned char ilokey = 0, ihikey = 127, ilovel = 0, ihivel = 127; + for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++) + { + if (pigen->genOper == GenKeyRange) { ilokey = pigen->genAmount.range.lo; ihikey = pigen->genAmount.range.hi; continue; } + if (pigen->genOper == GenVelRange) { ilovel = pigen->genAmount.range.lo; ihivel = pigen->genAmount.range.hi; continue; } + if (pigen->genOper == GenSampleID && ihikey >= plokey && ilokey <= phikey && ihivel >= plovel && ilovel <= phivel) preset->regionNum++; + } + } + } + } + + preset->regions = (struct tsf_region*)TSF_MALLOC(preset->regionNum * sizeof(struct tsf_region)); + if (!preset->regions) + { + int i; for (i = 0; i != res->presetNum; i++) TSF_FREE(res->presets[i].regions); + TSF_FREE(res->presets); + return 0; + } + tsf_region_clear(&globalRegion, TSF_TRUE); + + // Zones. + for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++) + { + struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd; + struct tsf_region presetRegion = globalRegion; + int hadGenInstrument = 0; + + // Generators. + for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++) + { + // Instrument. + if (ppgen->genOper == GenInstrument) + { + struct tsf_region instRegion; + tsf_u16 whichInst = ppgen->genAmount.wordAmount; + if (whichInst >= hydra->instNum) continue; + + tsf_region_clear(&instRegion, TSF_FALSE); + pinst = &hydra->insts[whichInst]; + for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++) + { + // Generators. + struct tsf_region zoneRegion = instRegion; + int hadSampleID = 0; + for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++) + { + if (pigen->genOper == GenSampleID) + { + struct tsf_hydra_shdr* pshdr; + + //preset region key and vel ranges are a filter for the zone regions + if (zoneRegion.hikey < presetRegion.lokey || zoneRegion.lokey > presetRegion.hikey) continue; + if (zoneRegion.hivel < presetRegion.lovel || zoneRegion.lovel > presetRegion.hivel) continue; + if (presetRegion.lokey > zoneRegion.lokey) zoneRegion.lokey = presetRegion.lokey; + if (presetRegion.hikey < zoneRegion.hikey) zoneRegion.hikey = presetRegion.hikey; + if (presetRegion.lovel > zoneRegion.lovel) zoneRegion.lovel = presetRegion.lovel; + if (presetRegion.hivel < zoneRegion.hivel) zoneRegion.hivel = presetRegion.hivel; + + //sum regions + tsf_region_operator(&zoneRegion, 0, TSF_NULL, &presetRegion); + + // EG times need to be converted from timecents to seconds. + tsf_region_envtosecs(&zoneRegion.ampenv, TSF_TRUE); + tsf_region_envtosecs(&zoneRegion.modenv, TSF_FALSE); + + // LFO times need to be converted from timecents to seconds. + zoneRegion.delayModLFO = (zoneRegion.delayModLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayModLFO)); + zoneRegion.delayVibLFO = (zoneRegion.delayVibLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayVibLFO)); + + // Fixup sample positions + pshdr = &hydra->shdrs[pigen->genAmount.wordAmount]; + zoneRegion.offset += pshdr->start; + zoneRegion.end += pshdr->end; + zoneRegion.loop_start += pshdr->startLoop; + zoneRegion.loop_end += pshdr->endLoop; + if (pshdr->endLoop > 0) zoneRegion.loop_end -= 1; + if (zoneRegion.pitch_keycenter == -1) zoneRegion.pitch_keycenter = pshdr->originalPitch; + zoneRegion.tune += pshdr->pitchCorrection; + zoneRegion.sample_rate = pshdr->sampleRate; + if (zoneRegion.end && zoneRegion.end < fontSampleCount) zoneRegion.end++; + else zoneRegion.end = fontSampleCount; + + preset->regions[region_index] = zoneRegion; + region_index++; + hadSampleID = 1; + } + else tsf_region_operator(&zoneRegion, pigen->genOper, &pigen->genAmount, TSF_NULL); + } + + // Handle instrument's global zone. + if (pibag == hydra->ibags + pinst->instBagNdx && !hadSampleID) + instRegion = zoneRegion; + + // Modulators (TODO) + //if (ibag->instModNdx < ibag[1].instModNdx) addUnsupportedOpcode("any modulator"); + } + hadGenInstrument = 1; + } + else tsf_region_operator(&presetRegion, ppgen->genOper, &ppgen->genAmount, TSF_NULL); + } + + // Modulators (TODO) + //if (pbag->modNdx < pbag[1].modNdx) addUnsupportedOpcode("any modulator"); + + // Handle preset's global zone. + if (ppbag == hydra->pbags + pphdr->presetBagNdx && !hadGenInstrument) + globalRegion = presetRegion; + } + } + return 1; +} + +static int tsf_load_samples(float** fontSamples, unsigned int* fontSampleCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream) +{ + // Read sample data into float format buffer. + float* out; unsigned int samplesLeft, samplesToRead, samplesToConvert; + samplesLeft = *fontSampleCount = chunkSmpl->size / sizeof(short); + out = *fontSamples = (float*)TSF_MALLOC(samplesLeft * sizeof(float)); + if (!out) return 0; + for (; samplesLeft; samplesLeft -= samplesToRead) + { + short sampleBuffer[1024], *in = sampleBuffer;; + samplesToRead = (samplesLeft > 1024 ? 1024 : samplesLeft); + stream->read(stream->data, sampleBuffer, samplesToRead * sizeof(short)); + + // Convert from signed 16-bit to float. + for (samplesToConvert = samplesToRead; samplesToConvert > 0; --samplesToConvert) + // If we ever need to compile for big-endian platforms, we'll need to byte-swap here. + *out++ = (float)(*in++ / 32767.0); + } + return 1; +} + +static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate) +{ + switch (active_segment) + { + case TSF_SEGMENT_NONE: + e->samplesUntilNextSegment = (int)(e->parameters.delay * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + e->segment = TSF_SEGMENT_DELAY; + e->segmentIsExponential = TSF_FALSE; + e->level = 0.0; + e->slope = 0.0; + return; + } + /* fall through */ + case TSF_SEGMENT_DELAY: + e->samplesUntilNextSegment = (int)(e->parameters.attack * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + if (!e->isAmpEnv) + { + //mod env attack duration scales with velocity (velocity of 1 is full duration, max velocity is 0.125 times duration) + e->samplesUntilNextSegment = (int)(e->parameters.attack * ((145 - e->midiVelocity) / 144.0f) * outSampleRate); + } + e->segment = TSF_SEGMENT_ATTACK; + e->segmentIsExponential = TSF_FALSE; + e->level = 0.0f; + e->slope = 1.0f / e->samplesUntilNextSegment; + return; + } + /* fall through */ + case TSF_SEGMENT_ATTACK: + e->samplesUntilNextSegment = (int)(e->parameters.hold * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + e->segment = TSF_SEGMENT_HOLD; + e->segmentIsExponential = TSF_FALSE; + e->level = 1.0f; + e->slope = 0.0f; + return; + } + /* fall through */ + case TSF_SEGMENT_HOLD: + e->samplesUntilNextSegment = (int)(e->parameters.decay * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + e->segment = TSF_SEGMENT_DECAY; + e->level = 1.0f; + if (e->isAmpEnv) + { + // I don't truly understand this; just following what LinuxSampler does. + float mysterySlope = -9.226f / e->samplesUntilNextSegment; + e->slope = TSF_EXPF(mysterySlope); + e->segmentIsExponential = TSF_TRUE; + if (e->parameters.sustain > 0.0f) + { + // Again, this is following LinuxSampler's example, which is similar to + // SF2-style decay, where "decay" specifies the time it would take to + // get to zero, not to the sustain level. The SFZ spec is not that + // specific about what "decay" means, so perhaps it's really supposed + // to specify the time to reach the sustain level. + e->samplesUntilNextSegment = (int)(TSF_LOG(e->parameters.sustain) / mysterySlope); + } + } + else + { + e->slope = -1.0f / e->samplesUntilNextSegment; + e->samplesUntilNextSegment = (int)(e->parameters.decay * (1.0f - e->parameters.sustain) * outSampleRate); + e->segmentIsExponential = TSF_FALSE; + } + return; + } + /* fall through */ + case TSF_SEGMENT_DECAY: + e->segment = TSF_SEGMENT_SUSTAIN; + e->level = e->parameters.sustain; + e->slope = 0.0f; + e->samplesUntilNextSegment = 0x7FFFFFFF; + e->segmentIsExponential = TSF_FALSE; + return; + case TSF_SEGMENT_SUSTAIN: + e->segment = TSF_SEGMENT_RELEASE; + e->samplesUntilNextSegment = (int)((e->parameters.release <= 0 ? TSF_FASTRELEASETIME : e->parameters.release) * outSampleRate); + if (e->isAmpEnv) + { + // I don't truly understand this; just following what LinuxSampler does. + float mysterySlope = -9.226f / e->samplesUntilNextSegment; + e->slope = TSF_EXPF(mysterySlope); + e->segmentIsExponential = TSF_TRUE; + } + else + { + e->slope = -e->level / e->samplesUntilNextSegment; + e->segmentIsExponential = TSF_FALSE; + } + return; + case TSF_SEGMENT_RELEASE: + default: + e->segment = TSF_SEGMENT_DONE; + e->segmentIsExponential = TSF_FALSE; + e->level = e->slope = 0.0f; + e->samplesUntilNextSegment = 0x7FFFFFF; + } +} + +static void tsf_voice_envelope_setup(struct tsf_voice_envelope* e, struct tsf_envelope* new_parameters, int midiNoteNumber, short midiVelocity, TSF_BOOL isAmpEnv, float outSampleRate) +{ + e->parameters = *new_parameters; + if (e->parameters.keynumToHold) + { + e->parameters.hold += e->parameters.keynumToHold * (60.0f - midiNoteNumber); + e->parameters.hold = (e->parameters.hold < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.hold)); + } + if (e->parameters.keynumToDecay) + { + e->parameters.decay += e->parameters.keynumToDecay * (60.0f - midiNoteNumber); + e->parameters.decay = (e->parameters.decay < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.decay)); + } + e->midiVelocity = midiVelocity; + e->isAmpEnv = isAmpEnv; + tsf_voice_envelope_nextsegment(e, TSF_SEGMENT_NONE, outSampleRate); +} + +static void tsf_voice_envelope_process(struct tsf_voice_envelope* e, int numSamples, float outSampleRate) +{ + if (e->slope) + { + if (e->segmentIsExponential) e->level *= TSF_POWF(e->slope, (float)numSamples); + else e->level += (e->slope * numSamples); + } + if ((e->samplesUntilNextSegment -= numSamples) <= 0) + tsf_voice_envelope_nextsegment(e, e->segment, outSampleRate); +} + +static void tsf_voice_lowpass_setup(struct tsf_voice_lowpass* e, float Fc) +{ + // Lowpass filter from http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ + double K = TSF_TAN(TSF_PI * Fc), KK = K * K; + double norm = 1 / (1 + K * e->QInv + KK); + e->a0 = KK * norm; + e->a1 = 2 * e->a0; + e->b1 = 2 * (KK - 1) * norm; + e->b2 = (1 - K * e->QInv + KK) * norm; +} + +static float tsf_voice_lowpass_process(struct tsf_voice_lowpass* e, double In) +{ + double Out = In * e->a0 + e->z1; e->z1 = In * e->a1 + e->z2 - e->b1 * Out; e->z2 = In * e->a0 - e->b2 * Out; return (float)Out; +} + +static void tsf_voice_lfo_setup(struct tsf_voice_lfo* e, float delay, int freqCents, float outSampleRate) +{ + e->samplesUntil = (int)(delay * outSampleRate); + e->delta = (4.0f * tsf_cents2Hertz((float)freqCents) / outSampleRate); + e->level = 0; +} + +static void tsf_voice_lfo_process(struct tsf_voice_lfo* e, int blockSamples) +{ + if (e->samplesUntil > blockSamples) { e->samplesUntil -= blockSamples; return; } + e->level += e->delta * blockSamples; + if (e->level > 1.0f) { e->delta = -e->delta; e->level = 2.0f - e->level; } + else if (e->level < -1.0f) { e->delta = -e->delta; e->level = -2.0f - e->level; } +} + +static void tsf_voice_kill(struct tsf_voice* v) +{ + v->playingPreset = -1; +} + +static void tsf_voice_end(tsf* f, struct tsf_voice* v) +{ + // if maxVoiceNum is set, assume that voice rendering and note queuing are on separate threads + // so to minimize the chance that voice rendering would advance the segment at the same time + // we just do it twice here and hope that it sticks + int repeats = (f->maxVoiceNum ? 2 : 1); + while (repeats--) + { + tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + if (v->region->loop_mode == TSF_LOOPMODE_SUSTAIN) + { + // Continue playing, but stop looping. + v->loopEnd = v->loopStart; + } + } +} + +static void tsf_voice_endquick(tsf* f, struct tsf_voice* v) +{ + // if maxVoiceNum is set, assume that voice rendering and note queuing are on separate threads + // so to minimize the chance that voice rendering would advance the segment at the same time + // we just do it twice here and hope that it sticks + int repeats = (f->maxVoiceNum ? 2 : 1); + while (repeats--) + { + v->ampenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + v->modenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + } +} + +static void tsf_voice_calcpitchratio(struct tsf_voice* v, float pitchShift, float outSampleRate) +{ + double note = v->playingKey + v->region->transpose + v->region->tune / 100.0; + double adjustedPitch = v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0); + if (pitchShift) adjustedPitch += pitchShift; + v->pitchInputTimecents = adjustedPitch * 100.0; + v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate); +} + +static void tsf_voice_render(tsf* f, struct tsf_voice* v, float* outputBuffer, int numSamples) +{ + struct tsf_region* region = v->region; + float* input = f->fontSamples; + float* outL = outputBuffer; + float* outR = (f->outputmode == TSF_STEREO_UNWEAVED ? outL + numSamples : TSF_NULL); + + // Cache some values, to give them at least some chance of ending up in registers. + TSF_BOOL updateModEnv = (region->modEnvToPitch || region->modEnvToFilterFc); + TSF_BOOL updateModLFO = (v->modlfo.delta && (region->modLfoToPitch || region->modLfoToFilterFc || region->modLfoToVolume)); + TSF_BOOL updateVibLFO = (v->viblfo.delta && (region->vibLfoToPitch)); + TSF_BOOL isLooping = (v->loopStart < v->loopEnd); + unsigned int tmpLoopStart = v->loopStart, tmpLoopEnd = v->loopEnd; + double tmpSampleEndDbl = (double)region->end, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0; + double tmpSourceSamplePosition = v->sourceSamplePosition; + struct tsf_voice_lowpass tmpLowpass = v->lowpass; + + TSF_BOOL dynamicLowpass = (region->modLfoToFilterFc || region->modEnvToFilterFc); + float tmpSampleRate = f->outSampleRate, tmpInitialFilterFc, tmpModLfoToFilterFc, tmpModEnvToFilterFc; + + TSF_BOOL dynamicPitchRatio = (region->modLfoToPitch || region->modEnvToPitch || region->vibLfoToPitch); + double pitchRatio; + float tmpModLfoToPitch, tmpVibLfoToPitch, tmpModEnvToPitch; + + TSF_BOOL dynamicGain = (region->modLfoToVolume != 0); + float noteGain = 0, tmpModLfoToVolume; + + if (dynamicLowpass) tmpInitialFilterFc = (float)region->initialFilterFc, tmpModLfoToFilterFc = (float)region->modLfoToFilterFc, tmpModEnvToFilterFc = (float)region->modEnvToFilterFc; + else tmpInitialFilterFc = 0, tmpModLfoToFilterFc = 0, tmpModEnvToFilterFc = 0; + + if (dynamicPitchRatio) pitchRatio = 0, tmpModLfoToPitch = (float)region->modLfoToPitch, tmpVibLfoToPitch = (float)region->vibLfoToPitch, tmpModEnvToPitch = (float)region->modEnvToPitch; + else pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents) * v->pitchOutputFactor, tmpModLfoToPitch = 0, tmpVibLfoToPitch = 0, tmpModEnvToPitch = 0; + + if (dynamicGain) tmpModLfoToVolume = (float)region->modLfoToVolume * 0.1f; + else noteGain = tsf_decibelsToGain(v->noteGainDB), tmpModLfoToVolume = 0; + + while (numSamples) + { + float gainMono, gainLeft, gainRight; + int blockSamples = (numSamples > TSF_RENDER_EFFECTSAMPLEBLOCK ? TSF_RENDER_EFFECTSAMPLEBLOCK : numSamples); + numSamples -= blockSamples; + + if (dynamicLowpass) + { + float fres = tmpInitialFilterFc + v->modlfo.level * tmpModLfoToFilterFc + v->modenv.level * tmpModEnvToFilterFc; + float lowpassFc = (fres <= 13500 ? tsf_cents2Hertz(fres) / tmpSampleRate : 1.0f); + tmpLowpass.active = (lowpassFc < 0.499f); + if (tmpLowpass.active) tsf_voice_lowpass_setup(&tmpLowpass, lowpassFc); + } + + if (dynamicPitchRatio) + pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents + (v->modlfo.level * tmpModLfoToPitch + v->viblfo.level * tmpVibLfoToPitch + v->modenv.level * tmpModEnvToPitch)) * v->pitchOutputFactor; + + if (dynamicGain) + noteGain = tsf_decibelsToGain(v->noteGainDB + (v->modlfo.level * tmpModLfoToVolume)); + + gainMono = noteGain * v->ampenv.level; + + // Update EG. + tsf_voice_envelope_process(&v->ampenv, blockSamples, tmpSampleRate); + if (updateModEnv) tsf_voice_envelope_process(&v->modenv, blockSamples, tmpSampleRate); + + // Update LFOs. + if (updateModLFO) tsf_voice_lfo_process(&v->modlfo, blockSamples); + if (updateVibLFO) tsf_voice_lfo_process(&v->viblfo, blockSamples); + + switch (f->outputmode) + { + case TSF_STEREO_INTERLEAVED: + gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight; + while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) + { + unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); + + *outL++ += val * gainLeft; + *outL++ += val * gainRight; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + + case TSF_STEREO_UNWEAVED: + gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight; + while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) + { + unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); + + *outL++ += val * gainLeft; + *outR++ += val * gainRight; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + + case TSF_MONO: + while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) + { + unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); + + *outL++ += val * gainMono; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + } + + if (tmpSourceSamplePosition >= tmpSampleEndDbl || v->ampenv.segment == TSF_SEGMENT_DONE) + { + tsf_voice_kill(v); + return; + } + } + + v->sourceSamplePosition = tmpSourceSamplePosition; + if (tmpLowpass.active || dynamicLowpass) v->lowpass = tmpLowpass; +} + +TSFDEF tsf* tsf_load(struct tsf_stream* stream) +{ + tsf* res = TSF_NULL; + struct tsf_riffchunk chunkHead; + struct tsf_riffchunk chunkList; + struct tsf_hydra hydra; + float* fontSamples = TSF_NULL; + unsigned int fontSampleCount = 0; + + if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk")) + { + //if (e) *e = TSF_INVALID_NOSF2HEADER; + return res; + } + + // Read hydra and locate sample data. + TSF_MEMSET(&hydra, 0, sizeof(hydra)); + while (tsf_riffchunk_read(&chunkHead, &chunkList, stream)) + { + struct tsf_riffchunk chunk; + if (TSF_FourCCEquals(chunkList.id, "pdta")) + { + while (tsf_riffchunk_read(&chunkList, &chunk, stream)) + { + #define HandleChunk(chunkName) (TSF_FourCCEquals(chunk.id, #chunkName) && !(chunk.size % chunkName##SizeInFile)) \ + { \ + int num = chunk.size / chunkName##SizeInFile, i; \ + hydra.chunkName##Num = num; \ + hydra.chunkName##s = (struct tsf_hydra_##chunkName*)TSF_MALLOC(num * sizeof(struct tsf_hydra_##chunkName)); \ + if (!hydra.chunkName##s) goto out_of_memory; \ + for (i = 0; i < num; ++i) tsf_hydra_read_##chunkName(&hydra.chunkName##s[i], stream); \ + } + enum + { + phdrSizeInFile = 38, pbagSizeInFile = 4, pmodSizeInFile = 10, + pgenSizeInFile = 4, instSizeInFile = 22, ibagSizeInFile = 4, + imodSizeInFile = 10, igenSizeInFile = 4, shdrSizeInFile = 46 + }; + if HandleChunk(phdr) else if HandleChunk(pbag) else if HandleChunk(pmod) + else if HandleChunk(pgen) else if HandleChunk(inst) else if HandleChunk(ibag) + else if HandleChunk(imod) else if HandleChunk(igen) else if HandleChunk(shdr) + else stream->skip(stream->data, chunk.size); + #undef HandleChunk + } + } + else if (TSF_FourCCEquals(chunkList.id, "sdta")) + { + while (tsf_riffchunk_read(&chunkList, &chunk, stream)) + { + if (TSF_FourCCEquals(chunk.id, "smpl") && !fontSamples && chunk.size >= sizeof(short)) + { + if (!tsf_load_samples(&fontSamples, &fontSampleCount, &chunk, stream)) goto out_of_memory; + } + else stream->skip(stream->data, chunk.size); + } + } + else stream->skip(stream->data, chunkList.size); + } + if (!hydra.phdrs || !hydra.pbags || !hydra.pmods || !hydra.pgens || !hydra.insts || !hydra.ibags || !hydra.imods || !hydra.igens || !hydra.shdrs) + { + //if (e) *e = TSF_INVALID_INCOMPLETE; + } + else if (fontSamples == TSF_NULL) + { + //if (e) *e = TSF_INVALID_NOSAMPLEDATA; + } + else + { + res = (tsf*)TSF_MALLOC(sizeof(tsf)); + if (!res) goto out_of_memory; + TSF_MEMSET(res, 0, sizeof(tsf)); + if (!tsf_load_presets(res, &hydra, fontSampleCount)) goto out_of_memory; + res->fontSamples = fontSamples; + fontSamples = TSF_NULL; //don't free below + res->outSampleRate = 44100.0f; + } + if (0) + { + out_of_memory: + TSF_FREE(res); + res = TSF_NULL; + //if (e) *e = TSF_OUT_OF_MEMORY; + } + TSF_FREE(hydra.phdrs); TSF_FREE(hydra.pbags); TSF_FREE(hydra.pmods); + TSF_FREE(hydra.pgens); TSF_FREE(hydra.insts); TSF_FREE(hydra.ibags); + TSF_FREE(hydra.imods); TSF_FREE(hydra.igens); TSF_FREE(hydra.shdrs); + TSF_FREE(fontSamples); + return res; +} + +TSFDEF tsf* tsf_copy(tsf* f) +{ + tsf* res; + if (!f) return TSF_NULL; + if (!f->refCount) + { + f->refCount = (int*)TSF_MALLOC(sizeof(int)); + if (!f->refCount) return TSF_NULL; + *f->refCount = 1; + } + res = (tsf*)TSF_MALLOC(sizeof(tsf)); + if (!res) return TSF_NULL; + TSF_MEMCPY(res, f, sizeof(tsf)); + res->voices = TSF_NULL; + res->voiceNum = 0; + res->channels = TSF_NULL; + (*res->refCount)++; + return res; +} + +TSFDEF void tsf_close(tsf* f) +{ + if (!f) return; + if (!f->refCount || !--(*f->refCount)) + { + struct tsf_preset *preset = f->presets, *presetEnd = preset + f->presetNum; + for (; preset != presetEnd; preset++) TSF_FREE(preset->regions); + TSF_FREE(f->presets); + TSF_FREE(f->fontSamples); + TSF_FREE(f->refCount); + } + TSF_FREE(f->channels); + TSF_FREE(f->voices); + TSF_FREE(f); +} + +TSFDEF void tsf_reset(tsf* f) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) + if (v->playingPreset != -1 && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release)) + tsf_voice_endquick(f, v); + if (f->channels) { TSF_FREE(f->channels); f->channels = TSF_NULL; } +} + +TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number) +{ + const struct tsf_preset *presets; + int i, iMax; + for (presets = f->presets, i = 0, iMax = f->presetNum; i < iMax; i++) + if (presets[i].preset == preset_number && presets[i].bank == bank) + return i; + return -1; +} + +TSFDEF int tsf_get_presetcount(const tsf* f) +{ + return f->presetNum; +} + +TSFDEF const char* tsf_get_presetname(const tsf* f, int preset) +{ + return (preset < 0 || preset >= f->presetNum ? TSF_NULL : f->presets[preset].presetName); +} + +TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number) +{ + return tsf_get_presetname(f, tsf_get_presetindex(f, bank, preset_number)); +} + +TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db) +{ + f->outputmode = outputmode; + f->outSampleRate = (float)(samplerate >= 1 ? samplerate : 44100.0f); + f->globalGainDB = global_gain_db; +} + +TSFDEF void tsf_set_volume(tsf* f, float global_volume) +{ + f->globalGainDB = (global_volume == 1.0f ? 0 : -tsf_gainToDecibels(1.0f / global_volume)); +} + +TSFDEF int tsf_set_max_voices(tsf* f, int max_voices) +{ + int i = f->voiceNum; + int newVoiceNum = (f->voiceNum > max_voices ? f->voiceNum : max_voices); + struct tsf_voice *newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, newVoiceNum * sizeof(struct tsf_voice)); + if (!newVoices) return 0; + f->voices = newVoices; + f->voiceNum = f->maxVoiceNum = newVoiceNum; + for (; i < max_voices; i++) + f->voices[i].playingPreset = -1; + return 1; +} + +TSFDEF int tsf_note_on(tsf* f, int preset_index, int key, float vel) +{ + short midiVelocity = (short)(vel * 127); + int voicePlayIndex; + struct tsf_region *region, *regionEnd; + + if (preset_index < 0 || preset_index >= f->presetNum) return 1; + if (vel <= 0.0f) { tsf_note_off(f, preset_index, key); return 1; } + + // Play all matching regions. + voicePlayIndex = f->voicePlayIndex++; + for (region = f->presets[preset_index].regions, regionEnd = region + f->presets[preset_index].regionNum; region != regionEnd; region++) + { + struct tsf_voice *voice, *v, *vEnd; TSF_BOOL doLoop; float lowpassFilterQDB, lowpassFc; + if (key < region->lokey || key > region->hikey || midiVelocity < region->lovel || midiVelocity > region->hivel) continue; + + voice = TSF_NULL, v = f->voices, vEnd = v + f->voiceNum; + if (region->group) + { + for (; v != vEnd; v++) + if (v->playingPreset == preset_index && v->region->group == region->group) tsf_voice_endquick(f, v); + else if (v->playingPreset == -1 && !voice) voice = v; + } + else for (; v != vEnd; v++) if (v->playingPreset == -1) { voice = v; break; } + + if (!voice) + { + struct tsf_voice* newVoices; + if (f->maxVoiceNum) + { + // voices have been pre-allocated and limited to a maximum, unable to start playing this voice + continue; + } + f->voiceNum += 4; + newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice)); + if (!newVoices) return 0; + f->voices = newVoices; + voice = &f->voices[f->voiceNum - 4]; + voice[1].playingPreset = voice[2].playingPreset = voice[3].playingPreset = -1; + } + + voice->region = region; + voice->playingPreset = preset_index; + voice->playingKey = key; + voice->playIndex = voicePlayIndex; + voice->noteGainDB = f->globalGainDB - region->attenuation - tsf_gainToDecibels(1.0f / vel); + + if (f->channels) + { + f->channels->setupVoice(f, voice); + } + else + { + tsf_voice_calcpitchratio(voice, 0, f->outSampleRate); + // The SFZ spec is silent about the pan curve, but a 3dB pan law seems common. This sqrt() curve matches what Dimension LE does; Alchemy Free seems closer to sin(adjustedPan * pi/2). + voice->panFactorLeft = TSF_SQRTF(0.5f - region->pan); + voice->panFactorRight = TSF_SQRTF(0.5f + region->pan); + } + + // Offset/end. + voice->sourceSamplePosition = region->offset; + + // Loop. + doLoop = (region->loop_mode != TSF_LOOPMODE_NONE && region->loop_start < region->loop_end); + voice->loopStart = (doLoop ? region->loop_start : 0); + voice->loopEnd = (doLoop ? region->loop_end : 0); + + // Setup envelopes. + tsf_voice_envelope_setup(&voice->ampenv, ®ion->ampenv, key, midiVelocity, TSF_TRUE, f->outSampleRate); + tsf_voice_envelope_setup(&voice->modenv, ®ion->modenv, key, midiVelocity, TSF_FALSE, f->outSampleRate); + + // Setup lowpass filter. + lowpassFc = (region->initialFilterFc <= 13500 ? tsf_cents2Hertz((float)region->initialFilterFc) / f->outSampleRate : 1.0f); + lowpassFilterQDB = region->initialFilterQ / 10.0f; + voice->lowpass.QInv = 1.0 / TSF_POW(10.0, (lowpassFilterQDB / 20.0)); + voice->lowpass.z1 = voice->lowpass.z2 = 0; + voice->lowpass.active = (lowpassFc < 0.499f); + if (voice->lowpass.active) tsf_voice_lowpass_setup(&voice->lowpass, lowpassFc); + + // Setup LFO filters. + tsf_voice_lfo_setup(&voice->modlfo, region->delayModLFO, region->freqModLFO, f->outSampleRate); + tsf_voice_lfo_setup(&voice->viblfo, region->delayVibLFO, region->freqVibLFO, f->outSampleRate); + } + return 1; +} + +TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel) +{ + int preset_index = tsf_get_presetindex(f, bank, preset_number); + if (preset_index == -1) return 0; + return tsf_note_on(f, preset_index, key, vel); +} + +TSFDEF void tsf_note_off(tsf* f, int preset_index, int key) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL; + for (; v != vEnd; v++) + { + //Find the first and last entry in the voices list with matching preset, key and look up the smallest play index + if (v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue; + else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v; + else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v; + } + if (!vMatchFirst) return; + for (v = vMatchFirst; v <= vMatchLast; v++) + { + //Stop all voices with matching preset, key and the smallest play index which was enumerated above + if (v != vMatchFirst && v != vMatchLast && + (v->playIndex != vMatchFirst->playIndex || v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue; + tsf_voice_end(f, v); + } +} + +TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key) +{ + int preset_index = tsf_get_presetindex(f, bank, preset_number); + if (preset_index == -1) return 0; + tsf_note_off(f, preset_index, key); + return 1; +} + +TSFDEF void tsf_note_off_all(tsf* f) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) if (v->playingPreset != -1 && v->ampenv.segment < TSF_SEGMENT_RELEASE) + tsf_voice_end(f, v); +} + +TSFDEF int tsf_active_voice_count(tsf* f) +{ + int count = 0; + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) if (v->playingPreset != -1) count++; + return count; +} + +TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing) +{ + float outputSamples[TSF_RENDER_SHORTBUFFERBLOCK]; + int channels = (f->outputmode == TSF_MONO ? 1 : 2), maxChannelSamples = TSF_RENDER_SHORTBUFFERBLOCK / channels; + while (samples > 0) + { + int channelSamples = (samples > maxChannelSamples ? maxChannelSamples : samples); + short* bufferEnd = buffer + channelSamples * channels; + float *floatSamples = outputSamples; + tsf_render_float(f, floatSamples, channelSamples, TSF_FALSE); + samples -= channelSamples; + + if (flag_mixing) + while (buffer != bufferEnd) + { + float v = *floatSamples++; + int vi = *buffer + (v < -1.00004566f ? (int)-32768 : (v > 1.00001514f ? (int)32767 : (int)(v * 32767.5f))); + *buffer++ = (vi < -32768 ? (short)-32768 : (vi > 32767 ? (short)32767 : (short)vi)); + } + else + while (buffer != bufferEnd) + { + float v = *floatSamples++; + *buffer++ = (v < -1.00004566f ? (short)-32768 : (v > 1.00001514f ? (short)32767 : (short)(v * 32767.5f))); + } + } +} + +TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + if (!flag_mixing) TSF_MEMSET(buffer, 0, (f->outputmode == TSF_MONO ? 1 : 2) * sizeof(float) * samples); + for (; v != vEnd; v++) + if (v->playingPreset != -1) + tsf_voice_render(f, v, buffer, samples); +} + +static void tsf_channel_setup_voice(tsf* f, struct tsf_voice* v) +{ + struct tsf_channel* c = &f->channels->channels[f->channels->activeChannel]; + float newpan = v->region->pan + c->panOffset; + v->playingChannel = f->channels->activeChannel; + v->noteGainDB += c->gainDB; + tsf_voice_calcpitchratio(v, (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)), f->outSampleRate); + if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; } + else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; } + else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); } +} + +static struct tsf_channel* tsf_channel_init(tsf* f, int channel) +{ + int i; + if (f->channels && channel < f->channels->channelNum) return &f->channels->channels[channel]; + if (!f->channels) + { + f->channels = (struct tsf_channels*)TSF_MALLOC(sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); + if (!f->channels) return NULL; + f->channels->setupVoice = &tsf_channel_setup_voice; + f->channels->channelNum = 0; + f->channels->activeChannel = 0; + } + else + { + struct tsf_channels *newChannels = (struct tsf_channels*)TSF_REALLOC(f->channels, sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); + if (!newChannels) return NULL; + f->channels = newChannels; + } + i = f->channels->channelNum; + f->channels->channelNum = channel + 1; + for (; i <= channel; i++) + { + struct tsf_channel* c = &f->channels->channels[i]; + c->presetIndex = c->bank = 0; + c->pitchWheel = c->midiPan = 8192; + c->midiVolume = c->midiExpression = 16383; + c->midiRPN = 0xFFFF; + c->midiData = 0; + c->panOffset = 0.0f; + c->gainDB = 0.0f; + c->pitchRange = 2.0f; + c->tuning = 0.0f; + } + return &f->channels->channels[channel]; +} + +static void tsf_channel_applypitch(tsf* f, int channel, struct tsf_channel* c) +{ + struct tsf_voice *v, *vEnd; + float pitchShift = (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)); + for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) + if (v->playingChannel == channel && v->playingPreset != -1) + tsf_voice_calcpitchratio(v, pitchShift, f->outSampleRate); +} + +TSFDEF int tsf_channel_set_presetindex(tsf* f, int channel, int preset_index) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + c->presetIndex = (unsigned short)preset_index; + return 1; +} + +TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums) +{ + int preset_index; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (flag_mididrums) + { + preset_index = tsf_get_presetindex(f, 128 | (c->bank & 0x7FFF), preset_number); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, preset_number); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, 0); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number); + } + else preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, 0, preset_number); + if (preset_index != -1) + { + c->presetIndex = (unsigned short)preset_index; + return 1; + } + return 0; +} + +TSFDEF int tsf_channel_set_bank(tsf* f, int channel, int bank) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + c->bank = (unsigned short)bank; + return 1; +} + +TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number) +{ + int preset_index; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + preset_index = tsf_get_presetindex(f, bank, preset_number); + if (preset_index == -1) return 0; + c->presetIndex = (unsigned short)preset_index; + c->bank = (unsigned short)bank; + return 1; +} + +TSFDEF int tsf_channel_set_pan(tsf* f, int channel, float pan) +{ + struct tsf_voice *v, *vEnd; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) + if (v->playingChannel == channel && v->playingPreset != -1) + { + float newpan = v->region->pan + pan - 0.5f; + if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; } + else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; } + else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); } + } + c->panOffset = pan - 0.5f; + return 1; +} + +TSFDEF int tsf_channel_set_volume(tsf* f, int channel, float volume) +{ + float gainDB = tsf_gainToDecibels(volume), gainDBChange; + struct tsf_voice *v, *vEnd; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (gainDB == c->gainDB) return 1; + for (v = f->voices, vEnd = v + f->voiceNum, gainDBChange = gainDB - c->gainDB; v != vEnd; v++) + if (v->playingChannel == channel && v->playingPreset != -1) + v->noteGainDB += gainDBChange; + c->gainDB = gainDB; + return 1; +} + +TSFDEF int tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (c->pitchWheel == pitch_wheel) return 1; + c->pitchWheel = (unsigned short)pitch_wheel; + tsf_channel_applypitch(f, channel, c); + return 1; +} + +TSFDEF int tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (c->pitchRange == pitch_range) return 1; + c->pitchRange = pitch_range; + if (c->pitchWheel != 8192) tsf_channel_applypitch(f, channel, c); + return 1; +} + +TSFDEF int tsf_channel_set_tuning(tsf* f, int channel, float tuning) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (c->tuning == tuning) return 1; + c->tuning = tuning; + tsf_channel_applypitch(f, channel, c); + return 1; +} + +TSFDEF int tsf_channel_note_on(tsf* f, int channel, int key, float vel) +{ + if (!f->channels || channel >= f->channels->channelNum) return 1; + f->channels->activeChannel = channel; + return tsf_note_on(f, f->channels->channels[channel].presetIndex, key, vel); +} + +TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL; + for (; v != vEnd; v++) + { + //Find the first and last entry in the voices list with matching channel, key and look up the smallest play index + if (v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue; + else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v; + else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v; + } + if (!vMatchFirst) return; + for (v = vMatchFirst; v <= vMatchLast; v++) + { + //Stop all voices with matching channel, key and the smallest play index which was enumerated above + if (v != vMatchFirst && v != vMatchLast && + (v->playIndex != vMatchFirst->playIndex || v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue; + tsf_voice_end(f, v); + } +} + +TSFDEF void tsf_channel_note_off_all(tsf* f, int channel) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) + if (v->playingPreset != -1 && v->playingChannel == channel && v->ampenv.segment < TSF_SEGMENT_RELEASE) + tsf_voice_end(f, v); +} + +TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) + if (v->playingPreset != -1 && v->playingChannel == channel && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release)) + tsf_voice_endquick(f, v); +} + +TSFDEF int tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value) +{ + struct tsf_channel* c = tsf_channel_init(f, channel); + if (!c) return 0; + switch (controller) + { + case 7 /*VOLUME_MSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME; + case 39 /*VOLUME_LSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x3F80) | control_value); goto TCMC_SET_VOLUME; + case 11 /*EXPRESSION_MSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME; + case 43 /*EXPRESSION_LSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x3F80) | control_value); goto TCMC_SET_VOLUME; + case 10 /*PAN_MSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x7F ) | (control_value << 7)); goto TCMC_SET_PAN; + case 42 /*PAN_LSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x3F80) | control_value); goto TCMC_SET_PAN; + case 6 /*DATA_ENTRY_MSB*/ : c->midiData = (unsigned short)((c->midiData & 0x7F) | (control_value << 7)); goto TCMC_SET_DATA; + case 38 /*DATA_ENTRY_LSB*/ : c->midiData = (unsigned short)((c->midiData & 0x3F80) | control_value); goto TCMC_SET_DATA; + case 0 /*BANK_SELECT_MSB*/ : c->bank = (unsigned short)(0x8000 | control_value); return 1; //bank select MSB alone acts like LSB + case 32 /*BANK_SELECT_LSB*/ : c->bank = (unsigned short)((c->bank & 0x8000 ? ((c->bank & 0x7F) << 7) : 0) | control_value); return 1; + case 101 /*RPN_MSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x7F ) | (control_value << 7)); return 1; + case 100 /*RPN_LSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x3F80) | control_value); return 1; + case 98 /*NRPN_LSB*/ : c->midiRPN = 0xFFFF; return 1; + case 99 /*NRPN_MSB*/ : c->midiRPN = 0xFFFF; return 1; + case 120 /*ALL_SOUND_OFF*/ : tsf_channel_sounds_off_all(f, channel); return 1; + case 123 /*ALL_NOTES_OFF*/ : tsf_channel_note_off_all(f, channel); return 1; + case 121 /*ALL_CTRL_OFF*/ : + c->midiVolume = c->midiExpression = 16383; + c->midiPan = 8192; + c->bank = 0; + c->midiRPN = 0xFFFF; + c->midiData = 0; + tsf_channel_set_volume(f, channel, 1.0f); + tsf_channel_set_pan(f, channel, 0.5f); + tsf_channel_set_pitchrange(f, channel, 2.0f); + tsf_channel_set_tuning(f, channel, 0); + return 1; + } + return 1; +TCMC_SET_VOLUME: + //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI + tsf_channel_set_volume(f, channel, TSF_POWF((c->midiVolume / 16383.0f) * (c->midiExpression / 16383.0f), 3.0f)); + return 1; +TCMC_SET_PAN: + tsf_channel_set_pan(f, channel, c->midiPan / 16383.0f); + return 1; +TCMC_SET_DATA: + if (c->midiRPN == 0) tsf_channel_set_pitchrange(f, channel, (c->midiData >> 7) + 0.01f * (c->midiData & 0x7F)); + else if (c->midiRPN == 1) tsf_channel_set_tuning(f, channel, (int)c->tuning + ((float)c->midiData - 8192.0f) / 8192.0f); //fine tune + else if (c->midiRPN == 2 && controller == 6) tsf_channel_set_tuning(f, channel, ((float)control_value - 64.0f) + (c->tuning - (int)c->tuning)); //coarse tune + return 1; +} + +TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].presetIndex : 0); +} + +TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? (f->channels->channels[channel].bank & 0x7FFF) : 0); +} + +TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->presets[f->channels->channels[channel].presetIndex].preset : 0); +} + +TSFDEF float tsf_channel_get_pan(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].panOffset - 0.5f : 0.5f); +} + +TSFDEF float tsf_channel_get_volume(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? tsf_decibelsToGain(f->channels->channels[channel].gainDB) : 1.0f); +} + +TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchWheel : 8192); +} + +TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchRange : 2.0f); +} + +TSFDEF float tsf_channel_get_tuning(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].tuning : 0.0f); +} + +#ifdef __cplusplus +} +#endif + +#endif //TSF_IMPLEMENTATION diff --git a/source/engine/util.c b/source/engine/util.c new file mode 100644 index 0000000..49edbf3 --- /dev/null +++ b/source/engine/util.c @@ -0,0 +1,21 @@ +#include "util.h" + +unsigned int powof2(unsigned int num) +{ + if (num != 0) { + num--; + num |= (num >> 1); + num |= (num >> 2); + num |= (num >> 4); + num |= (num >> 8); + num |= (num >> 16); + num++; + } + + return num; +} + +int ispow2(int num) +{ + return (num && !(num & (num - 1))); +} \ No newline at end of file diff --git a/source/engine/util.h b/source/engine/util.h new file mode 100644 index 0000000..a217d33 --- /dev/null +++ b/source/engine/util.h @@ -0,0 +1,7 @@ +#ifndef UTIL_H +#define UTIL_H + +unsigned int powof2(unsigned int num); +int ispow2(int num); + +#endif \ No newline at end of file