├── sfotool ├── Makefile ├── .gitignore ├── sfotool.sln ├── README.md ├── sfotool.vcxproj └── main.c ├── examples ├── venture.mid ├── florestan-subset.sf2 ├── .gitignore ├── build-linux-gcc.sh ├── build-linux-clang.sh ├── build-osx.sh ├── examples.sln ├── example2.c ├── example1.c ├── example1.dsp ├── example2.dsp ├── example3.dsp ├── example3.c ├── example1.vcxproj ├── example2.vcxproj └── example3.vcxproj ├── LICENSE ├── README.md ├── tml.h └── tsf.h /sfotool/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc main.c -o sfotool 3 | -------------------------------------------------------------------------------- /examples/venture.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schellingb/TinySoundFont/HEAD/examples/venture.mid -------------------------------------------------------------------------------- /examples/florestan-subset.sf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schellingb/TinySoundFont/HEAD/examples/florestan-subset.sf2 -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.ncb 2 | *.opt 3 | *.plg 4 | *.aps 5 | *.ipch 6 | *.suo 7 | *.user 8 | *.sdf 9 | *.opensdf 10 | *.dsw 11 | *-i686 12 | *-x86_64 13 | Debug/ 14 | Release/ 15 | .vs/ 16 | -------------------------------------------------------------------------------- /sfotool/.gitignore: -------------------------------------------------------------------------------- 1 | *.ncb 2 | *.opt 3 | *.plg 4 | *.aps 5 | *.ipch 6 | *.suo 7 | *.user 8 | *.sdf 9 | *.opensdf 10 | *.dsw 11 | *-i686 12 | *-x86_64 13 | Debug 14 | Release 15 | .vs 16 | -------------------------------------------------------------------------------- /examples/build-linux-gcc.sh: -------------------------------------------------------------------------------- 1 | echo Building \'example1-linux-`uname -m`\' ... 2 | gcc -Wall example1.c -lm -ldl -lpthread -o example1-linux-`uname -m` 3 | echo Building \'example2-linux-`uname -m`\' ... 4 | gcc -Wall example2.c -lm -ldl -lpthread -o example2-linux-`uname -m` 5 | echo Building \'example3-linux-`uname -m`\' ... 6 | gcc -Wall example3.c -lm -ldl -lpthread -o example3-linux-`uname -m` 7 | echo Done! 8 | -------------------------------------------------------------------------------- /examples/build-linux-clang.sh: -------------------------------------------------------------------------------- 1 | echo Building \'example1-linux-`uname -m`\' ... 2 | clang -std=c99 -Wall example1.c -lm -ldl -lpthread -o example1-linux-`uname -m` 3 | echo Building \'example2-linux-`uname -m`\' ... 4 | clang -std=c99 -Wall example2.c -lm -ldl -lpthread -o example2-linux-`uname -m` 5 | echo Building \'example3-linux-`uname -m`\' ... 6 | clang -std=c99 -Wall example3.c -lm -ldl -lpthread -o example3-linux-`uname -m` 7 | echo Done! 8 | -------------------------------------------------------------------------------- /examples/build-osx.sh: -------------------------------------------------------------------------------- 1 | echo Building \'example1-osx-`uname -m`\' ... 2 | clang -std=c99 -Wall example1.c -lm -ldl -lpthread -framework CoreServices -framework CoreAudio -framework AudioUnit -Wno-unknown-warning-option -o example1-osx-`uname -m` 3 | echo Building \'example2-osx-`uname -m`\' ... 4 | clang -std=c99 -Wall example2.c -lm -ldl -lpthread -framework CoreServices -framework CoreAudio -framework AudioUnit -Wno-unknown-warning-option -o example2-osx-`uname -m` 5 | echo Building \'example3-osx-`uname -m`\' ... 6 | clang -std=c99 -Wall example3.c -lm -ldl -lpthread -framework CoreServices -framework CoreAudio -framework AudioUnit -Wno-unknown-warning-option -o example3-osx-`uname -m` 7 | echo Done! 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017-2023 Bernhard Schelling (Based on SFZero, Copyright (C) 2012 Steve Folta, https://github.com/stevefolta/SFZero) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /sfotool/sfotool.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sfotool", "sfotool.vcxproj", "{FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|Win32.Build.0 = Debug|Win32 18 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|x64.ActiveCfg = Debug|x64 19 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|x64.Build.0 = Debug|x64 20 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|Win32.ActiveCfg = Release|Win32 21 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|Win32.Build.0 = Release|Win32 22 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|x64.ActiveCfg = Release|x64 23 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinySoundFont 2 | SoundFont2 synthesizer library in a single C/C++ file 3 | 4 | ## Overview 5 | 6 | TinySoundFont is a software synthesizer using SoundFont2 sound bank files. 7 | 8 | The library is a single C header file so it is extremely simple to integrate in your C/C++ projects. 9 | 10 | ```c++ 11 | #define TSF_IMPLEMENTATION 12 | #include "tsf.h" 13 | 14 | ... 15 | 16 | tsf* TinySoundFont = tsf_load_filename("soundfont.sf2"); 17 | tsf_set_output(TinySoundFont, TSF_MONO, 44100, 0); //sample rate 18 | tsf_note_on(TinySoundFont, 0, 60, 1.0f); //preset 0, middle C 19 | short HalfSecond[22050]; //synthesize 0.5 seconds 20 | tsf_render_short(TinySoundFont, HalfSecond, 22050, 0); 21 | ``` 22 | 23 | The library code is based on [SFZero by Steve Folta](https://github.com/stevefolta/SFZero). 24 | 25 | ## Documentation 26 | 27 | The API documentation can be found on [top of the library source code](https://github.com/schellingb/TinySoundFont/blob/master/tsf.h). 28 | 29 | There are also [examples available](https://github.com/schellingb/TinySoundFont/tree/master/examples) which come with a sample SoundFont file and build and play sound on Win32, Win64, Linux and MacOSX with no further dependencies. 30 | 31 | ## Dependencies 32 | 33 | C standard libraries for fopen, math and malloc (can be removed by providing custom functions with #defines). 34 | 35 | ## License 36 | 37 | TinySoundFont is available under the [MIT license](https://choosealicense.com/licenses/mit/). 38 | -------------------------------------------------------------------------------- /sfotool/README.md: -------------------------------------------------------------------------------- 1 | # SFOTool for TinySoundFont 2 | A tool to create heavily compressed .SFO files from .SF2 SoundFont files. 3 | 4 | ## Purpose 5 | SFO files are just regular SoundFont v2 files with the entire block of raw PCM samples replaced 6 | with a single Ogg Vorbis compressed stream. Unlike .sf3 files, which can have every separate font 7 | sample compressed individually, this will compress the entire sound data as if it were a single 8 | sample. This results in much higher compression than processing samples individually but also 9 | higher loss of quality. 10 | 11 | ## Usage Help 12 | ```sh 13 | sfotool : Show type of sample stream contained (PCM or OGG) 14 | sfotool : Dump PCM sample stream to .WAV file 15 | sfotool : Dump OGG sample stream to .OGG file 16 | sfotool : Write new .SF2 soundfont file using PCM sample stream from .WAV file 17 | sfotool : Write new .SFO soundfont file using OGG sample stream from .OGG file 18 | ``` 19 | 20 | ## Making a .SFO file from a .SF2 file 21 | 1. Dump the PCM data of a .SF2 file to a .WAV file 22 | `sfotool ` 23 | 2. Compress the .WAV file to .OGG (i.e. with [Audacity](https://www.audacityteam.org/download/legacy-windows/)) 24 | Make sure to choose the desired compression quality level 25 | 3. Build the .SFO file from the .SF2 file and the new .OGG 26 | `sfotool ` 27 | 28 | # License 29 | SFOTool is available under the [Unlicense](http://unlicense.org/) (public domain). 30 | -------------------------------------------------------------------------------- /examples/examples.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example1", "example1.vcxproj", "{A47C788B-1BDA-4057-87A9-FC35ED711B44}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example2", "example2.vcxproj", "{78C3A807-00CB-410B-8F75-04910BB3975F}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example3", "example3.vcxproj", "{78C3A807-00CB-410B-8F75-04910BB39760}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Win32 = Debug|Win32 15 | Debug|x64 = Debug|x64 16 | Release|Win32 = Release|Win32 17 | Release|x64 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|Win32.ActiveCfg = Debug|Win32 21 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|Win32.Build.0 = Debug|Win32 22 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|x64.ActiveCfg = Debug|x64 23 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|x64.Build.0 = Debug|x64 24 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|Win32.ActiveCfg = Release|Win32 25 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|Win32.Build.0 = Release|Win32 26 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|x64.ActiveCfg = Release|x64 27 | {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|x64.Build.0 = Release|x64 28 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|Win32.ActiveCfg = Debug|Win32 29 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|Win32.Build.0 = Debug|Win32 30 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|x64.ActiveCfg = Debug|x64 31 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|x64.Build.0 = Debug|x64 32 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|Win32.ActiveCfg = Release|Win32 33 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|Win32.Build.0 = Release|Win32 34 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|x64.ActiveCfg = Release|x64 35 | {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|x64.Build.0 = Release|x64 36 | {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|Win32.ActiveCfg = Debug|Win32 37 | {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|Win32.Build.0 = Debug|Win32 38 | {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|x64.ActiveCfg = Debug|x64 39 | {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|x64.Build.0 = Debug|x64 40 | {78C3A807-00CB-410B-8F75-04910BB39760}.Release|Win32.ActiveCfg = Release|Win32 41 | {78C3A807-00CB-410B-8F75-04910BB39760}.Release|Win32.Build.0 = Release|Win32 42 | {78C3A807-00CB-410B-8F75-04910BB39760}.Release|x64.ActiveCfg = Release|x64 43 | {78C3A807-00CB-410B-8F75-04910BB39760}.Release|x64.Build.0 = Release|x64 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = TRUE 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /examples/example2.c: -------------------------------------------------------------------------------- 1 | #define MINIAUDIO_IMPLEMENTATION 2 | #include "miniaudio_io.h" 3 | 4 | #define TSF_IMPLEMENTATION 5 | #include "../tsf.h" 6 | 7 | // Holds the global instance pointer 8 | static tsf* g_TinySoundFont; 9 | 10 | // A Mutex so we don't call note_on/note_off while rendering audio samples 11 | static ma_mutex g_Mutex; 12 | 13 | // Callback function called by the audio thread 14 | static void AudioCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) 15 | { 16 | // Render the audio samples in float format 17 | ma_mutex_lock(&g_Mutex); //get exclusive lock 18 | tsf_render_float(g_TinySoundFont, (float*)pOutput, (int)frameCount, 0); 19 | ma_mutex_unlock(&g_Mutex); 20 | } 21 | 22 | int main(int argc, char *argv[]) 23 | { 24 | int i, Notes[7] = { 48, 50, 52, 53, 55, 57, 59 }; 25 | 26 | // Define the desired audio output format we request 27 | ma_device device; 28 | ma_device_config deviceConfig; 29 | deviceConfig = ma_device_config_init(ma_device_type_playback); 30 | deviceConfig.playback.format = ma_format_f32; 31 | deviceConfig.playback.channels = 2; 32 | deviceConfig.sampleRate = 44100; 33 | deviceConfig.dataCallback = AudioCallback; 34 | 35 | // Initialize the audio system 36 | if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) 37 | { 38 | fprintf(stderr, "Could not initialize audio hardware or driver\n"); 39 | return 1; 40 | } 41 | 42 | // Load the SoundFont from a file 43 | g_TinySoundFont = tsf_load_filename("florestan-subset.sf2"); 44 | if (!g_TinySoundFont) 45 | { 46 | fprintf(stderr, "Could not load SoundFont\n"); 47 | return 1; 48 | } 49 | 50 | // Set the SoundFont rendering output mode 51 | tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, (int)deviceConfig.sampleRate, 0); 52 | 53 | // Create the mutex 54 | ma_mutex_init(&g_Mutex); 55 | 56 | // Start the actual audio playback here 57 | // The audio thread will begin to call our AudioCallback function 58 | if (ma_device_start(&device) != MA_SUCCESS) 59 | { 60 | fprintf(stderr, "Failed to start playback device.\n"); 61 | ma_device_uninit(&device); 62 | return 1; 63 | } 64 | 65 | // Loop through all the presets in the loaded SoundFont 66 | for (i = 0; i < tsf_get_presetcount(g_TinySoundFont); i++) 67 | { 68 | //Get exclusive mutex lock, end the previous note and play a new note 69 | printf("Play note %d with preset #%d '%s'\n", Notes[i % 7], i, tsf_get_presetname(g_TinySoundFont, i)); 70 | ma_mutex_lock(&g_Mutex); 71 | tsf_note_off(g_TinySoundFont, i - 1, Notes[(i - 1) % 7]); 72 | tsf_note_on(g_TinySoundFont, i, Notes[i % 7], 1.0f); 73 | ma_mutex_unlock(&g_Mutex); 74 | ma_sleep(1000); 75 | } 76 | 77 | ma_device_uninit(&device); 78 | 79 | // We could call tsf_close(g_TinySoundFont) and ma_mutex_uninit(&g_Mutex) 80 | // here to free the memory and resources but we just let the OS clean up 81 | // because the process ends here. 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /examples/example1.c: -------------------------------------------------------------------------------- 1 | #define TSF_IMPLEMENTATION 2 | #include "../tsf.h" 3 | 4 | #define MINIAUDIO_IMPLEMENTATION 5 | #include "miniaudio_io.h" 6 | 7 | //This is a minimal SoundFont with a single loopin saw-wave sample/instrument/preset (484 bytes) 8 | const static unsigned char MinimalSoundFont[] = 9 | { 10 | #define TEN0 0,0,0,0,0,0,0,0,0,0 11 | 'R','I','F','F',220,1,0,0,'s','f','b','k', 12 | 'L','I','S','T',88,1,0,0,'p','d','t','a', 13 | 'p','h','d','r',76,TEN0,TEN0,TEN0,TEN0,0,0,0,0,TEN0,0,0,0,0,0,0,0,255,0,255,0,1,TEN0,0,0,0, 14 | 'p','b','a','g',8,0,0,0,0,0,0,0,1,0,0,0,'p','m','o','d',10,TEN0,0,0,0,'p','g','e','n',8,0,0,0,41,0,0,0,0,0,0,0, 15 | 'i','n','s','t',44,TEN0,TEN0,0,0,0,0,0,0,0,0,TEN0,0,0,0,0,0,0,0,1,0, 16 | 'i','b','a','g',8,0,0,0,0,0,0,0,2,0,0,0,'i','m','o','d',10,TEN0,0,0,0, 17 | 'i','g','e','n',12,0,0,0,54,0,1,0,53,0,0,0,0,0,0,0, 18 | 's','h','d','r',92,TEN0,TEN0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,49,0,0,0,34,86,0,0,60,0,0,0,1,TEN0,TEN0,TEN0,TEN0,0,0,0,0,0,0,0, 19 | 'L','I','S','T',112,0,0,0,'s','d','t','a','s','m','p','l',100,0,0,0,86,0,119,3,31,7,147,10,43,14,169,17,58,21,189,24,73,28,204,31,73,35,249,38,46,42,71,46,250,48,150,53,242,55,126,60,151,63,108,66,126,72,207, 20 | 70,86,83,100,72,74,100,163,39,241,163,59,175,59,179,9,179,134,187,6,186,2,194,5,194,15,200,6,202,96,206,159,209,35,213,213,216,45,220,221,223,76,227,221,230,91,234,242,237,105,241,8,245,118,248,32,252 21 | }; 22 | 23 | // Holds the global instance pointer 24 | static tsf* g_TinySoundFont; 25 | 26 | // Callback function called by the audio thread 27 | static void AudioCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) 28 | { 29 | // Note we don't do any thread concurrency control here because in this 30 | // example all notes are started before the audio playback begins. 31 | // If you do play notes while the audio thread renders output you 32 | // will need a mutex of some sort. 33 | tsf_render_short(g_TinySoundFont, (short*)pOutput, (int)frameCount, 0); 34 | } 35 | 36 | int main(int argc, char *argv[]) 37 | { 38 | // Define the desired audio output format we request 39 | ma_device device; 40 | ma_device_config deviceConfig; 41 | deviceConfig = ma_device_config_init(ma_device_type_playback); 42 | deviceConfig.playback.format = ma_format_s16; 43 | deviceConfig.playback.channels = 2; 44 | deviceConfig.sampleRate = 44100; 45 | deviceConfig.dataCallback = AudioCallback; 46 | 47 | // Initialize the audio system 48 | if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) 49 | { 50 | fprintf(stderr, "Could not initialize audio hardware or driver\n"); 51 | return 1; 52 | } 53 | 54 | // Load the SoundFont from the memory block 55 | g_TinySoundFont = tsf_load_memory(MinimalSoundFont, sizeof(MinimalSoundFont)); 56 | if (!g_TinySoundFont) 57 | { 58 | fprintf(stderr, "Could not load SoundFont\n"); 59 | return 1; 60 | } 61 | 62 | // Set the rendering output mode to 44.1khz and -10 decibel gain 63 | tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, (int)deviceConfig.sampleRate, -10); 64 | 65 | // Start two notes before starting the audio playback 66 | tsf_note_on(g_TinySoundFont, 0, 48, 1.0f); //C2 67 | tsf_note_on(g_TinySoundFont, 0, 52, 1.0f); //E2 68 | 69 | // Start the actual audio playback here 70 | // The audio thread will begin to call our AudioCallback function 71 | if (ma_device_start(&device) != MA_SUCCESS) 72 | { 73 | fprintf(stderr, "Failed to start playback device.\n"); 74 | ma_device_uninit(&device); 75 | return 1; 76 | } 77 | 78 | // Let the audio callback play some sound for 3 seconds 79 | ma_sleep(3000); 80 | 81 | ma_device_uninit(&device); 82 | 83 | // We could call tsf_close(g_TinySoundFont) 84 | // here to free the memory and resources but we just let the OS clean up 85 | // because the process ends here. 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /examples/example1.dsp: -------------------------------------------------------------------------------- 1 | # Microsoft Developer Studio Project File - Name="example1" - Package Owner=<4> 2 | # Microsoft Developer Studio Generated Build File, Format Version 6.00 3 | 4 | # TARGTYPE "Win32 (x86) Console Application" 0x0103 5 | 6 | CFG=example1 - Win32 Debug 7 | !MESSAGE "example1 - Win32 Release" (based on "Win32 (x86) Console Application") 8 | !MESSAGE "example1 - Win32 Debug" (based on "Win32 (x86) Console Application") 9 | 10 | # Begin Project 11 | # PROP AllowPerConfigDependencies 0 12 | # PROP Scc_ProjName "" 13 | # PROP Scc_LocalPath "" 14 | CPP=cl.exe 15 | RSC=rc.exe 16 | 17 | !IF "$(CFG)" == "example1 - Win32 Release" 18 | 19 | # PROP BASE Use_MFC 0 20 | # PROP BASE Use_Debug_Libraries 0 21 | # PROP BASE Output_Dir "Release" 22 | # PROP BASE Intermediate_Dir "Release" 23 | # PROP BASE Target_Dir "" 24 | # PROP Use_MFC 0 25 | # PROP Use_Debug_Libraries 0 26 | # PROP Output_Dir "Release" 27 | # PROP Intermediate_Dir "Release" 28 | # PROP Ignore_Export_Lib 0 29 | # PROP Target_Dir "" 30 | # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 31 | # ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 32 | # ADD BASE RSC /l 0x807 /d "NDEBUG" 33 | # ADD RSC /l 0x807 /d "NDEBUG" 34 | BSC32=bscmake.exe 35 | # ADD BASE BSC32 /nologo 36 | # ADD BSC32 /nologo 37 | LINK32=link.exe 38 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 39 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 40 | 41 | !ELSEIF "$(CFG)" == "example1 - Win32 Debug" 42 | 43 | # PROP BASE Use_MFC 0 44 | # PROP BASE Use_Debug_Libraries 1 45 | # PROP BASE Output_Dir "Debug" 46 | # PROP BASE Intermediate_Dir "Debug" 47 | # PROP BASE Target_Dir "" 48 | # PROP Use_MFC 0 49 | # PROP Use_Debug_Libraries 1 50 | # PROP Output_Dir "Debug" 51 | # PROP Intermediate_Dir "Debug" 52 | # PROP Ignore_Export_Lib 0 53 | # PROP Target_Dir "" 54 | # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 55 | # ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 56 | # ADD BASE RSC /l 0x807 /d "_DEBUG" 57 | # ADD RSC /l 0x807 /d "_DEBUG" 58 | BSC32=bscmake.exe 59 | # ADD BASE BSC32 /nologo 60 | # ADD BSC32 /nologo 61 | LINK32=link.exe 62 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 63 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 64 | 65 | !ENDIF 66 | 67 | # Begin Target 68 | # Name "example1 - Win32 Release" 69 | # Name "example1 - Win32 Debug" 70 | # Begin Source File 71 | SOURCE=example1.c 72 | # End Source File 73 | # Begin Source File 74 | SOURCE=minisdl_audio.c 75 | # End Source File 76 | # End Target 77 | # End Project 78 | -------------------------------------------------------------------------------- /examples/example2.dsp: -------------------------------------------------------------------------------- 1 | # Microsoft Developer Studio Project File - Name="example2" - Package Owner=<4> 2 | # Microsoft Developer Studio Generated Build File, Format Version 6.00 3 | 4 | # TARGTYPE "Win32 (x86) Console Application" 0x0103 5 | 6 | CFG=example2 - Win32 Debug 7 | !MESSAGE "example2 - Win32 Release" (based on "Win32 (x86) Console Application") 8 | !MESSAGE "example2 - Win32 Debug" (based on "Win32 (x86) Console Application") 9 | 10 | # Begin Project 11 | # PROP AllowPerConfigDependencies 0 12 | # PROP Scc_ProjName "" 13 | # PROP Scc_LocalPath "" 14 | CPP=cl.exe 15 | RSC=rc.exe 16 | 17 | !IF "$(CFG)" == "example2 - Win32 Release" 18 | 19 | # PROP BASE Use_MFC 0 20 | # PROP BASE Use_Debug_Libraries 0 21 | # PROP BASE Output_Dir "Release" 22 | # PROP BASE Intermediate_Dir "Release" 23 | # PROP BASE Target_Dir "" 24 | # PROP Use_MFC 0 25 | # PROP Use_Debug_Libraries 0 26 | # PROP Output_Dir "Release" 27 | # PROP Intermediate_Dir "Release" 28 | # PROP Ignore_Export_Lib 0 29 | # PROP Target_Dir "" 30 | # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 31 | # ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 32 | # ADD BASE RSC /l 0x807 /d "NDEBUG" 33 | # ADD RSC /l 0x807 /d "NDEBUG" 34 | BSC32=bscmake.exe 35 | # ADD BASE BSC32 /nologo 36 | # ADD BSC32 /nologo 37 | LINK32=link.exe 38 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 39 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 40 | 41 | !ELSEIF "$(CFG)" == "example2 - Win32 Debug" 42 | 43 | # PROP BASE Use_MFC 0 44 | # PROP BASE Use_Debug_Libraries 1 45 | # PROP BASE Output_Dir "Debug" 46 | # PROP BASE Intermediate_Dir "Debug" 47 | # PROP BASE Target_Dir "" 48 | # PROP Use_MFC 0 49 | # PROP Use_Debug_Libraries 1 50 | # PROP Output_Dir "Debug" 51 | # PROP Intermediate_Dir "Debug" 52 | # PROP Ignore_Export_Lib 0 53 | # PROP Target_Dir "" 54 | # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 55 | # ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 56 | # ADD BASE RSC /l 0x807 /d "_DEBUG" 57 | # ADD RSC /l 0x807 /d "_DEBUG" 58 | BSC32=bscmake.exe 59 | # ADD BASE BSC32 /nologo 60 | # ADD BSC32 /nologo 61 | LINK32=link.exe 62 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 63 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 64 | 65 | !ENDIF 66 | 67 | # Begin Target 68 | # Name "example2 - Win32 Release" 69 | # Name "example2 - Win32 Debug" 70 | # Begin Source File 71 | SOURCE=example2.c 72 | # End Source File 73 | # Begin Source File 74 | SOURCE=minisdl_audio.c 75 | # End Source File 76 | # End Target 77 | # End Project 78 | -------------------------------------------------------------------------------- /examples/example3.dsp: -------------------------------------------------------------------------------- 1 | # Microsoft Developer Studio Project File - Name="example3" - Package Owner=<4> 2 | # Microsoft Developer Studio Generated Build File, Format Version 6.00 3 | 4 | # TARGTYPE "Win32 (x86) Console Application" 0x0103 5 | 6 | CFG=example3 - Win32 Debug 7 | !MESSAGE "example3 - Win32 Release" (based on "Win32 (x86) Console Application") 8 | !MESSAGE "example3 - Win32 Debug" (based on "Win32 (x86) Console Application") 9 | 10 | # Begin Project 11 | # PROP AllowPerConfigDependencies 0 12 | # PROP Scc_ProjName "" 13 | # PROP Scc_LocalPath "" 14 | CPP=cl.exe 15 | RSC=rc.exe 16 | 17 | !IF "$(CFG)" == "example3 - Win32 Release" 18 | 19 | # PROP BASE Use_MFC 0 20 | # PROP BASE Use_Debug_Libraries 0 21 | # PROP BASE Output_Dir "Release" 22 | # PROP BASE Intermediate_Dir "Release" 23 | # PROP BASE Target_Dir "" 24 | # PROP Use_MFC 0 25 | # PROP Use_Debug_Libraries 0 26 | # PROP Output_Dir "Release" 27 | # PROP Intermediate_Dir "Release" 28 | # PROP Ignore_Export_Lib 0 29 | # PROP Target_Dir "" 30 | # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 31 | # ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 32 | # ADD BASE RSC /l 0x807 /d "NDEBUG" 33 | # ADD RSC /l 0x807 /d "NDEBUG" 34 | BSC32=bscmake.exe 35 | # ADD BASE BSC32 /nologo 36 | # ADD BSC32 /nologo 37 | LINK32=link.exe 38 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 39 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 40 | 41 | !ELSEIF "$(CFG)" == "example3 - Win32 Debug" 42 | 43 | # PROP BASE Use_MFC 0 44 | # PROP BASE Use_Debug_Libraries 1 45 | # PROP BASE Output_Dir "Debug" 46 | # PROP BASE Intermediate_Dir "Debug" 47 | # PROP BASE Target_Dir "" 48 | # PROP Use_MFC 0 49 | # PROP Use_Debug_Libraries 1 50 | # PROP Output_Dir "Debug" 51 | # PROP Intermediate_Dir "Debug" 52 | # PROP Ignore_Export_Lib 0 53 | # PROP Target_Dir "" 54 | # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 55 | # ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 56 | # ADD BASE RSC /l 0x807 /d "_DEBUG" 57 | # ADD RSC /l 0x807 /d "_DEBUG" 58 | BSC32=bscmake.exe 59 | # ADD BASE BSC32 /nologo 60 | # ADD BSC32 /nologo 61 | LINK32=link.exe 62 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 63 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 64 | 65 | !ENDIF 66 | 67 | # Begin Target 68 | # Name "example3 - Win32 Release" 69 | # Name "example3 - Win32 Debug" 70 | # Begin Source File 71 | SOURCE=example3.c 72 | # End Source File 73 | # Begin Source File 74 | SOURCE=minisdl_audio.c 75 | # End Source File 76 | # End Target 77 | # End Project 78 | -------------------------------------------------------------------------------- /examples/example3.c: -------------------------------------------------------------------------------- 1 | #define MINIAUDIO_IMPLEMENTATION 2 | #include "miniaudio_io.h" 3 | 4 | #define TSF_IMPLEMENTATION 5 | #include "../tsf.h" 6 | 7 | #define TML_IMPLEMENTATION 8 | #include "../tml.h" 9 | 10 | // Holds the global instance pointer 11 | static tsf* g_TinySoundFont; 12 | 13 | // Holds global MIDI playback state 14 | static double g_Msec; //current playback time 15 | static tml_message* g_MidiMessage; //next message to be played 16 | 17 | // Callback function called by the audio thread 18 | static void AudioCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) 19 | { 20 | float* stream = (float*)pOutput; 21 | for (ma_uint32 SampleBlock = TSF_RENDER_EFFECTSAMPLEBLOCK; frameCount; frameCount -= SampleBlock, stream += SampleBlock * 2) // 2 channel output 22 | { 23 | //We progress the MIDI playback and then process TSF_RENDER_EFFECTSAMPLEBLOCK samples at once 24 | if (SampleBlock > frameCount) SampleBlock = frameCount; 25 | 26 | //Loop through all MIDI messages which need to be played up until the current playback time 27 | for (g_Msec += SampleBlock * (1000.0 / 44100.0); g_MidiMessage && g_Msec >= g_MidiMessage->time; g_MidiMessage = g_MidiMessage->next) 28 | { 29 | switch (g_MidiMessage->type) 30 | { 31 | case TML_PROGRAM_CHANGE: //channel program (preset) change (special handling for 10th MIDI channel with drums) 32 | tsf_channel_set_presetnumber(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->program, (g_MidiMessage->channel == 9)); 33 | break; 34 | case TML_NOTE_ON: //play a note 35 | tsf_channel_note_on(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->key, g_MidiMessage->velocity / 127.0f); 36 | break; 37 | case TML_NOTE_OFF: //stop a note 38 | tsf_channel_note_off(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->key); 39 | break; 40 | case TML_PITCH_BEND: //pitch wheel modification 41 | tsf_channel_set_pitchwheel(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->pitch_bend); 42 | break; 43 | case TML_CONTROL_CHANGE: //MIDI controller messages 44 | tsf_channel_midi_control(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->control, g_MidiMessage->control_value); 45 | break; 46 | } 47 | } 48 | 49 | // Render the block of audio samples in float format 50 | tsf_render_float(g_TinySoundFont, stream, (int)SampleBlock, 0); 51 | } 52 | } 53 | 54 | int main(int argc, char *argv[]) 55 | { 56 | // This implements a small program that you can launch without 57 | // parameters for a default file & soundfont, or with these arguments: 58 | // 59 | // ./example3-... .mid .sf2 60 | 61 | tml_message* TinyMidiLoader = NULL; 62 | 63 | // Define the desired audio output format we request 64 | ma_device device; 65 | ma_device_config deviceConfig; 66 | deviceConfig = ma_device_config_init(ma_device_type_playback); 67 | deviceConfig.playback.format = ma_format_f32; 68 | deviceConfig.playback.channels = 2; 69 | deviceConfig.sampleRate = 44100; 70 | deviceConfig.dataCallback = AudioCallback; 71 | 72 | // Initialize the audio system 73 | if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) 74 | { 75 | fprintf(stderr, "Could not initialize audio hardware or driver\n"); 76 | return 1; 77 | } 78 | 79 | //Venture (Original WIP) by Ximon 80 | //https://musescore.com/user/2391686/scores/841451 81 | //License: Creative Commons copyright waiver (CC0) 82 | TinyMidiLoader = tml_load_filename((argc >= 2 ? argv[1] : "venture.mid")); 83 | if (!TinyMidiLoader) 84 | { 85 | fprintf(stderr, "Could not load MIDI file\n"); 86 | return 1; 87 | } 88 | 89 | //Set up the global MidiMessage pointer to the first MIDI message 90 | g_MidiMessage = TinyMidiLoader; 91 | 92 | // Load the SoundFont from a file 93 | g_TinySoundFont = tsf_load_filename( 94 | (argc >= 3 ? argv[2] : "florestan-subset.sf2") 95 | ); 96 | if (!g_TinySoundFont) 97 | { 98 | fprintf(stderr, "Could not load SoundFont\n"); 99 | return 1; 100 | } 101 | 102 | //Initialize preset on special 10th MIDI channel to use percussion sound bank (128) if available 103 | tsf_channel_set_bank_preset(g_TinySoundFont, 9, 128, 0); 104 | 105 | // Set the SoundFont rendering output mode 106 | tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, (int)deviceConfig.sampleRate, -10.0f); 107 | 108 | // Start the actual audio playback here 109 | // The audio thread will begin to call our AudioCallback function 110 | if (ma_device_start(&device) != MA_SUCCESS) 111 | { 112 | fprintf(stderr, "Failed to start playback device.\n"); 113 | ma_device_uninit(&device); 114 | return 1; 115 | } 116 | 117 | // Wait until the entire MIDI file has been played back (until the end of the linked message list is reached) 118 | while (g_MidiMessage != NULL) ma_sleep(100); 119 | 120 | ma_device_uninit(&device); 121 | 122 | // We could call tsf_close(g_TinySoundFont) and tml_free(TinyMidiLoader) 123 | // here to free the memory and resources but we just let the OS clean up 124 | // because the process ends here. 125 | return 0; 126 | } 127 | -------------------------------------------------------------------------------- /examples/example1.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {A47C788B-1BDA-4057-87A9-FC35ED711B44} 23 | Win32Proj 24 | example1 25 | example1 26 | 27 | 28 | 29 | Application 30 | v110 31 | v120 32 | v140 33 | v141 34 | v142 35 | MultiByte 36 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 37 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 38 | 39 | 40 | true 41 | 42 | 43 | false 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | Level3 62 | true 63 | false 64 | false 65 | false 66 | 67 | 68 | Console 69 | true 70 | 71 | 72 | 73 | 74 | Disabled 75 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 76 | MultiThreadedDebug 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | MaxSpeed 85 | true 86 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 87 | MultiThreaded 88 | /Gw %(AdditionalOptions) 89 | 90 | 91 | true 92 | true 93 | false 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /examples/example2.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {78C3A807-00CB-410B-8F75-04910BB3975F} 23 | Win32Proj 24 | example2 25 | example2 26 | 27 | 28 | 29 | Application 30 | v110 31 | v120 32 | v140 33 | v141 34 | v142 35 | MultiByte 36 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 37 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 38 | 39 | 40 | true 41 | 42 | 43 | false 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | Level3 62 | true 63 | false 64 | false 65 | false 66 | 67 | 68 | Console 69 | true 70 | 71 | 72 | 73 | 74 | Disabled 75 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 76 | MultiThreadedDebug 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | MaxSpeed 85 | true 86 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 87 | MultiThreaded 88 | /Gw %(AdditionalOptions) 89 | 90 | 91 | true 92 | true 93 | false 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /examples/example3.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {78C3A807-00CB-410B-8F75-04910BB39760} 23 | Win32Proj 24 | example3 25 | example3 26 | 27 | 28 | 29 | Application 30 | v110 31 | v120 32 | v140 33 | v141 34 | v142 35 | MultiByte 36 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 37 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 38 | 39 | 40 | true 41 | 42 | 43 | false 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | Level3 62 | true 63 | false 64 | false 65 | false 66 | 67 | 68 | Console 69 | true 70 | 71 | 72 | 73 | 74 | Disabled 75 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 76 | MultiThreadedDebug 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | MaxSpeed 85 | true 86 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 87 | MultiThreaded 88 | /Gw %(AdditionalOptions) 89 | 90 | 91 | true 92 | true 93 | false 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /sfotool/sfotool.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF} 23 | Win32Proj 24 | sfotool 25 | sfotool 26 | 27 | 28 | 29 | Application 30 | v110_xp 31 | v120 32 | v140 33 | v141 34 | v142 35 | v143 36 | $(DefaultPlatformToolset) 37 | false 38 | MultiByte 39 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 40 | $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ 41 | 42 | 43 | true 44 | 45 | 46 | false 47 | true 48 | 49 | 50 | 51 | 52 | 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | Level3 62 | true 63 | false 64 | false 65 | false 66 | 67 | 68 | Console 69 | true 70 | 71 | 72 | 73 | 74 | Disabled 75 | WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) 76 | MultiThreadedDebug 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | MaxSpeed 85 | true 86 | WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) 87 | MultiThreaded 88 | /Gw %(AdditionalOptions) 89 | 90 | 91 | true 92 | true 93 | false 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /sfotool/main.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------// 2 | // SFOTool // 3 | // License: Public Domain (www.unlicense.org) // 4 | //--------------------------------------------// 5 | 6 | #include 7 | #include 8 | 9 | typedef char sfo_fourcc[4]; 10 | #define SFO_FourCCEquals(a, b) (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]) 11 | struct sfo_riffchunk { sfo_fourcc id; unsigned int size; }; 12 | struct sfo_wavheader 13 | { 14 | char RIFF[4]; unsigned int ChunkSize; char WAVE[4], fmt[4]; unsigned int Subchunk1Size; 15 | unsigned short AudioFormat,NumOfChan; unsigned int SamplesPerSec, bytesPerSec; 16 | unsigned short blockAlign, bitsPerSample; char Subchunk2ID[4]; unsigned int Subchunk2Size; 17 | }; 18 | 19 | static void sfo_copy(FILE* src, FILE* trg, unsigned int size) 20 | { 21 | unsigned int block; 22 | unsigned char buf[512]; 23 | for (; size; size -= block) 24 | { 25 | block = (size > sizeof(buf) ? sizeof(buf) : size); 26 | fread(buf, 1, block, src); 27 | fwrite(buf, 1, block, trg); 28 | } 29 | } 30 | 31 | static int sfo_riffchunk_read(struct sfo_riffchunk* parent, struct sfo_riffchunk* chunk, FILE* f) 32 | { 33 | int is_riff, is_list; 34 | if (parent && sizeof(sfo_fourcc) + sizeof(unsigned int) > parent->size) return 0; 35 | if (!fread(&chunk->id, sizeof(sfo_fourcc), 1, f) || *chunk->id <= ' ' || *chunk->id >= 'z') return 0; 36 | if (!fread(&chunk->size, sizeof(unsigned int), 1, f)) return 0; 37 | if (parent && sizeof(sfo_fourcc) + sizeof(unsigned int) + chunk->size > parent->size) return 0; 38 | if (parent) parent->size -= sizeof(sfo_fourcc) + sizeof(unsigned int) + chunk->size; 39 | is_riff = SFO_FourCCEquals(chunk->id, "RIFF"), is_list = SFO_FourCCEquals(chunk->id, "LIST"); 40 | if (is_riff && parent) return 0; /* not allowed */ 41 | if (!is_riff && !is_list) return 1; /* custom type without sub type */ 42 | if (!fread(&chunk->id, sizeof(sfo_fourcc), 1, f) || *chunk->id <= ' ' || *chunk->id >= 'z') return 0; 43 | chunk->size -= sizeof(sfo_fourcc); 44 | return 1; 45 | } 46 | 47 | int main(int argc, const char** argv) 48 | { 49 | const char* arg_sf_in = (argc > 1 ? argv[1] : NULL); 50 | const char* arg_smpl = (argc > 2 ? argv[2] : NULL); 51 | const char* arg_sf_out = (argc > 3 ? argv[3] : NULL); 52 | char ext_sf_in = (arg_sf_in ? arg_sf_in [strlen(arg_sf_in)-1] | 0x20 : '\0'); 53 | char ext_smpl = (arg_smpl ? arg_smpl [strlen(arg_smpl)-1] | 0x20 : '\0'); 54 | char ext_sf_out = (arg_sf_out ? arg_sf_out[strlen(arg_sf_out)-1] | 0x20 : '\0'); 55 | struct sfo_riffchunk chunkHead, chunkList, chunk; 56 | FILE* f_sf_in = NULL, *f_smpl = NULL, *f_sf_out = NULL; 57 | 58 | if (argc < 2 || argc > 4) 59 | { 60 | print_usage: 61 | fprintf(stderr, "Usage Help:\n"); 62 | fprintf(stderr, "%s : Show type of sample stream contained (PCM or OGG)\n", argv[0]); 63 | fprintf(stderr, "%s : Dump PCM sample stream to .WAV file\n", argv[0]); 64 | fprintf(stderr, "%s : Dump OGG sample stream to .OGG file\n", argv[0]); 65 | fprintf(stderr, "%s : Write new .SF2 soundfont file using PCM sample stream from .WAV file\n", argv[0]); 66 | fprintf(stderr, "%s : Write new .SFO soundfont file using OGG sample stream from .OGG file\n", argv[0]); 67 | if (f_sf_in) fclose(f_sf_in); 68 | if (f_smpl) fclose(f_smpl); 69 | if (f_sf_out) fclose(f_sf_out); 70 | return 1; 71 | } 72 | 73 | f_sf_in = fopen(arg_sf_in, "rb"); 74 | if (!f_sf_in) { fprintf(stderr, "Error: Passed input file '%s' does not exist\n\n", arg_sf_in); goto print_usage; } 75 | 76 | if (!sfo_riffchunk_read(NULL, &chunkHead, f_sf_in) || !SFO_FourCCEquals(chunkHead.id, "sfbk")) 77 | { 78 | fprintf(stderr, "Error: Passed input file '%s' is not a valid soundfont file\n\n", arg_sf_in); 79 | goto print_usage; 80 | } 81 | while (sfo_riffchunk_read(&chunkHead, &chunkList, f_sf_in)) 82 | { 83 | unsigned int pos_listsize = (unsigned int)ftell(f_sf_in) - 8; 84 | if (!SFO_FourCCEquals(chunkList.id, "sdta")) 85 | { 86 | fseek(f_sf_in, chunkList.size, SEEK_CUR); 87 | continue; 88 | } 89 | for (; sfo_riffchunk_read(&chunkList, &chunk, f_sf_in); fseek(f_sf_in, chunkList.size, SEEK_CUR)) 90 | { 91 | int is_pcm = SFO_FourCCEquals(chunk.id, "smpl"); 92 | if (!is_pcm && !SFO_FourCCEquals(chunk.id, "smpo")) 93 | continue; 94 | 95 | printf("Soundfont file '%s' contains a %s sample stream\n", arg_sf_in, (is_pcm ? "PCM" : "OGG")); 96 | if (ext_sf_in != '2' && ext_sf_in != 'o') printf(" Warning: Soundfont file has unknown file extension (should be .SF2 or .SFO)\n"); 97 | if (ext_sf_in == '2' && !is_pcm) printf(" Warning: Soundfont file has .SF%c extension but sample stream is %s\n", '2', "OGG (should be .SFO)"); 98 | if (ext_sf_in == 'o' && is_pcm) printf(" Warning: Soundfont file has .SF%c extension but sample stream is %s\n", 'O', "PCM (should be .SF2)"); 99 | if (arg_sf_out) 100 | { 101 | unsigned int pos_smpchunk, end_smpchunk, len_smpl, end_sf, len_list_in, len_list_out; 102 | 103 | printf("Writing file '%s' with samples from '%s'\n", arg_sf_out, arg_smpl); 104 | if (ext_sf_out != '2' && ext_sf_out != 'o') printf(" Warning: Soundfont file has unknown file extension (should be .SF2 or .SFO)\n"); 105 | if (ext_smpl != 'v' && ext_smpl != 'g') printf(" Warning: Sample file has unknown file extension (should be .WAV or .OGG)\n"); 106 | if (ext_sf_out == '2' && ext_smpl != 'v') printf(" Warning: Soundfont file has .SF%c extension but sample file is .%s\n", '2', "OGG"); 107 | if (ext_sf_out == 'o' && ext_smpl == 'v') printf(" Warning: Soundfont file has .SF%c extension but sample file is .%s\n", 'O', "WAV"); 108 | 109 | f_smpl = fopen(arg_smpl, "rb"); 110 | if (!f_smpl) { fprintf(stderr, "Error: Unable to open input file '%s'\n\n", arg_smpl); goto print_usage; } 111 | 112 | if (ext_smpl == 'v') 113 | { 114 | struct sfo_wavheader wav_hdr; 115 | fread(&wav_hdr, sizeof(wav_hdr), 1, f_smpl); 116 | if (!SFO_FourCCEquals(wav_hdr.Subchunk2ID, "data") || !SFO_FourCCEquals(wav_hdr.RIFF, "RIFF") 117 | || !SFO_FourCCEquals(wav_hdr.WAVE, "WAVE") || !SFO_FourCCEquals(wav_hdr.fmt, "fmt ") 118 | || wav_hdr.Subchunk1Size != 16 || wav_hdr.AudioFormat != 1 || wav_hdr.NumOfChan != 1 119 | || wav_hdr.bytesPerSec != wav_hdr.SamplesPerSec * sizeof(short) || wav_hdr.bitsPerSample != sizeof(short) * 8) 120 | { fprintf(stderr, "Input .WAV file is not a valid raw PCM encoded wave file\n\n"); goto print_usage; } 121 | 122 | len_smpl = wav_hdr.Subchunk2Size; 123 | } 124 | else 125 | { 126 | fseek(f_smpl, 0, SEEK_END); 127 | len_smpl = (unsigned int)ftell(f_smpl); 128 | fseek(f_smpl, 0, SEEK_SET); 129 | } 130 | 131 | f_sf_out = fopen(arg_sf_out, "wb"); 132 | if (!f_sf_out) { fprintf(stderr, "Error: Unable to open output file '%s'\n\n", arg_sf_out); goto print_usage; } 133 | 134 | pos_smpchunk = (unsigned int)(ftell(f_sf_in) - sizeof(struct sfo_riffchunk)); 135 | end_smpchunk = pos_smpchunk + (unsigned int)sizeof(struct sfo_riffchunk) + chunk.size; 136 | fseek(f_sf_in, 0, SEEK_END); 137 | end_sf = (unsigned int)ftell(f_sf_in); 138 | 139 | /* Write data before list chunk size */ 140 | fseek(f_sf_in, 0, SEEK_SET); 141 | sfo_copy(f_sf_in, f_sf_out, pos_listsize); 142 | 143 | /* Write new list chunk size */ 144 | fread(&len_list_in, 4, 1, f_sf_in); 145 | len_list_out = len_list_in - chunk.size + len_smpl; 146 | fwrite(&len_list_out, 4, 1, f_sf_out); 147 | 148 | /* Write data until sample chunk */ 149 | sfo_copy(f_sf_in, f_sf_out, pos_smpchunk - pos_listsize - 4); 150 | 151 | /* Write sample chunk */ 152 | fwrite((ext_smpl == 'v' ? "smpl" : "smpo"), 4, 1, f_sf_out); 153 | fwrite(&len_smpl, 4, 1, f_sf_out); 154 | sfo_copy(f_smpl, f_sf_out, len_smpl); 155 | fclose(f_smpl); 156 | 157 | /* Write data after sample chunk */ 158 | fseek(f_sf_in, end_smpchunk, SEEK_SET); 159 | sfo_copy(f_sf_in, f_sf_out, end_sf - end_smpchunk); 160 | fclose(f_sf_out); 161 | } 162 | else if (arg_smpl) 163 | { 164 | f_smpl = fopen(arg_smpl, "wb"); 165 | printf("Writing file '%s' with %s sample stream\n", arg_smpl, (is_pcm ? "PCM" : "OGG")); 166 | if (ext_smpl != 'v' && ext_smpl != 'g') printf(" Warning: Sample file has unknown file extension (should be .WAV or .OGG)\n"); 167 | if (ext_smpl == 'v' && !is_pcm) printf(" Warning: Sample file has .%s extension but sample stream is %s\n", "WAV", "OGG (should be .OGG)"); 168 | if (ext_smpl == 'g' && is_pcm) printf(" Warning: Sample file has .%s extension but sample stream is %s\n", "OGG", "PCM (should be .WAV)"); 169 | if (!f_smpl) { fprintf(stderr, "Unable to open output file '%s'\n\n", arg_smpl); goto print_usage; } 170 | 171 | if (is_pcm) 172 | { 173 | struct sfo_wavheader wav_hdr; 174 | memcpy(wav_hdr.Subchunk2ID, "data", 4); 175 | memcpy(wav_hdr.RIFF, "RIFF", 4); 176 | memcpy(wav_hdr.WAVE, "WAVE", 4); 177 | memcpy(wav_hdr.fmt, "fmt ", 4); 178 | wav_hdr.Subchunk1Size = 16; 179 | wav_hdr.AudioFormat = 1; 180 | wav_hdr.NumOfChan = 1; 181 | wav_hdr.Subchunk2Size = (unsigned int)chunk.size; 182 | wav_hdr.ChunkSize = sizeof(wav_hdr) - 4 - 4 + wav_hdr.Subchunk2Size; 183 | wav_hdr.SamplesPerSec = 22050; 184 | wav_hdr.bytesPerSec = 22050 * sizeof(short); 185 | wav_hdr.blockAlign = 1 * sizeof(short); 186 | wav_hdr.bitsPerSample = sizeof(short) * 8; 187 | fwrite(&wav_hdr, sizeof(wav_hdr), 1, f_smpl); 188 | } 189 | sfo_copy(f_sf_in, f_smpl, chunk.size); 190 | fclose(f_smpl); 191 | printf("DONE\n"); 192 | } 193 | fclose(f_sf_in); 194 | return 0; 195 | } 196 | } 197 | 198 | fprintf(stderr, "Passed input file is not a valid soundfont file\n\n"); 199 | goto print_usage; 200 | } 201 | -------------------------------------------------------------------------------- /tml.h: -------------------------------------------------------------------------------- 1 | /* TinyMidiLoader - v0.7 - Minimalistic midi parsing library - https://github.com/schellingb/TinySoundFont 2 | no warranty implied; use at your own risk 3 | Do this: 4 | #define TML_IMPLEMENTATION 5 | before you include this file in *one* C or C++ file to create the implementation. 6 | // i.e. it should look like this: 7 | #include ... 8 | #include ... 9 | #define TML_IMPLEMENTATION 10 | #include "tml.h" 11 | 12 | [OPTIONAL] #define TML_NO_STDIO to remove stdio dependency 13 | [OPTIONAL] #define TML_MALLOC, TML_REALLOC, and TML_FREE to avoid stdlib.h 14 | [OPTIONAL] #define TML_MEMCPY to avoid string.h 15 | 16 | LICENSE (ZLIB) 17 | 18 | Copyright (C) 2017, 2018, 2020 Bernhard Schelling 19 | 20 | This software is provided 'as-is', without any express or implied 21 | warranty. In no event will the authors be held liable for any damages 22 | arising from the use of this software. 23 | 24 | Permission is granted to anyone to use this software for any purpose, 25 | including commercial applications, and to alter it and redistribute it 26 | freely, subject to the following restrictions: 27 | 28 | 1. The origin of this software must not be misrepresented; you must not 29 | claim that you wrote the original software. If you use this software 30 | in a product, an acknowledgment in the product documentation would be 31 | appreciated but is not required. 32 | 2. Altered source versions must be plainly marked as such, and must not be 33 | misrepresented as being the original software. 34 | 3. This notice may not be removed or altered from any source distribution. 35 | 36 | */ 37 | 38 | #ifndef TML_INCLUDE_TML_INL 39 | #define TML_INCLUDE_TML_INL 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | // Define this if you want the API functions to be static 46 | #ifdef TML_STATIC 47 | #define TMLDEF static 48 | #else 49 | #define TMLDEF extern 50 | #endif 51 | 52 | // Channel message type 53 | enum TMLMessageType 54 | { 55 | 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 56 | }; 57 | 58 | // Midi controller numbers 59 | enum TMLController 60 | { 61 | 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, 62 | 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, 63 | 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, 64 | 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, 65 | TML_SUSTAIN_SWITCH = 64, TML_PORTAMENTO_SWITCH, TML_SOSTENUTO_SWITCH, TML_SOFT_PEDAL_SWITCH, TML_LEGATO_SWITCH, TML_HOLD2_SWITCH, 66 | TML_SOUND_CTRL1, TML_SOUND_CTRL2, TML_SOUND_CTRL3, TML_SOUND_CTRL4, TML_SOUND_CTRL5, TML_SOUND_CTRL6, 67 | TML_SOUND_CTRL7, TML_SOUND_CTRL8, TML_SOUND_CTRL9, TML_SOUND_CTRL10, TML_GPC5, TML_GPC6, TML_GPC7, TML_GPC8, 68 | TML_PORTAMENTO_CTRL, TML_FX_REVERB = 91, TML_FX_TREMOLO, TML_FX_CHORUS, TML_FX_CELESTE_DETUNE, TML_FX_PHASER, 69 | TML_DATA_ENTRY_INCR, TML_DATA_ENTRY_DECR, TML_NRPN_LSB, TML_NRPN_MSB, TML_RPN_LSB, TML_RPN_MSB, 70 | 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 71 | }; 72 | 73 | // A single MIDI message linked to the next message in time 74 | typedef struct tml_message 75 | { 76 | // Time of the message in milliseconds 77 | unsigned int time; 78 | 79 | // Type (see TMLMessageType) and channel number 80 | unsigned char type, channel; 81 | 82 | // 2 byte of parameter data based on the type: 83 | // - key, velocity for TML_NOTE_ON and TML_NOTE_OFF messages 84 | // - key, key_pressure for TML_KEY_PRESSURE messages 85 | // - control, control_value for TML_CONTROL_CHANGE messages (see TMLController) 86 | // - program for TML_PROGRAM_CHANGE messages 87 | // - channel_pressure for TML_CHANNEL_PRESSURE messages 88 | // - pitch_bend for TML_PITCH_BEND messages 89 | union 90 | { 91 | #ifdef _MSC_VER 92 | #pragma warning(push) 93 | #pragma warning(disable:4201) //nonstandard extension used: nameless struct/union 94 | #elif defined(__GNUC__) 95 | #pragma GCC diagnostic push 96 | #pragma GCC diagnostic ignored "-Wpedantic" //ISO C++ prohibits anonymous structs 97 | #endif 98 | 99 | struct { union { char key, control, program, channel_pressure; }; union { char velocity, key_pressure, control_value; }; }; 100 | struct { unsigned short pitch_bend; }; 101 | 102 | #ifdef _MSC_VER 103 | #pragma warning( pop ) 104 | #elif defined(__GNUC__) 105 | #pragma GCC diagnostic pop 106 | #endif 107 | }; 108 | 109 | // The pointer to the next message in time following this event 110 | struct tml_message* next; 111 | } tml_message; 112 | 113 | // The load functions will return a pointer to a struct tml_message. 114 | // Normally the linked list gets traversed by following the next pointers. 115 | // Make sure to keep the pointer to the first message to free the memory. 116 | // On error the tml_load* functions will return NULL most likely due to an 117 | // invalid MIDI stream (or if the file did not exist in tml_load_filename). 118 | 119 | #ifndef TML_NO_STDIO 120 | // Directly load a MIDI file from a .mid file path 121 | TMLDEF tml_message* tml_load_filename(const char* filename); 122 | #endif 123 | 124 | // Load a MIDI file from a block of memory 125 | TMLDEF tml_message* tml_load_memory(const void* buffer, int size); 126 | 127 | // Get infos about this loaded MIDI file, returns the note count 128 | // NULL can be passed for any output value pointer if not needed. 129 | // used_channels: Will be set to how many channels play notes 130 | // (i.e. 1 if channel 15 is used but no other) 131 | // used_programs: Will be set to how many different programs are used 132 | // total_notes: Will be set to the total number of note on messages 133 | // time_first_note: Will be set to the time of the first note on message 134 | // time_length: Will be set to the total time in milliseconds 135 | 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); 136 | 137 | // Read the tempo (microseconds per quarter note) value from a message with the type TML_SET_TEMPO 138 | TMLDEF int tml_get_tempo_value(tml_message* set_tempo_message); 139 | 140 | // Free all the memory of the linked message list (can also call free() manually) 141 | TMLDEF void tml_free(tml_message* f); 142 | 143 | // Stream structure for the generic loading 144 | struct tml_stream 145 | { 146 | // Custom data given to the functions as the first parameter 147 | void* data; 148 | 149 | // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes) 150 | int (*read)(void* data, void* ptr, unsigned int size); 151 | }; 152 | 153 | // Generic Midi loading method using the stream structure above 154 | TMLDEF tml_message* tml_load(struct tml_stream* stream); 155 | 156 | // If this library is used together with TinySoundFont, tsf_stream (equivalent to tml_stream) can also be used 157 | struct tsf_stream; 158 | TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream); 159 | 160 | #ifdef __cplusplus 161 | } 162 | #endif 163 | 164 | // end header 165 | // --------------------------------------------------------------------------------------------------------- 166 | #endif //TML_INCLUDE_TML_INL 167 | 168 | #ifdef TML_IMPLEMENTATION 169 | 170 | #if !defined(TML_MALLOC) || !defined(TML_FREE) || !defined(TML_REALLOC) 171 | # include 172 | # define TML_MALLOC malloc 173 | # define TML_FREE free 174 | # define TML_REALLOC realloc 175 | #endif 176 | 177 | #if !defined(TML_MEMCPY) 178 | # include 179 | # define TML_MEMCPY memcpy 180 | #endif 181 | 182 | #ifndef TML_NO_STDIO 183 | # include 184 | #endif 185 | 186 | #define TML_NULL 0 187 | 188 | ////crash on errors and warnings to find broken midi files while debugging 189 | //#define TML_ERROR(msg) *(int*)0 = 0xbad; 190 | //#define TML_WARN(msg) *(int*)0 = 0xf00d; 191 | 192 | ////print errors and warnings 193 | //#define TML_ERROR(msg) printf("ERROR: %s\n", msg); 194 | //#define TML_WARN(msg) printf("WARNING: %s\n", msg); 195 | 196 | #ifndef TML_ERROR 197 | #define TML_ERROR(msg) 198 | #endif 199 | 200 | #ifndef TML_WARN 201 | #define TML_WARN(msg) 202 | #endif 203 | 204 | #ifdef __cplusplus 205 | extern "C" { 206 | #endif 207 | 208 | #ifndef TML_NO_STDIO 209 | static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } 210 | TMLDEF tml_message* tml_load_filename(const char* filename) 211 | { 212 | struct tml_message* res; 213 | struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_stdio_read }; 214 | #if __STDC_WANT_SECURE_LIB__ 215 | FILE* f = TML_NULL; fopen_s(&f, filename, "rb"); 216 | #else 217 | FILE* f = fopen(filename, "rb"); 218 | #endif 219 | if (!f) { TML_ERROR("File not found"); return 0; } 220 | stream.data = f; 221 | res = tml_load(&stream); 222 | fclose(f); 223 | return res; 224 | } 225 | #endif 226 | 227 | struct tml_stream_memory { const char* buffer; unsigned int total, pos; }; 228 | 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; } 229 | TMLDEF struct tml_message* tml_load_memory(const void* buffer, int size) 230 | { 231 | struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_memory_read }; 232 | struct tml_stream_memory f = { 0, 0, 0 }; 233 | f.buffer = (const char*)buffer; 234 | f.total = size; 235 | stream.data = &f; 236 | return tml_load(&stream); 237 | } 238 | 239 | struct tml_track 240 | { 241 | unsigned int Idx, End, Ticks; 242 | }; 243 | 244 | struct tml_tempomsg 245 | { 246 | unsigned int time; 247 | unsigned char type, Tempo[3]; 248 | tml_message* next; 249 | }; 250 | 251 | struct tml_parser 252 | { 253 | unsigned char *buf, *buf_end; 254 | int last_status, message_array_size, message_count; 255 | }; 256 | 257 | enum TMLSystemType 258 | { 259 | TML_TEXT = 0x01, TML_COPYRIGHT = 0x02, TML_TRACK_NAME = 0x03, TML_INST_NAME = 0x04, TML_LYRIC = 0x05, TML_MARKER = 0x06, TML_CUE_POINT = 0x07, 260 | TML_EOT = 0x2f, TML_SMPTE_OFFSET = 0x54, TML_TIME_SIGNATURE = 0x58, TML_KEY_SIGNATURE = 0x59, TML_SEQUENCER_EVENT = 0x7f, 261 | 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, 262 | TML_TICK = 0xf9, TML_START = 0xfa, TML_CONTINUE = 0xfb, TML_STOP = 0xfc, TML_ACTIVE_SENSING = 0xfe, TML_SYSTEM_RESET = 0xff 263 | }; 264 | 265 | static int tml_readbyte(struct tml_parser* p) 266 | { 267 | return (p->buf == p->buf_end ? -1 : *(p->buf++)); 268 | } 269 | 270 | static int tml_readvariablelength(struct tml_parser* p) 271 | { 272 | unsigned int res = 0, i = 0; 273 | unsigned char c; 274 | for (; i != 4; i++) 275 | { 276 | if (p->buf == p->buf_end) { TML_WARN("Unexpected end of file"); return -1; } 277 | c = *(p->buf++); 278 | if (c & 0x80) res = ((res | (c & 0x7F)) << 7); 279 | else return (int)(res | c); 280 | } 281 | TML_WARN("Invalid variable length byte count"); return -1; 282 | } 283 | 284 | static int tml_parsemessage(tml_message** f, struct tml_parser* p) 285 | { 286 | int deltatime = tml_readvariablelength(p), status = tml_readbyte(p); 287 | tml_message* evt; 288 | 289 | if (deltatime & 0xFFF00000) deltatime = 0; //throw away delays that are insanely high for malformatted midis 290 | if (status < 0) { TML_WARN("Unexpected end of file"); return -1; } 291 | if ((status & 0x80) == 0) 292 | { 293 | // Invalid, use same status as before 294 | if ((p->last_status & 0x80) == 0) { TML_WARN("Undefined status and invalid running status"); return -1; } 295 | p->buf--; 296 | status = p->last_status; 297 | } 298 | else p->last_status = status; 299 | 300 | if (p->message_array_size == p->message_count) 301 | { 302 | //start allocated memory size of message array at 64, double each time until 8192, then add 1024 entries until done 303 | p->message_array_size += (!p->message_array_size ? 64 : (p->message_array_size > 4096 ? 1024 : p->message_array_size)); 304 | *f = (tml_message*)TML_REALLOC(*f, p->message_array_size * sizeof(tml_message)); 305 | if (!*f) { TML_ERROR("Out of memory"); return -1; } 306 | } 307 | evt = *f + p->message_count; 308 | 309 | //check what message we have 310 | if ((status == TML_SYSEX) || (status == TML_EOX)) //sysex 311 | { 312 | //sysex messages are not handled 313 | p->buf += tml_readvariablelength(p); 314 | if (p->buf > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; } 315 | evt->type = 0; 316 | } 317 | else if (status == 0xFF) //meta events 318 | { 319 | int meta_type = tml_readbyte(p), buflen = tml_readvariablelength(p); 320 | unsigned char* metadata = p->buf; 321 | if (meta_type < 0) { TML_WARN("Unexpected end of file"); return -1; } 322 | if (buflen > 0 && (p->buf += buflen) > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; } 323 | 324 | switch (meta_type) 325 | { 326 | case TML_EOT: 327 | if (buflen != 0) { TML_WARN("Invalid length for EndOfTrack event"); return -1; } 328 | if (!deltatime) return TML_EOT; //no need to store this message 329 | evt->type = TML_EOT; 330 | break; 331 | 332 | case TML_SET_TEMPO: 333 | if (buflen != 3) { TML_WARN("Invalid length for SetTempo meta event"); return -1; } 334 | evt->type = TML_SET_TEMPO; 335 | ((struct tml_tempomsg*)evt)->Tempo[0] = metadata[0]; 336 | ((struct tml_tempomsg*)evt)->Tempo[1] = metadata[1]; 337 | ((struct tml_tempomsg*)evt)->Tempo[2] = metadata[2]; 338 | break; 339 | 340 | default: 341 | evt->type = 0; 342 | } 343 | } 344 | else //channel message 345 | { 346 | int param; 347 | if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } 348 | evt->key = (param & 0x7f); 349 | evt->channel = (status & 0x0f); 350 | switch (evt->type = (status & 0xf0)) 351 | { 352 | case TML_NOTE_OFF: 353 | case TML_NOTE_ON: 354 | case TML_KEY_PRESSURE: 355 | case TML_CONTROL_CHANGE: 356 | if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } 357 | evt->velocity = (param & 0x7f); 358 | break; 359 | 360 | case TML_PITCH_BEND: 361 | if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } 362 | evt->pitch_bend = ((param & 0x7f) << 7) | evt->key; 363 | break; 364 | 365 | case TML_PROGRAM_CHANGE: 366 | case TML_CHANNEL_PRESSURE: 367 | evt->velocity = 0; 368 | break; 369 | 370 | default: //ignore system/manufacture messages 371 | evt->type = 0; 372 | break; 373 | } 374 | } 375 | 376 | if (deltatime || evt->type) 377 | { 378 | evt->time = deltatime; 379 | p->message_count++; 380 | } 381 | return evt->type; 382 | } 383 | 384 | TMLDEF tml_message* tml_load(struct tml_stream* stream) 385 | { 386 | int num_tracks, division, trackbufsize = 0; 387 | unsigned char midi_header[14], *trackbuf = TML_NULL; 388 | struct tml_message* messages = TML_NULL; 389 | struct tml_track *tracks, *t, *tracksEnd; 390 | struct tml_parser p = { TML_NULL, TML_NULL, 0, 0, 0 }; 391 | 392 | // Parse MIDI header 393 | if (stream->read(stream->data, midi_header, 14) != 14) { TML_ERROR("Unexpected end of file"); return messages; } 394 | if (midi_header[0] != 'M' || midi_header[1] != 'T' || midi_header[2] != 'h' || midi_header[3] != 'd' || 395 | midi_header[7] != 6 || midi_header[9] > 2) { TML_ERROR("Doesn't look like a MIDI file: invalid MThd header"); return messages; } 396 | if (midi_header[12] & 0x80) { TML_ERROR("File uses unsupported SMPTE timing"); return messages; } 397 | num_tracks = (int)(midi_header[10] << 8) | midi_header[11]; 398 | division = (int)(midi_header[12] << 8) | midi_header[13]; //division is ticks per beat (quarter-note) 399 | if (num_tracks <= 0 && division <= 0) { TML_ERROR("Doesn't look like a MIDI file: invalid track or division values"); return messages; } 400 | 401 | // Allocate temporary tracks array for parsing 402 | tracks = (struct tml_track*)TML_MALLOC(sizeof(struct tml_track) * num_tracks); 403 | tracksEnd = &tracks[num_tracks]; 404 | for (t = tracks; t != tracksEnd; t++) t->Idx = t->End = t->Ticks = 0; 405 | 406 | // Read all messages for all tracks 407 | for (t = tracks; t != tracksEnd; t++) 408 | { 409 | unsigned char track_header[8]; 410 | int track_length; 411 | if (stream->read(stream->data, track_header, 8) != 8) { TML_WARN("Unexpected end of file"); break; } 412 | if (track_header[0] != 'M' || track_header[1] != 'T' || track_header[2] != 'r' || track_header[3] != 'k') 413 | { TML_WARN("Invalid MTrk header"); break; } 414 | 415 | // Get size of track data and read into buffer (allocate bigger buffer if needed) 416 | track_length = track_header[7] | (track_header[6] << 8) | (track_header[5] << 16) | (track_header[4] << 24); 417 | if (track_length < 0) { TML_WARN("Invalid MTrk header"); break; } 418 | if (trackbufsize < track_length) { TML_FREE(trackbuf); trackbuf = (unsigned char*)TML_MALLOC(trackbufsize = track_length); } 419 | if (stream->read(stream->data, trackbuf, track_length) != track_length) { TML_WARN("Unexpected end of file"); break; } 420 | 421 | t->Idx = p.message_count; 422 | for (p.buf_end = (p.buf = trackbuf) + track_length; p.buf != p.buf_end;) 423 | { 424 | int type = tml_parsemessage(&messages, &p); 425 | if (type == TML_EOT || type < 0) break; //file end or illegal data encountered 426 | } 427 | if (p.buf != p.buf_end) { TML_WARN( "Track length did not match data length"); } 428 | t->End = p.message_count; 429 | } 430 | TML_FREE(trackbuf); 431 | 432 | // Change message time signature from delta ticks to actual msec values and link messages ordered by time 433 | if (p.message_count) 434 | { 435 | tml_message *PrevMessage = TML_NULL, *Msg, *MsgEnd, Swap; 436 | unsigned int ticks = 0, tempo_ticks = 0; //tick counter and value at last tempo change 437 | int step_smallest, msec, tempo_msec = 0; //msec value at last tempo change 438 | double ticks2time = 500000 / (1000.0 * division); //milliseconds per tick 439 | 440 | // Loop through all messages over all tracks ordered by time 441 | for (step_smallest = 0; step_smallest != 0x7fffffff; ticks += step_smallest) 442 | { 443 | step_smallest = 0x7fffffff; 444 | msec = tempo_msec + (int)((ticks - tempo_ticks) * ticks2time); 445 | for (t = tracks; t != tracksEnd; t++) 446 | { 447 | if (t->Idx == t->End) continue; 448 | for (Msg = &messages[t->Idx], MsgEnd = &messages[t->End]; Msg != MsgEnd && t->Ticks + Msg->time == ticks; Msg++, t->Idx++) 449 | { 450 | t->Ticks += Msg->time; 451 | if (Msg->type == TML_SET_TEMPO) 452 | { 453 | unsigned char* Tempo = ((struct tml_tempomsg*)Msg)->Tempo; 454 | ticks2time = ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2])/(1000.0 * division); 455 | tempo_msec = msec; 456 | tempo_ticks = ticks; 457 | } 458 | if (Msg->type) 459 | { 460 | Msg->time = msec; 461 | if (PrevMessage) { PrevMessage->next = Msg; PrevMessage = Msg; } 462 | else { Swap = *Msg; *Msg = *messages; *messages = Swap; PrevMessage = messages; } 463 | } 464 | } 465 | if (Msg != MsgEnd && t->Ticks + Msg->time > ticks) 466 | { 467 | int step = (int)(t->Ticks + Msg->time - ticks); 468 | if (step < step_smallest) step_smallest = step; 469 | } 470 | } 471 | } 472 | if (PrevMessage) PrevMessage->next = TML_NULL; 473 | else p.message_count = 0; 474 | } 475 | TML_FREE(tracks); 476 | 477 | if (p.message_count == 0) 478 | { 479 | TML_FREE(messages); 480 | messages = TML_NULL; 481 | } 482 | 483 | return messages; 484 | } 485 | 486 | TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream) 487 | { 488 | return tml_load((struct tml_stream*)stream); 489 | } 490 | 491 | 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) 492 | { 493 | int used_programs = 0, used_channels = 0, total_notes = 0; 494 | unsigned int time_first_note = 0xffffffff, time_length = 0; 495 | unsigned char channels[16] = { 0 }, programs[128] = { 0 }; 496 | for (;Msg; Msg = Msg->next) 497 | { 498 | time_length = Msg->time; 499 | if (Msg->type == TML_PROGRAM_CHANGE && !programs[(int)Msg->program]) { programs[(int)Msg->program] = 1; used_programs++; } 500 | if (Msg->type != TML_NOTE_ON) continue; 501 | if (time_first_note == 0xffffffff) time_first_note = time_length; 502 | if (!channels[Msg->channel]) { channels[Msg->channel] = 1; used_channels++; } 503 | total_notes++; 504 | } 505 | if (time_first_note == 0xffffffff) time_first_note = 0; 506 | if (out_used_channels ) *out_used_channels = used_channels; 507 | if (out_used_programs ) *out_used_programs = used_programs; 508 | if (out_total_notes ) *out_total_notes = total_notes; 509 | if (out_time_first_note) *out_time_first_note = time_first_note; 510 | if (out_time_length ) *out_time_length = time_length; 511 | return total_notes; 512 | } 513 | 514 | TMLDEF int tml_get_tempo_value(tml_message* msg) 515 | { 516 | unsigned char* Tempo; 517 | if (!msg || msg->type != TML_SET_TEMPO) return 0; 518 | Tempo = ((struct tml_tempomsg*)msg)->Tempo; 519 | return ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2]); 520 | } 521 | 522 | TMLDEF void tml_free(tml_message* f) 523 | { 524 | TML_FREE(f); 525 | } 526 | 527 | #ifdef __cplusplus 528 | } 529 | #endif 530 | 531 | #endif //TML_IMPLEMENTATION 532 | -------------------------------------------------------------------------------- /tsf.h: -------------------------------------------------------------------------------- 1 | /* TinySoundFont - v0.9 - SoundFont2 synthesizer - https://github.com/schellingb/TinySoundFont 2 | no warranty implied; use at your own risk 3 | Do this: 4 | #define TSF_IMPLEMENTATION 5 | before you include this file in *one* C or C++ file to create the implementation. 6 | // i.e. it should look like this: 7 | #include ... 8 | #include ... 9 | #define TSF_IMPLEMENTATION 10 | #include "tsf.h" 11 | 12 | [OPTIONAL] #define TSF_NO_STDIO to remove stdio dependency 13 | [OPTIONAL] #define TSF_MALLOC, TSF_REALLOC, and TSF_FREE to avoid stdlib.h 14 | [OPTIONAL] #define TSF_MEMCPY, TSF_MEMSET to avoid string.h 15 | [OPTIONAL] #define TSF_POW, TSF_POWF, TSF_EXPF, TSF_LOG, TSF_TAN, TSF_LOG10, TSF_SQRT to avoid math.h 16 | 17 | NOT YET IMPLEMENTED 18 | - Support for ChorusEffectsSend and ReverbEffectsSend generators 19 | - Better low-pass filter without lowering performance too much 20 | - Support for modulators 21 | 22 | LICENSE (MIT) 23 | 24 | Copyright (C) 2017-2025 Bernhard Schelling 25 | Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 28 | software and associated documentation files (the "Software"), to deal in the Software 29 | without restriction, including without limitation the rights to use, copy, modify, merge, 30 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 31 | to whom the Software is furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in all 34 | copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 37 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 38 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 39 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 40 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 41 | USE OR OTHER DEALINGS IN THE SOFTWARE. 42 | 43 | */ 44 | 45 | #ifndef TSF_INCLUDE_TSF_INL 46 | #define TSF_INCLUDE_TSF_INL 47 | 48 | #ifdef __cplusplus 49 | extern "C" { 50 | # define CPP_DEFAULT0 = 0 51 | #else 52 | # define CPP_DEFAULT0 53 | #endif 54 | 55 | //define this if you want the API functions to be static 56 | #ifdef TSF_STATIC 57 | #define TSFDEF static 58 | #else 59 | #define TSFDEF extern 60 | #endif 61 | 62 | // The load functions will return a pointer to a struct tsf which all functions 63 | // thereafter take as the first parameter. 64 | // On error the tsf_load* functions will return NULL most likely due to invalid 65 | // data (or if the file did not exist in tsf_load_filename). 66 | typedef struct tsf tsf; 67 | 68 | #ifndef TSF_NO_STDIO 69 | // Directly load a SoundFont from a .sf2 file path 70 | TSFDEF tsf* tsf_load_filename(const char* filename); 71 | #endif 72 | 73 | // Load a SoundFont from a block of memory 74 | TSFDEF tsf* tsf_load_memory(const void* buffer, int size); 75 | 76 | // Stream structure for the generic loading 77 | struct tsf_stream 78 | { 79 | // Custom data given to the functions as the first parameter 80 | void* data; 81 | 82 | // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes) 83 | int (*read)(void* data, void* ptr, unsigned int size); 84 | 85 | // Function pointer will be called to skip ahead over 'count' bytes (returns 1 on success, 0 on error) 86 | int (*skip)(void* data, unsigned int count); 87 | }; 88 | 89 | // Generic SoundFont loading method using the stream structure above 90 | TSFDEF tsf* tsf_load(struct tsf_stream* stream); 91 | 92 | // Copy a tsf instance from an existing one, use tsf_close to close it as well. 93 | // All copied tsf instances and their original instance are linked, and share the underlying soundfont. 94 | // This allows loading a soundfont only once, but using it for multiple independent playbacks. 95 | // (This function isn't thread-safe without locking.) 96 | TSFDEF tsf* tsf_copy(tsf* f); 97 | 98 | // Free the memory related to this tsf instance 99 | TSFDEF void tsf_close(tsf* f); 100 | 101 | // Stop all playing notes immediately and reset all channel parameters 102 | TSFDEF void tsf_reset(tsf* f); 103 | 104 | // Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont 105 | TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number); 106 | 107 | // Returns the number of presets in the loaded SoundFont 108 | TSFDEF int tsf_get_presetcount(const tsf* f); 109 | 110 | // Returns the name of a preset index >= 0 and < tsf_get_presetcount() 111 | TSFDEF const char* tsf_get_presetname(const tsf* f, int preset_index); 112 | 113 | // Returns the name of a preset by bank and preset number 114 | TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number); 115 | 116 | // Supported output modes by the render methods 117 | enum TSFOutputMode 118 | { 119 | // Two channels with single left/right samples one after another 120 | TSF_STEREO_INTERLEAVED, 121 | // Two channels with all samples for the left channel first then right 122 | TSF_STEREO_UNWEAVED, 123 | // A single channel (stereo instruments are mixed into center) 124 | TSF_MONO 125 | }; 126 | 127 | // Thread safety: 128 | // 129 | // 1. Rendering / voices: 130 | // 131 | // Your audio output which calls the tsf_render* functions will most likely 132 | // run on a different thread than where the playback tsf_note* functions 133 | // are called. In which case some sort of concurrency control like a 134 | // mutex needs to be used so they are not called at the same time. 135 | // Alternatively, you can pre-allocate a maximum number of voices that can 136 | // play simultaneously by calling tsf_set_max_voices after loading. 137 | // That way memory re-allocation will not happen during tsf_note_on and 138 | // TSF should become mostly thread safe. 139 | // There is a theoretical chance that ending notes would negatively influence 140 | // a voice that is rendering at the time but it is hard to say. 141 | // Also be aware, this has not been tested much. 142 | // 143 | // 2. Channels: 144 | // 145 | // Calls to tsf_channel_set_... functions may allocate new channels 146 | // if no channel with that number was previously used. Make sure to 147 | // create all channels at the beginning as required if you call tsf_render* 148 | // from a different thread. 149 | 150 | // Setup the parameters for the voice render methods 151 | // outputmode: if mono or stereo and how stereo channel data is ordered 152 | // samplerate: the number of samples per second (output frequency) 153 | // global_gain_db: volume gain in decibels (>0 means higher, <0 means lower) 154 | TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db CPP_DEFAULT0); 155 | 156 | // Set the global gain as a volume factor 157 | // global_gain: the desired volume where 1.0 is 100% 158 | TSFDEF void tsf_set_volume(tsf* f, float global_gain); 159 | 160 | // Set the maximum number of voices to play simultaneously 161 | // Depending on the soundfond, one note can cause many new voices to be started, 162 | // so don't keep this number too low or otherwise sounds may not play. 163 | // max_voices: maximum number to pre-allocate and set the limit to 164 | // (tsf_set_max_voices returns 0 if allocation failed, otherwise 1) 165 | TSFDEF int tsf_set_max_voices(tsf* f, int max_voices); 166 | 167 | // Start playing a note 168 | // preset_index: preset index >= 0 and < tsf_get_presetcount() 169 | // key: note value between 0 and 127 (60 being middle C) 170 | // vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full) 171 | // bank: instrument bank number (alternative to preset_index) 172 | // preset_number: preset number (alternative to preset_index) 173 | // (tsf_note_on returns 0 if the allocation of a new voice failed, otherwise 1) 174 | // (tsf_bank_note_on returns 0 if preset does not exist or allocation failed, otherwise 1) 175 | TSFDEF int tsf_note_on(tsf* f, int preset_index, int key, float vel); 176 | TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel); 177 | 178 | // Stop playing a note 179 | // (bank_note_off returns 0 if preset does not exist, otherwise 1) 180 | TSFDEF void tsf_note_off(tsf* f, int preset_index, int key); 181 | TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key); 182 | 183 | // Stop playing all notes (end with sustain and release) 184 | TSFDEF void tsf_note_off_all(tsf* f); 185 | 186 | // Returns the number of active voices 187 | TSFDEF int tsf_active_voice_count(tsf* f); 188 | 189 | // Render output samples into a buffer 190 | // You can either render as signed 16-bit values (tsf_render_short) or 191 | // as 32-bit float values (tsf_render_float) 192 | // buffer: target buffer of size samples * output_channels * sizeof(type) 193 | // samples: number of samples to render 194 | // flag_mixing: if 0 clear the buffer first, otherwise mix into existing data 195 | TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing CPP_DEFAULT0); 196 | TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing CPP_DEFAULT0); 197 | 198 | // Higher level channel based functions, set up channel parameters 199 | // channel: channel number 200 | // preset_index: preset index >= 0 and < tsf_get_presetcount() 201 | // preset_number: preset number (alternative to preset_index) 202 | // flag_mididrums: 0 for normal channels, otherwise apply MIDI drum channel rules 203 | // bank: instrument bank number (alternative to preset_index) 204 | // pan: stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center) 205 | // volume: linear volume scale factor (default 1.0 full) 206 | // pitch_wheel: pitch wheel position 0 to 16383 (default 8192 unpitched) 207 | // pitch_range: range of the pitch wheel in semitones (default 2.0, total +/- 2 semitones) 208 | // tuning: tuning of all playing voices in semitones (default 0.0, standard (A440) tuning) 209 | // flag_sustain: 0 to end notes that were held sustained and disable holding sustain otherwise enable it 210 | // (tsf_set_preset_number and set_bank_preset return 0 if preset does not exist, otherwise 1) 211 | // (tsf_channel_set_... return 0 if a new channel needed allocation and that failed, otherwise 1) 212 | TSFDEF int tsf_channel_set_presetindex(tsf* f, int channel, int preset_index); 213 | TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums CPP_DEFAULT0); 214 | TSFDEF int tsf_channel_set_bank(tsf* f, int channel, int bank); 215 | TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number); 216 | TSFDEF int tsf_channel_set_pan(tsf* f, int channel, float pan); 217 | TSFDEF int tsf_channel_set_volume(tsf* f, int channel, float volume); 218 | TSFDEF int tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel); 219 | TSFDEF int tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range); 220 | TSFDEF int tsf_channel_set_tuning(tsf* f, int channel, float tuning); 221 | TSFDEF int tsf_channel_set_sustain(tsf* f, int channel, int flag_sustain); 222 | 223 | // Start or stop playing notes on a channel (needs channel preset to be set) 224 | // channel: channel number 225 | // key: note value between 0 and 127 (60 being middle C) 226 | // vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full) 227 | // (tsf_channel_note_on returns 0 on allocation failure of new voice, otherwise 1) 228 | TSFDEF int tsf_channel_note_on(tsf* f, int channel, int key, float vel); 229 | TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key); 230 | TSFDEF void tsf_channel_note_off_all(tsf* f, int channel); //end with sustain and release 231 | TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediately 232 | 233 | // Apply a MIDI control change to the channel (not all controllers are supported!) 234 | // (tsf_channel_midi_control returns 0 on allocation failure of new channel, otherwise 1) 235 | TSFDEF int tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value); 236 | 237 | // Get current values set on the channels 238 | TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel); 239 | TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel); 240 | TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel); 241 | TSFDEF float tsf_channel_get_pan(tsf* f, int channel); 242 | TSFDEF float tsf_channel_get_volume(tsf* f, int channel); 243 | TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel); 244 | TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel); 245 | TSFDEF float tsf_channel_get_tuning(tsf* f, int channel); 246 | 247 | #ifdef __cplusplus 248 | # undef CPP_DEFAULT0 249 | } 250 | #endif 251 | 252 | // end header 253 | // --------------------------------------------------------------------------------------------------------- 254 | #endif //TSF_INCLUDE_TSF_INL 255 | 256 | #ifdef TSF_IMPLEMENTATION 257 | #undef TSF_IMPLEMENTATION 258 | 259 | // The lower this block size is the more accurate the effects are. 260 | // Increasing the value significantly lowers the CPU usage of the voice rendering. 261 | // If LFO affects the low-pass filter it can be hearable even as low as 8. 262 | #ifndef TSF_RENDER_EFFECTSAMPLEBLOCK 263 | #define TSF_RENDER_EFFECTSAMPLEBLOCK 64 264 | #endif 265 | 266 | // When using tsf_render_short, to do the conversion a buffer of a fixed size is 267 | // allocated on the stack. On low memory platforms this could be made smaller. 268 | // Increasing this above 512 should not have a significant impact on performance. 269 | // The value should be a multiple of TSF_RENDER_EFFECTSAMPLEBLOCK. 270 | #ifndef TSF_RENDER_SHORTBUFFERBLOCK 271 | #define TSF_RENDER_SHORTBUFFERBLOCK 512 272 | #endif 273 | 274 | // Grace release time for quick voice off (avoid clicking noise) 275 | #define TSF_FASTRELEASETIME 0.01f 276 | 277 | #if !defined(TSF_MALLOC) || !defined(TSF_FREE) || !defined(TSF_REALLOC) 278 | # include 279 | # define TSF_MALLOC malloc 280 | # define TSF_FREE free 281 | # define TSF_REALLOC realloc 282 | #endif 283 | 284 | #if !defined(TSF_MEMCPY) || !defined(TSF_MEMSET) 285 | # include 286 | # define TSF_MEMCPY memcpy 287 | # define TSF_MEMSET memset 288 | #endif 289 | 290 | #if !defined(TSF_POW) || !defined(TSF_POWF) || !defined(TSF_EXPF) || !defined(TSF_LOG) || !defined(TSF_TAN) || !defined(TSF_LOG10) || !defined(TSF_SQRT) 291 | # include 292 | # if !defined(__cplusplus) && !defined(NAN) && !defined(powf) && !defined(expf) && !defined(sqrtf) 293 | # define powf (float)pow // deal with old math.h 294 | # define expf (float)exp // files that come without 295 | # define sqrtf (float)sqrt // powf, expf and sqrtf 296 | # endif 297 | # define TSF_POW pow 298 | # define TSF_POWF powf 299 | # define TSF_EXPF expf 300 | # define TSF_LOG log 301 | # define TSF_TAN tan 302 | # define TSF_LOG10 log10 303 | # define TSF_SQRTF sqrtf 304 | #endif 305 | 306 | #ifndef TSF_NO_STDIO 307 | # include 308 | #endif 309 | 310 | #define TSF_TRUE 1 311 | #define TSF_FALSE 0 312 | #define TSF_BOOL unsigned char 313 | #define TSF_PI 3.14159265358979323846264338327950288 314 | #define TSF_NULL 0 315 | 316 | #ifdef __cplusplus 317 | extern "C" { 318 | #endif 319 | 320 | typedef char tsf_fourcc[4]; 321 | typedef signed char tsf_s8; 322 | typedef unsigned char tsf_u8; 323 | typedef unsigned short tsf_u16; 324 | typedef signed short tsf_s16; 325 | typedef unsigned int tsf_u32; 326 | typedef char tsf_char20[20]; 327 | 328 | #define TSF_FourCCEquals(value1, value2) (value1[0] == value2[0] && value1[1] == value2[1] && value1[2] == value2[2] && value1[3] == value2[3]) 329 | 330 | struct tsf 331 | { 332 | struct tsf_preset* presets; 333 | float* fontSamples; 334 | struct tsf_voice* voices; 335 | struct tsf_channels* channels; 336 | 337 | int presetNum; 338 | int voiceNum; 339 | int maxVoiceNum; 340 | unsigned int voicePlayIndex; 341 | 342 | enum TSFOutputMode outputmode; 343 | float outSampleRate; 344 | float globalGainDB; 345 | int* refCount; 346 | }; 347 | 348 | #ifndef TSF_NO_STDIO 349 | static int tsf_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } 350 | static int tsf_stream_stdio_skip(FILE* f, unsigned int count) { return !fseek(f, count, SEEK_CUR); } 351 | TSFDEF tsf* tsf_load_filename(const char* filename) 352 | { 353 | tsf* res; 354 | struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_stdio_read, (int(*)(void*,unsigned int))&tsf_stream_stdio_skip }; 355 | #if __STDC_WANT_SECURE_LIB__ 356 | FILE* f = TSF_NULL; fopen_s(&f, filename, "rb"); 357 | #else 358 | FILE* f = fopen(filename, "rb"); 359 | #endif 360 | if (!f) 361 | { 362 | //if (e) *e = TSF_FILENOTFOUND; 363 | return TSF_NULL; 364 | } 365 | stream.data = f; 366 | res = tsf_load(&stream); 367 | fclose(f); 368 | return res; 369 | } 370 | #endif 371 | 372 | struct tsf_stream_memory { const char* buffer; unsigned int total, pos; }; 373 | 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; } 374 | 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; } 375 | TSFDEF tsf* tsf_load_memory(const void* buffer, int size) 376 | { 377 | struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_memory_read, (int(*)(void*,unsigned int))&tsf_stream_memory_skip }; 378 | struct tsf_stream_memory f = { 0, 0, 0 }; 379 | f.buffer = (const char*)buffer; 380 | f.total = size; 381 | stream.data = &f; 382 | return tsf_load(&stream); 383 | } 384 | 385 | enum { TSF_LOOPMODE_NONE, TSF_LOOPMODE_CONTINUOUS, TSF_LOOPMODE_SUSTAIN }; 386 | 387 | 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 }; 388 | 389 | struct tsf_hydra 390 | { 391 | struct tsf_hydra_phdr *phdrs; struct tsf_hydra_pbag *pbags; struct tsf_hydra_pmod *pmods; 392 | struct tsf_hydra_pgen *pgens; struct tsf_hydra_inst *insts; struct tsf_hydra_ibag *ibags; 393 | struct tsf_hydra_imod *imods; struct tsf_hydra_igen *igens; struct tsf_hydra_shdr *shdrs; 394 | int phdrNum, pbagNum, pmodNum, pgenNum, instNum, ibagNum, imodNum, igenNum, shdrNum; 395 | }; 396 | 397 | union tsf_hydra_genamount { struct { tsf_u8 lo, hi; } range; tsf_s16 shortAmount; tsf_u16 wordAmount; }; 398 | struct tsf_hydra_phdr { tsf_char20 presetName; tsf_u16 preset, bank, presetBagNdx; tsf_u32 library, genre, morphology; }; 399 | struct tsf_hydra_pbag { tsf_u16 genNdx, modNdx; }; 400 | struct tsf_hydra_pmod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; }; 401 | struct tsf_hydra_pgen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; }; 402 | struct tsf_hydra_inst { tsf_char20 instName; tsf_u16 instBagNdx; }; 403 | struct tsf_hydra_ibag { tsf_u16 instGenNdx, instModNdx; }; 404 | struct tsf_hydra_imod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; }; 405 | struct tsf_hydra_igen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; }; 406 | struct tsf_hydra_shdr { tsf_char20 sampleName; tsf_u32 start, end, startLoop, endLoop, sampleRate; tsf_u8 originalPitch; tsf_s8 pitchCorrection; tsf_u16 sampleLink, sampleType; }; 407 | 408 | #define TSFR(FIELD) stream->read(stream->data, &i->FIELD, sizeof(i->FIELD)); 409 | 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) } 410 | static void tsf_hydra_read_pbag(struct tsf_hydra_pbag* i, struct tsf_stream* stream) { TSFR(genNdx) TSFR(modNdx) } 411 | 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) } 412 | static void tsf_hydra_read_pgen(struct tsf_hydra_pgen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) } 413 | static void tsf_hydra_read_inst(struct tsf_hydra_inst* i, struct tsf_stream* stream) { TSFR(instName) TSFR(instBagNdx) } 414 | static void tsf_hydra_read_ibag(struct tsf_hydra_ibag* i, struct tsf_stream* stream) { TSFR(instGenNdx) TSFR(instModNdx) } 415 | 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) } 416 | static void tsf_hydra_read_igen(struct tsf_hydra_igen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) } 417 | 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) } 418 | #undef TSFR 419 | 420 | struct tsf_riffchunk { tsf_fourcc id; tsf_u32 size; }; 421 | struct tsf_envelope { float delay, attack, hold, decay, sustain, release, keynumToHold, keynumToDecay; }; 422 | struct tsf_voice_envelope { unsigned char segment, segmentIsExponential : 1, isAmpEnv : 1; short midiVelocity; float level, slope; int samplesUntilNextSegment; struct tsf_envelope parameters; }; 423 | struct tsf_voice_lowpass { double QInv, a0, a1, b1, b2, z1, z2; TSF_BOOL active; }; 424 | struct tsf_voice_lfo { int samplesUntil; float level, delta; }; 425 | 426 | struct tsf_region 427 | { 428 | int loop_mode; 429 | unsigned int sample_rate; 430 | unsigned char lokey, hikey, lovel, hivel; 431 | unsigned int group, offset, end, loop_start, loop_end; 432 | int transpose, tune, pitch_keycenter, pitch_keytrack; 433 | float attenuation, pan; 434 | struct tsf_envelope ampenv, modenv; 435 | int initialFilterQ, initialFilterFc; 436 | int modEnvToPitch, modEnvToFilterFc, modLfoToFilterFc, modLfoToVolume; 437 | float delayModLFO; 438 | int freqModLFO, modLfoToPitch; 439 | float delayVibLFO; 440 | int freqVibLFO, vibLfoToPitch; 441 | }; 442 | 443 | struct tsf_preset 444 | { 445 | tsf_char20 presetName; 446 | tsf_u16 preset, bank; 447 | struct tsf_region* regions; 448 | int regionNum; 449 | }; 450 | 451 | struct tsf_voice 452 | { 453 | int playingPreset, playingKey, playingChannel, heldSustain; 454 | struct tsf_region* region; 455 | double pitchInputTimecents, pitchOutputFactor; 456 | double sourceSamplePosition; 457 | float noteGainDB, panFactorLeft, panFactorRight; 458 | unsigned int playIndex, loopStart, loopEnd; 459 | struct tsf_voice_envelope ampenv, modenv; 460 | struct tsf_voice_lowpass lowpass; 461 | struct tsf_voice_lfo modlfo, viblfo; 462 | }; 463 | 464 | struct tsf_channel 465 | { 466 | unsigned short presetIndex, bank, pitchWheel, midiPan, midiVolume, midiExpression, midiRPN, midiData : 14, sustain : 1; 467 | float panOffset, gainDB, pitchRange, tuning; 468 | }; 469 | 470 | struct tsf_channels 471 | { 472 | void (*setupVoice)(tsf* f, struct tsf_voice* voice); 473 | int channelNum, activeChannel; 474 | struct tsf_channel channels[1]; 475 | }; 476 | 477 | static double tsf_timecents2Secsd(double timecents) { return TSF_POW(2.0, timecents / 1200.0); } 478 | static float tsf_timecents2Secsf(float timecents) { return TSF_POWF(2.0f, timecents / 1200.0f); } 479 | static float tsf_cents2Hertz(float cents) { return 8.176f * TSF_POWF(2.0f, cents / 1200.0f); } 480 | static float tsf_decibelsToGain(float db) { return (db > -100.f ? TSF_POWF(10.0f, db * 0.05f) : 0); } 481 | static float tsf_gainToDecibels(float gain) { return (gain <= .00001f ? -100.f : (float)(20.0 * TSF_LOG10(gain))); } 482 | 483 | static TSF_BOOL tsf_riffchunk_read(struct tsf_riffchunk* parent, struct tsf_riffchunk* chunk, struct tsf_stream* stream) 484 | { 485 | TSF_BOOL IsRiff, IsList; 486 | if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) > parent->size) return TSF_FALSE; 487 | if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE; 488 | if (!stream->read(stream->data, &chunk->size, sizeof(tsf_u32))) return TSF_FALSE; 489 | if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size > parent->size) return TSF_FALSE; 490 | if (parent) parent->size -= sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size; 491 | IsRiff = TSF_FourCCEquals(chunk->id, "RIFF"), IsList = TSF_FourCCEquals(chunk->id, "LIST"); 492 | if (IsRiff && parent) return TSF_FALSE; //not allowed 493 | if (!IsRiff && !IsList) return TSF_TRUE; //custom type without sub type 494 | if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE; 495 | chunk->size -= sizeof(tsf_fourcc); 496 | return TSF_TRUE; 497 | } 498 | 499 | static void tsf_region_clear(struct tsf_region* i, TSF_BOOL for_relative) 500 | { 501 | TSF_MEMSET(i, 0, sizeof(struct tsf_region)); 502 | i->hikey = i->hivel = 127; 503 | i->pitch_keycenter = 60; // C4 504 | if (for_relative) return; 505 | 506 | i->pitch_keytrack = 100; 507 | 508 | i->pitch_keycenter = -1; 509 | 510 | // SF2 defaults in timecents. 511 | i->ampenv.delay = i->ampenv.attack = i->ampenv.hold = i->ampenv.decay = i->ampenv.release = -12000.0f; 512 | i->modenv.delay = i->modenv.attack = i->modenv.hold = i->modenv.decay = i->modenv.release = -12000.0f; 513 | 514 | i->initialFilterFc = 13500; 515 | 516 | i->delayModLFO = -12000.0f; 517 | i->delayVibLFO = -12000.0f; 518 | } 519 | 520 | static void tsf_region_operator(struct tsf_region* region, tsf_u16 genOper, union tsf_hydra_genamount* amount, struct tsf_region* merge_region) 521 | { 522 | enum 523 | { 524 | _GEN_TYPE_MASK = 0x0F, 525 | GEN_FLOAT = 0x01, 526 | GEN_INT = 0x02, 527 | GEN_UINT_ADD = 0x03, 528 | GEN_UINT_ADD15 = 0x04, 529 | GEN_KEYRANGE = 0x05, 530 | GEN_VELRANGE = 0x06, 531 | GEN_LOOPMODE = 0x07, 532 | GEN_GROUP = 0x08, 533 | GEN_KEYCENTER = 0x09, 534 | 535 | _GEN_LIMIT_MASK = 0xF0, 536 | GEN_INT_LIMIT12K = 0x10, //min -12000, max 12000 537 | GEN_INT_LIMITFC = 0x20, //min 1500, max 13500 538 | GEN_INT_LIMITQ = 0x30, //min 0, max 960 539 | GEN_INT_LIMIT960 = 0x40, //min -960, max 960 540 | GEN_INT_LIMIT16K4500 = 0x50, //min -16000, max 4500 541 | GEN_FLOAT_LIMIT12K5K = 0x60, //min -12000, max 5000 542 | GEN_FLOAT_LIMIT12K8K = 0x70, //min -12000, max 8000 543 | GEN_FLOAT_LIMIT1200 = 0x80, //min -1200, max 1200 544 | GEN_FLOAT_LIMITPAN = 0x90, //* .001f, min -.5f, max .5f, 545 | GEN_FLOAT_LIMITATTN = 0xA0, //* .1f, min 0, max 144.0 546 | GEN_FLOAT_MAX1000 = 0xB0, //min 0, max 1000 547 | GEN_FLOAT_MAX1440 = 0xC0, //min 0, max 1440 548 | 549 | _GEN_MAX = 59 550 | }; 551 | #define _TSFREGIONOFFSET(TYPE, FIELD) (unsigned char)(((TYPE*)&((struct tsf_region*)0)->FIELD) - (TYPE*)0) 552 | #define _TSFREGIONENVOFFSET(TYPE, ENV, FIELD) (unsigned char)(((TYPE*)&((&(((struct tsf_region*)0)->ENV))->FIELD)) - (TYPE*)0) 553 | static const struct { unsigned char mode, offset; } genMetas[_GEN_MAX] = 554 | { 555 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, offset ) }, // 0 StartAddrsOffset 556 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, end ) }, // 1 EndAddrsOffset 557 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_start ) }, // 2 StartloopAddrsOffset 558 | { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_end ) }, // 3 EndloopAddrsOffset 559 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, offset ) }, // 4 StartAddrsCoarseOffset 560 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToPitch ) }, // 5 ModLfoToPitch 561 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, vibLfoToPitch ) }, // 6 VibLfoToPitch 562 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToPitch ) }, // 7 ModEnvToPitch 563 | { GEN_INT | GEN_INT_LIMITFC , _TSFREGIONOFFSET( int, initialFilterFc ) }, // 8 InitialFilterFc 564 | { GEN_INT | GEN_INT_LIMITQ , _TSFREGIONOFFSET( int, initialFilterQ ) }, // 9 InitialFilterQ 565 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToFilterFc ) }, //10 ModLfoToFilterFc 566 | { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToFilterFc ) }, //11 ModEnvToFilterFc 567 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, end ) }, //12 EndAddrsCoarseOffset 568 | { GEN_INT | GEN_INT_LIMIT960 , _TSFREGIONOFFSET( int, modLfoToVolume ) }, //13 ModLfoToVolume 569 | { 0 , (0 ) }, // Unused 570 | { 0 , (0 ) }, //15 ChorusEffectsSend (unsupported) 571 | { 0 , (0 ) }, //16 ReverbEffectsSend (unsupported) 572 | { GEN_FLOAT | GEN_FLOAT_LIMITPAN , _TSFREGIONOFFSET( float, pan ) }, //17 Pan 573 | { 0 , (0 ) }, // Unused 574 | { 0 , (0 ) }, // Unused 575 | { 0 , (0 ) }, // Unused 576 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayModLFO ) }, //21 DelayModLFO 577 | { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqModLFO ) }, //22 FreqModLFO 578 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayVibLFO ) }, //23 DelayVibLFO 579 | { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqVibLFO ) }, //24 FreqVibLFO 580 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, delay ) }, //25 DelayModEnv 581 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, attack ) }, //26 AttackModEnv 582 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, hold ) }, //27 HoldModEnv 583 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, decay ) }, //28 DecayModEnv 584 | { GEN_FLOAT | GEN_FLOAT_MAX1000 , _TSFREGIONENVOFFSET( float, modenv, sustain ) }, //29 SustainModEnv 585 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, release ) }, //30 ReleaseModEnv 586 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToHold ) }, //31 KeynumToModEnvHold 587 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToDecay) }, //32 KeynumToModEnvDecay 588 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, delay ) }, //33 DelayVolEnv 589 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, attack ) }, //34 AttackVolEnv 590 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, hold ) }, //35 HoldVolEnv 591 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, decay ) }, //36 DecayVolEnv 592 | { GEN_FLOAT | GEN_FLOAT_MAX1440 , _TSFREGIONENVOFFSET( float, ampenv, sustain ) }, //37 SustainVolEnv 593 | { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, release ) }, //38 ReleaseVolEnv 594 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToHold ) }, //39 KeynumToVolEnvHold 595 | { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToDecay) }, //40 KeynumToVolEnvDecay 596 | { 0 , (0 ) }, // Instrument (special) 597 | { 0 , (0 ) }, // Reserved 598 | { GEN_KEYRANGE , (0 ) }, //43 KeyRange 599 | { GEN_VELRANGE , (0 ) }, //44 VelRange 600 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_start ) }, //45 StartloopAddrsCoarseOffset 601 | { 0 , (0 ) }, //46 Keynum (special) 602 | { 0 , (0 ) }, //47 Velocity (special) 603 | { GEN_FLOAT | GEN_FLOAT_LIMITATTN , _TSFREGIONOFFSET( float, attenuation ) }, //48 InitialAttenuation 604 | { 0 , (0 ) }, // Reserved 605 | { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_end ) }, //50 EndloopAddrsCoarseOffset 606 | { GEN_INT , _TSFREGIONOFFSET( int, transpose ) }, //51 CoarseTune 607 | { GEN_INT , _TSFREGIONOFFSET( int, tune ) }, //52 FineTune 608 | { 0 , (0 ) }, // SampleID (special) 609 | { GEN_LOOPMODE , _TSFREGIONOFFSET( int, loop_mode ) }, //54 SampleModes 610 | { 0 , (0 ) }, // Reserved 611 | { GEN_INT , _TSFREGIONOFFSET( int, pitch_keytrack ) }, //56 ScaleTuning 612 | { GEN_GROUP , _TSFREGIONOFFSET(unsigned int, group ) }, //57 ExclusiveClass 613 | { GEN_KEYCENTER , _TSFREGIONOFFSET( int, pitch_keycenter ) }, //58 OverridingRootKey 614 | }; 615 | #undef _TSFREGIONOFFSET 616 | #undef _TSFREGIONENVOFFSET 617 | if (amount) 618 | { 619 | int offset; 620 | if (genOper >= _GEN_MAX) return; 621 | offset = genMetas[genOper].offset; 622 | switch (genMetas[genOper].mode & _GEN_TYPE_MASK) 623 | { 624 | case GEN_FLOAT: (( float*)region)[offset] = amount->shortAmount; return; 625 | case GEN_INT: (( int*)region)[offset] = amount->shortAmount; return; 626 | case GEN_UINT_ADD: ((unsigned int*)region)[offset] += amount->shortAmount; return; 627 | case GEN_UINT_ADD15: ((unsigned int*)region)[offset] += amount->shortAmount<<15; return; 628 | case GEN_KEYRANGE: region->lokey = amount->range.lo; region->hikey = amount->range.hi; return; 629 | case GEN_VELRANGE: region->lovel = amount->range.lo; region->hivel = amount->range.hi; return; 630 | case GEN_LOOPMODE: region->loop_mode = ((amount->wordAmount&3) == 3 ? TSF_LOOPMODE_SUSTAIN : ((amount->wordAmount&3) == 1 ? TSF_LOOPMODE_CONTINUOUS : TSF_LOOPMODE_NONE)); return; 631 | case GEN_GROUP: region->group = amount->wordAmount; return; 632 | case GEN_KEYCENTER: region->pitch_keycenter = amount->shortAmount; return; 633 | } 634 | } 635 | else //merge regions and clamp values 636 | { 637 | for (genOper = 0; genOper != _GEN_MAX; genOper++) 638 | { 639 | int offset = genMetas[genOper].offset; 640 | switch (genMetas[genOper].mode & _GEN_TYPE_MASK) 641 | { 642 | case GEN_FLOAT: 643 | { 644 | float *val = &((float*)region)[offset], vfactor, vmin, vmax; 645 | *val += ((float*)merge_region)[offset]; 646 | switch (genMetas[genOper].mode & _GEN_LIMIT_MASK) 647 | { 648 | case GEN_FLOAT_LIMIT12K5K: vfactor = 1.0f; vmin = -12000.0f; vmax = 5000.0f; break; 649 | case GEN_FLOAT_LIMIT12K8K: vfactor = 1.0f; vmin = -12000.0f; vmax = 8000.0f; break; 650 | case GEN_FLOAT_LIMIT1200: vfactor = 1.0f; vmin = -1200.0f; vmax = 1200.0f; break; 651 | case GEN_FLOAT_LIMITPAN: vfactor = 0.001f; vmin = -0.5f; vmax = 0.5f; break; 652 | case GEN_FLOAT_LIMITATTN: vfactor = 0.01f; vmin = 0.0f; vmax = 14.4f; break; 653 | case GEN_FLOAT_MAX1000: vfactor = 1.0f; vmin = 0.0f; vmax = 1000.0f; break; 654 | case GEN_FLOAT_MAX1440: vfactor = 1.0f; vmin = 0.0f; vmax = 1440.0f; break; 655 | default: continue; 656 | } 657 | *val *= vfactor; 658 | if (*val < vmin) *val = vmin; 659 | else if (*val > vmax) *val = vmax; 660 | continue; 661 | } 662 | case GEN_INT: 663 | { 664 | int *val = &((int*)region)[offset], vmin, vmax; 665 | *val += ((int*)merge_region)[offset]; 666 | switch (genMetas[genOper].mode & _GEN_LIMIT_MASK) 667 | { 668 | case GEN_INT_LIMIT12K: vmin = -12000; vmax = 12000; break; 669 | case GEN_INT_LIMITFC: vmin = 1500; vmax = 13500; break; 670 | case GEN_INT_LIMITQ: vmin = 0; vmax = 960; break; 671 | case GEN_INT_LIMIT960: vmin = -960; vmax = 960; break; 672 | case GEN_INT_LIMIT16K4500: vmin = -16000; vmax = 4500; break; 673 | default: continue; 674 | } 675 | if (*val < vmin) *val = vmin; 676 | else if (*val > vmax) *val = vmax; 677 | continue; 678 | } 679 | case GEN_UINT_ADD: 680 | { 681 | ((unsigned int*)region)[offset] += ((unsigned int*)merge_region)[offset]; 682 | continue; 683 | } 684 | } 685 | } 686 | } 687 | } 688 | 689 | static void tsf_region_envtosecs(struct tsf_envelope* p, TSF_BOOL sustainIsGain) 690 | { 691 | // EG times need to be converted from timecents to seconds. 692 | // Pin very short EG segments. Timecents don't get to zero, and our EG is 693 | // happier with zero values. 694 | p->delay = (p->delay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->delay)); 695 | p->attack = (p->attack < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->attack)); 696 | p->release = (p->release < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->release)); 697 | 698 | // If we have dynamic hold or decay times depending on key number we need 699 | // to keep the values in timecents so we can calculate it during startNote 700 | if (!p->keynumToHold) p->hold = (p->hold < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->hold)); 701 | if (!p->keynumToDecay) p->decay = (p->decay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->decay)); 702 | 703 | if (p->sustain < 0.0f) p->sustain = 0.0f; 704 | else if (sustainIsGain) p->sustain = tsf_decibelsToGain(-p->sustain / 10.0f); 705 | else p->sustain = 1.0f - (p->sustain / 1000.0f); 706 | } 707 | 708 | static int tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int fontSampleCount) 709 | { 710 | enum { GenInstrument = 41, GenKeyRange = 43, GenVelRange = 44, GenSampleID = 53 }; 711 | // Read each preset. 712 | struct tsf_hydra_phdr *pphdr, *pphdrMax; 713 | res->presetNum = hydra->phdrNum - 1; 714 | res->presets = (struct tsf_preset*)TSF_MALLOC(res->presetNum * sizeof(struct tsf_preset)); 715 | if (!res->presets) return 0; 716 | else { int i; for (i = 0; i != res->presetNum; i++) res->presets[i].regions = TSF_NULL; } 717 | for (pphdr = hydra->phdrs, pphdrMax = pphdr + hydra->phdrNum - 1; pphdr != pphdrMax; pphdr++) 718 | { 719 | int sortedIndex = 0, region_index = 0; 720 | struct tsf_hydra_phdr *otherphdr; 721 | struct tsf_preset* preset; 722 | struct tsf_hydra_pbag *ppbag, *ppbagEnd; 723 | struct tsf_region globalRegion; 724 | for (otherphdr = hydra->phdrs; otherphdr != pphdrMax; otherphdr++) 725 | { 726 | if (otherphdr == pphdr || otherphdr->bank > pphdr->bank) continue; 727 | else if (otherphdr->bank < pphdr->bank) sortedIndex++; 728 | else if (otherphdr->preset > pphdr->preset) continue; 729 | else if (otherphdr->preset < pphdr->preset) sortedIndex++; 730 | else if (otherphdr < pphdr) sortedIndex++; 731 | } 732 | 733 | preset = &res->presets[sortedIndex]; 734 | TSF_MEMCPY(preset->presetName, pphdr->presetName, sizeof(preset->presetName)); 735 | preset->presetName[sizeof(preset->presetName)-1] = '\0'; //should be zero terminated in source file but make sure 736 | preset->bank = pphdr->bank; 737 | preset->preset = pphdr->preset; 738 | preset->regionNum = 0; 739 | 740 | //count regions covered by this preset 741 | for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++) 742 | { 743 | unsigned char plokey = 0, phikey = 127, plovel = 0, phivel = 127; 744 | struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd; 745 | for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++) 746 | { 747 | if (ppgen->genOper == GenKeyRange) { plokey = ppgen->genAmount.range.lo; phikey = ppgen->genAmount.range.hi; continue; } 748 | if (ppgen->genOper == GenVelRange) { plovel = ppgen->genAmount.range.lo; phivel = ppgen->genAmount.range.hi; continue; } 749 | if (ppgen->genOper != GenInstrument) continue; 750 | if (ppgen->genAmount.wordAmount >= hydra->instNum) continue; 751 | pinst = hydra->insts + ppgen->genAmount.wordAmount; 752 | for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++) 753 | { 754 | unsigned char ilokey = 0, ihikey = 127, ilovel = 0, ihivel = 127; 755 | for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++) 756 | { 757 | if (pigen->genOper == GenKeyRange) { ilokey = pigen->genAmount.range.lo; ihikey = pigen->genAmount.range.hi; continue; } 758 | if (pigen->genOper == GenVelRange) { ilovel = pigen->genAmount.range.lo; ihivel = pigen->genAmount.range.hi; continue; } 759 | if (pigen->genOper == GenSampleID && ihikey >= plokey && ilokey <= phikey && ihivel >= plovel && ilovel <= phivel) preset->regionNum++; 760 | } 761 | } 762 | } 763 | } 764 | 765 | preset->regions = (struct tsf_region*)TSF_MALLOC(preset->regionNum * sizeof(struct tsf_region)); 766 | if (!preset->regions) 767 | { 768 | int i; for (i = 0; i != res->presetNum; i++) TSF_FREE(res->presets[i].regions); 769 | TSF_FREE(res->presets); 770 | return 0; 771 | } 772 | tsf_region_clear(&globalRegion, TSF_TRUE); 773 | 774 | // Zones. 775 | for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++) 776 | { 777 | struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd; 778 | struct tsf_region presetRegion = globalRegion; 779 | int hadGenInstrument = 0; 780 | 781 | // Generators. 782 | for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++) 783 | { 784 | // Instrument. 785 | if (ppgen->genOper == GenInstrument) 786 | { 787 | struct tsf_region instRegion; 788 | tsf_u16 whichInst = ppgen->genAmount.wordAmount; 789 | if (whichInst >= hydra->instNum) continue; 790 | 791 | tsf_region_clear(&instRegion, TSF_FALSE); 792 | pinst = &hydra->insts[whichInst]; 793 | for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++) 794 | { 795 | // Generators. 796 | struct tsf_region zoneRegion = instRegion; 797 | int hadSampleID = 0; 798 | for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++) 799 | { 800 | if (pigen->genOper == GenSampleID) 801 | { 802 | struct tsf_hydra_shdr* pshdr; 803 | 804 | //preset region key and vel ranges are a filter for the zone regions 805 | if (zoneRegion.hikey < presetRegion.lokey || zoneRegion.lokey > presetRegion.hikey) continue; 806 | if (zoneRegion.hivel < presetRegion.lovel || zoneRegion.lovel > presetRegion.hivel) continue; 807 | if (presetRegion.lokey > zoneRegion.lokey) zoneRegion.lokey = presetRegion.lokey; 808 | if (presetRegion.hikey < zoneRegion.hikey) zoneRegion.hikey = presetRegion.hikey; 809 | if (presetRegion.lovel > zoneRegion.lovel) zoneRegion.lovel = presetRegion.lovel; 810 | if (presetRegion.hivel < zoneRegion.hivel) zoneRegion.hivel = presetRegion.hivel; 811 | 812 | //sum regions 813 | tsf_region_operator(&zoneRegion, 0, TSF_NULL, &presetRegion); 814 | 815 | // EG times need to be converted from timecents to seconds. 816 | tsf_region_envtosecs(&zoneRegion.ampenv, TSF_TRUE); 817 | tsf_region_envtosecs(&zoneRegion.modenv, TSF_FALSE); 818 | 819 | // LFO times need to be converted from timecents to seconds. 820 | zoneRegion.delayModLFO = (zoneRegion.delayModLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayModLFO)); 821 | zoneRegion.delayVibLFO = (zoneRegion.delayVibLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayVibLFO)); 822 | 823 | // Fixup sample positions 824 | pshdr = &hydra->shdrs[pigen->genAmount.wordAmount]; 825 | zoneRegion.offset += pshdr->start; 826 | zoneRegion.end += pshdr->end; 827 | zoneRegion.loop_start += pshdr->startLoop; 828 | zoneRegion.loop_end += pshdr->endLoop; 829 | if (pshdr->endLoop > 0) zoneRegion.loop_end -= 1; 830 | if (zoneRegion.loop_end > fontSampleCount) zoneRegion.loop_end = fontSampleCount; 831 | if (zoneRegion.pitch_keycenter == -1) zoneRegion.pitch_keycenter = pshdr->originalPitch; 832 | zoneRegion.tune += pshdr->pitchCorrection; 833 | zoneRegion.sample_rate = pshdr->sampleRate; 834 | if (zoneRegion.end && zoneRegion.end < fontSampleCount) zoneRegion.end++; 835 | else zoneRegion.end = fontSampleCount; 836 | 837 | preset->regions[region_index] = zoneRegion; 838 | region_index++; 839 | hadSampleID = 1; 840 | } 841 | else tsf_region_operator(&zoneRegion, pigen->genOper, &pigen->genAmount, TSF_NULL); 842 | } 843 | 844 | // Handle instrument's global zone. 845 | if (pibag == hydra->ibags + pinst->instBagNdx && !hadSampleID) 846 | instRegion = zoneRegion; 847 | 848 | // Modulators (TODO) 849 | //if (ibag->instModNdx < ibag[1].instModNdx) addUnsupportedOpcode("any modulator"); 850 | } 851 | hadGenInstrument = 1; 852 | } 853 | else tsf_region_operator(&presetRegion, ppgen->genOper, &ppgen->genAmount, TSF_NULL); 854 | } 855 | 856 | // Modulators (TODO) 857 | //if (pbag->modNdx < pbag[1].modNdx) addUnsupportedOpcode("any modulator"); 858 | 859 | // Handle preset's global zone. 860 | if (ppbag == hydra->pbags + pphdr->presetBagNdx && !hadGenInstrument) 861 | globalRegion = presetRegion; 862 | } 863 | } 864 | return 1; 865 | } 866 | 867 | #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H 868 | static int tsf_decode_ogg(const tsf_u8 *pSmpl, const tsf_u8 *pSmplEnd, float** pRes, tsf_u32* pResNum, tsf_u32* pResMax, tsf_u32 resInitial) 869 | { 870 | float *res = *pRes, *oldres; tsf_u32 resNum = *pResNum; tsf_u32 resMax = *pResMax; stb_vorbis *v; 871 | 872 | // Use whatever stb_vorbis API that is available (either pull or push) 873 | #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) 874 | v = stb_vorbis_open_memory(pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, TSF_NULL); 875 | #else 876 | { int use, err; v = stb_vorbis_open_pushdata(pSmpl, (int)(pSmplEnd - pSmpl), &use, &err, TSF_NULL); pSmpl += use; } 877 | #endif 878 | if (v == TSF_NULL) return 0; 879 | 880 | for (;;) 881 | { 882 | float** outputs; int n_samples; 883 | 884 | // Decode one frame of vorbis samples with whatever stb_vorbis API that is available 885 | #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) 886 | n_samples = stb_vorbis_get_frame_float(v, TSF_NULL, &outputs); 887 | if (!n_samples) break; 888 | #else 889 | if (pSmpl >= pSmplEnd) break; 890 | { int use = stb_vorbis_decode_frame_pushdata(v, pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, &outputs, &n_samples); pSmpl += use; } 891 | if (!n_samples) continue; 892 | #endif 893 | 894 | // Expand our output buffer if necessary then copy over the decoded frame samples 895 | resNum += n_samples; 896 | if (resNum > resMax) 897 | { 898 | do { resMax += (resMax ? (resMax < 1048576 ? resMax : 1048576) : resInitial); } while (resNum > resMax); 899 | oldres = res; 900 | res = (float*)TSF_REALLOC(res, resMax * sizeof(float)); 901 | if (!res) { TSF_FREE(oldres); stb_vorbis_close(v); return 0; } 902 | } 903 | TSF_MEMCPY(res + resNum - n_samples, outputs[0], n_samples * sizeof(float)); 904 | } 905 | stb_vorbis_close(v); 906 | *pRes = res; *pResNum = resNum; *pResMax = resMax; 907 | return 1; 908 | } 909 | 910 | static int tsf_decode_sf3_samples(const void* rawBuffer, float** pFloatBuffer, unsigned int* pSmplCount, struct tsf_hydra *hydra) 911 | { 912 | const tsf_u8* smplBuffer = (const tsf_u8*)rawBuffer; 913 | tsf_u32 smplLength = *pSmplCount, resNum = 0, resMax = 0, resInitial = (smplLength > 0x100000 ? (smplLength & ~0xFFFFF) : 65536); 914 | float *res = TSF_NULL, *oldres; 915 | int i, shdrLast = hydra->shdrNum - 1, is_sf3 = 0; 916 | for (i = 0; i <= shdrLast; i++) 917 | { 918 | struct tsf_hydra_shdr *shdr = &hydra->shdrs[i]; 919 | if (shdr->sampleType & 0x30) // compression flags (sometimes Vorbis flag) 920 | { 921 | const tsf_u8 *pSmpl = smplBuffer + shdr->start, *pSmplEnd = smplBuffer + shdr->end; 922 | if (pSmpl + 4 > pSmplEnd || !TSF_FourCCEquals(pSmpl, "OggS")) 923 | { 924 | shdr->start = shdr->end = shdr->startLoop = shdr->endLoop = 0; 925 | continue; 926 | } 927 | 928 | // Fix up sample indices in shdr (end index is set after decoding) 929 | shdr->start = resNum; 930 | shdr->startLoop += resNum; 931 | shdr->endLoop += resNum; 932 | if (!tsf_decode_ogg(pSmpl, pSmplEnd, &res, &resNum, &resMax, resInitial)) { TSF_FREE(res); return 0; } 933 | shdr->end = resNum; 934 | is_sf3 = 1; 935 | } 936 | else // raw PCM sample 937 | { 938 | float *out; short *in = (short*)smplBuffer + resNum, *inEnd; tsf_u32 oldResNum = resNum; 939 | if (is_sf3) // Fix up sample indices in shdr 940 | { 941 | tsf_u32 fix_offset = resNum - shdr->start; 942 | in -= fix_offset; 943 | shdr->start = resNum; 944 | shdr->end += fix_offset; 945 | shdr->startLoop += fix_offset; 946 | shdr->endLoop += fix_offset; 947 | } 948 | inEnd = in + ((shdr->end >= shdr->endLoop ? shdr->end : shdr->endLoop) - resNum); 949 | if (i == shdrLast || (tsf_u8*)inEnd > (smplBuffer + smplLength)) inEnd = (short*)(smplBuffer + smplLength); 950 | if (inEnd <= in) continue; 951 | 952 | // expand our output buffer if necessary then convert the PCM data from short to float 953 | resNum += (tsf_u32)(inEnd - in); 954 | if (resNum > resMax) 955 | { 956 | do { resMax += (resMax ? (resMax < 1048576 ? resMax : 1048576) : resInitial); } while (resNum > resMax); 957 | oldres = res; 958 | res = (float*)TSF_REALLOC(res, resMax * sizeof(float)); 959 | if (!res) { TSF_FREE(oldres); return 0; } 960 | } 961 | 962 | // Convert the samples from short to float 963 | for (out = res + oldResNum; in < inEnd;) 964 | *(out++) = (float)(*(in++) / 32767.0); 965 | } 966 | } 967 | 968 | // Trim the sample buffer down then return success (unless out of memory) 969 | if (!(*pFloatBuffer = (float*)TSF_REALLOC(res, resNum * sizeof(float)))) *pFloatBuffer = res; 970 | *pSmplCount = resNum; 971 | return (res ? 1 : 0); 972 | } 973 | #endif 974 | 975 | static int tsf_load_samples(void** pRawBuffer, float** pFloatBuffer, unsigned int* pSmplCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream) 976 | { 977 | #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H 978 | // With OGG Vorbis support we cannot pre-allocate the memory for tsf_decode_sf3_samples 979 | tsf_u32 resNum, resMax; float* oldres; 980 | *pSmplCount = chunkSmpl->size; 981 | *pRawBuffer = (void*)TSF_MALLOC(*pSmplCount); 982 | if (!*pRawBuffer || !stream->read(stream->data, *pRawBuffer, chunkSmpl->size)) return 0; 983 | if (chunkSmpl->id[3] != 'o') return 1; 984 | 985 | // Decode custom .sfo 'smpo' format where all samples are in a single ogg stream 986 | resNum = resMax = 0; 987 | if (!tsf_decode_ogg((tsf_u8*)*pRawBuffer, (tsf_u8*)*pRawBuffer + chunkSmpl->size, pFloatBuffer, &resNum, &resMax, 65536)) return 0; 988 | oldres = *pFloatBuffer; 989 | if (!(*pFloatBuffer = (float*)TSF_REALLOC(*pFloatBuffer, resNum * sizeof(float)))) *pFloatBuffer = oldres; 990 | *pSmplCount = resNum; 991 | return (*pFloatBuffer ? 1 : 0); 992 | #else 993 | // Inline convert the samples from short to float 994 | float *res, *out; const short *in; 995 | (void)pRawBuffer; 996 | *pSmplCount = chunkSmpl->size / (unsigned int)sizeof(short); 997 | *pFloatBuffer = (float*)TSF_MALLOC(*pSmplCount * sizeof(float)); 998 | if (!*pFloatBuffer || !stream->read(stream->data, *pFloatBuffer, chunkSmpl->size)) return 0; 999 | for (res = *pFloatBuffer, out = res + *pSmplCount, in = (short*)res + *pSmplCount; out != res;) 1000 | *(--out) = (float)(*(--in) / 32767.0); 1001 | return 1; 1002 | #endif 1003 | } 1004 | 1005 | static int tsf_voice_envelope_release_samples(struct tsf_voice_envelope* e, float outSampleRate) 1006 | { 1007 | return (int)((e->parameters.release <= 0 ? TSF_FASTRELEASETIME : e->parameters.release) * outSampleRate); 1008 | } 1009 | 1010 | static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate) 1011 | { 1012 | switch (active_segment) 1013 | { 1014 | case TSF_SEGMENT_NONE: 1015 | e->samplesUntilNextSegment = (int)(e->parameters.delay * outSampleRate); 1016 | if (e->samplesUntilNextSegment > 0) 1017 | { 1018 | e->segment = TSF_SEGMENT_DELAY; 1019 | e->segmentIsExponential = TSF_FALSE; 1020 | e->level = 0.0; 1021 | e->slope = 0.0; 1022 | return; 1023 | } 1024 | /* fall through */ 1025 | case TSF_SEGMENT_DELAY: 1026 | e->samplesUntilNextSegment = (int)(e->parameters.attack * outSampleRate); 1027 | if (e->samplesUntilNextSegment > 0) 1028 | { 1029 | if (!e->isAmpEnv) 1030 | { 1031 | //mod env attack duration scales with velocity (velocity of 1 is full duration, max velocity is 0.125 times duration) 1032 | e->samplesUntilNextSegment = (int)(e->parameters.attack * ((145 - e->midiVelocity) / 144.0f) * outSampleRate); 1033 | } 1034 | e->segment = TSF_SEGMENT_ATTACK; 1035 | e->segmentIsExponential = TSF_FALSE; 1036 | e->level = 0.0f; 1037 | e->slope = 1.0f / e->samplesUntilNextSegment; 1038 | return; 1039 | } 1040 | /* fall through */ 1041 | case TSF_SEGMENT_ATTACK: 1042 | e->samplesUntilNextSegment = (int)(e->parameters.hold * outSampleRate); 1043 | if (e->samplesUntilNextSegment > 0) 1044 | { 1045 | e->segment = TSF_SEGMENT_HOLD; 1046 | e->segmentIsExponential = TSF_FALSE; 1047 | e->level = 1.0f; 1048 | e->slope = 0.0f; 1049 | return; 1050 | } 1051 | /* fall through */ 1052 | case TSF_SEGMENT_HOLD: 1053 | e->samplesUntilNextSegment = (int)(e->parameters.decay * outSampleRate); 1054 | if (e->samplesUntilNextSegment > 0) 1055 | { 1056 | e->segment = TSF_SEGMENT_DECAY; 1057 | e->level = 1.0f; 1058 | if (e->isAmpEnv) 1059 | { 1060 | // I don't truly understand this; just following what LinuxSampler does. 1061 | float mysterySlope = -9.226f / e->samplesUntilNextSegment; 1062 | e->slope = TSF_EXPF(mysterySlope); 1063 | e->segmentIsExponential = TSF_TRUE; 1064 | if (e->parameters.sustain > 0.0f) 1065 | { 1066 | // Again, this is following LinuxSampler's example, which is similar to 1067 | // SF2-style decay, where "decay" specifies the time it would take to 1068 | // get to zero, not to the sustain level. The SFZ spec is not that 1069 | // specific about what "decay" means, so perhaps it's really supposed 1070 | // to specify the time to reach the sustain level. 1071 | e->samplesUntilNextSegment = (int)(TSF_LOG(e->parameters.sustain) / mysterySlope); 1072 | } 1073 | } 1074 | else 1075 | { 1076 | e->slope = -1.0f / e->samplesUntilNextSegment; 1077 | e->samplesUntilNextSegment = (int)(e->parameters.decay * (1.0f - e->parameters.sustain) * outSampleRate); 1078 | e->segmentIsExponential = TSF_FALSE; 1079 | } 1080 | return; 1081 | } 1082 | /* fall through */ 1083 | case TSF_SEGMENT_DECAY: 1084 | e->segment = TSF_SEGMENT_SUSTAIN; 1085 | e->level = e->parameters.sustain; 1086 | e->slope = 0.0f; 1087 | e->samplesUntilNextSegment = 0x7FFFFFFF; 1088 | e->segmentIsExponential = TSF_FALSE; 1089 | return; 1090 | case TSF_SEGMENT_SUSTAIN: 1091 | e->segment = TSF_SEGMENT_RELEASE; 1092 | e->samplesUntilNextSegment = tsf_voice_envelope_release_samples(e, outSampleRate); 1093 | if (e->isAmpEnv) 1094 | { 1095 | // I don't truly understand this; just following what LinuxSampler does. 1096 | float mysterySlope = -9.226f / e->samplesUntilNextSegment; 1097 | e->slope = TSF_EXPF(mysterySlope); 1098 | e->segmentIsExponential = TSF_TRUE; 1099 | } 1100 | else 1101 | { 1102 | e->slope = -e->level / e->samplesUntilNextSegment; 1103 | e->segmentIsExponential = TSF_FALSE; 1104 | } 1105 | return; 1106 | case TSF_SEGMENT_RELEASE: 1107 | default: 1108 | e->segment = TSF_SEGMENT_DONE; 1109 | e->segmentIsExponential = TSF_FALSE; 1110 | e->level = e->slope = 0.0f; 1111 | e->samplesUntilNextSegment = 0x7FFFFFF; 1112 | } 1113 | } 1114 | 1115 | 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) 1116 | { 1117 | e->parameters = *new_parameters; 1118 | if (e->parameters.keynumToHold) 1119 | { 1120 | e->parameters.hold += e->parameters.keynumToHold * (60.0f - midiNoteNumber); 1121 | e->parameters.hold = (e->parameters.hold < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.hold)); 1122 | } 1123 | if (e->parameters.keynumToDecay) 1124 | { 1125 | e->parameters.decay += e->parameters.keynumToDecay * (60.0f - midiNoteNumber); 1126 | e->parameters.decay = (e->parameters.decay < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.decay)); 1127 | } 1128 | e->midiVelocity = midiVelocity; 1129 | e->isAmpEnv = isAmpEnv; 1130 | tsf_voice_envelope_nextsegment(e, TSF_SEGMENT_NONE, outSampleRate); 1131 | } 1132 | 1133 | static void tsf_voice_envelope_process(struct tsf_voice_envelope* e, int numSamples, float outSampleRate) 1134 | { 1135 | if (e->slope) 1136 | { 1137 | if (e->segmentIsExponential) e->level *= TSF_POWF(e->slope, (float)numSamples); 1138 | else e->level += (e->slope * numSamples); 1139 | } 1140 | if ((e->samplesUntilNextSegment -= numSamples) <= 0) 1141 | tsf_voice_envelope_nextsegment(e, e->segment, outSampleRate); 1142 | } 1143 | 1144 | static void tsf_voice_lowpass_setup(struct tsf_voice_lowpass* e, float Fc) 1145 | { 1146 | // Lowpass filter from http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 1147 | double K = TSF_TAN(TSF_PI * Fc), KK = K * K; 1148 | double norm = 1 / (1 + K * e->QInv + KK); 1149 | e->a0 = KK * norm; 1150 | e->a1 = 2 * e->a0; 1151 | e->b1 = 2 * (KK - 1) * norm; 1152 | e->b2 = (1 - K * e->QInv + KK) * norm; 1153 | } 1154 | 1155 | static float tsf_voice_lowpass_process(struct tsf_voice_lowpass* e, double In) 1156 | { 1157 | 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; 1158 | } 1159 | 1160 | static void tsf_voice_lfo_setup(struct tsf_voice_lfo* e, float delay, int freqCents, float outSampleRate) 1161 | { 1162 | e->samplesUntil = (int)(delay * outSampleRate); 1163 | e->delta = (4.0f * tsf_cents2Hertz((float)freqCents) / outSampleRate); 1164 | e->level = 0; 1165 | } 1166 | 1167 | static void tsf_voice_lfo_process(struct tsf_voice_lfo* e, int blockSamples) 1168 | { 1169 | if (e->samplesUntil > blockSamples) { e->samplesUntil -= blockSamples; return; } 1170 | e->level += e->delta * blockSamples; 1171 | if (e->level > 1.0f) { e->delta = -e->delta; e->level = 2.0f - e->level; } 1172 | else if (e->level < -1.0f) { e->delta = -e->delta; e->level = -2.0f - e->level; } 1173 | } 1174 | 1175 | static void tsf_voice_kill(struct tsf_voice* v) 1176 | { 1177 | v->playingPreset = -1; 1178 | } 1179 | 1180 | static void tsf_voice_end(tsf* f, struct tsf_voice* v) 1181 | { 1182 | // if maxVoiceNum is set, assume that voice rendering and note queuing are on separate threads 1183 | // so to minimize the chance that voice rendering would advance the segment at the same time 1184 | // we just do it twice here and hope that it sticks 1185 | int repeats = (f->maxVoiceNum ? 2 : 1); 1186 | while (repeats--) 1187 | { 1188 | tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); 1189 | tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); 1190 | if (v->region->loop_mode == TSF_LOOPMODE_SUSTAIN) 1191 | { 1192 | // Continue playing, but stop looping. 1193 | v->loopEnd = v->loopStart; 1194 | } 1195 | } 1196 | } 1197 | 1198 | static void tsf_voice_endquick(tsf* f, struct tsf_voice* v) 1199 | { 1200 | // if maxVoiceNum is set, assume that voice rendering and note queuing are on separate threads 1201 | // so to minimize the chance that voice rendering would advance the segment at the same time 1202 | // we just do it twice here and hope that it sticks 1203 | int repeats = (f->maxVoiceNum ? 2 : 1); 1204 | while (repeats--) 1205 | { 1206 | v->ampenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); 1207 | v->modenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); 1208 | } 1209 | } 1210 | 1211 | static void tsf_voice_calcpitchratio(struct tsf_voice* v, float pitchShift, float outSampleRate) 1212 | { 1213 | double note = v->playingKey + v->region->transpose + v->region->tune / 100.0; 1214 | double adjustedPitch = v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0); 1215 | if (pitchShift) adjustedPitch += pitchShift; 1216 | v->pitchInputTimecents = adjustedPitch * 100.0; 1217 | v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate); 1218 | } 1219 | 1220 | static void tsf_voice_render(tsf* f, struct tsf_voice* v, float* outputBuffer, int numSamples) 1221 | { 1222 | struct tsf_region* region = v->region; 1223 | float* input = f->fontSamples; 1224 | float* outL = outputBuffer; 1225 | float* outR = (f->outputmode == TSF_STEREO_UNWEAVED ? outL + numSamples : TSF_NULL); 1226 | 1227 | // Cache some values, to give them at least some chance of ending up in registers. 1228 | TSF_BOOL updateModEnv = (region->modEnvToPitch || region->modEnvToFilterFc); 1229 | TSF_BOOL updateModLFO = (v->modlfo.delta && (region->modLfoToPitch || region->modLfoToFilterFc || region->modLfoToVolume)); 1230 | TSF_BOOL updateVibLFO = (v->viblfo.delta && (region->vibLfoToPitch)); 1231 | TSF_BOOL isLooping = (v->loopStart < v->loopEnd); 1232 | unsigned int tmpLoopStart = v->loopStart, tmpLoopEnd = v->loopEnd; 1233 | double tmpSampleEndDbl = (double)region->end, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0; 1234 | double tmpSourceSamplePosition = v->sourceSamplePosition; 1235 | struct tsf_voice_lowpass tmpLowpass = v->lowpass; 1236 | 1237 | TSF_BOOL dynamicLowpass = (region->modLfoToFilterFc || region->modEnvToFilterFc); 1238 | float tmpSampleRate = f->outSampleRate, tmpInitialFilterFc, tmpModLfoToFilterFc, tmpModEnvToFilterFc; 1239 | 1240 | TSF_BOOL dynamicPitchRatio = (region->modLfoToPitch || region->modEnvToPitch || region->vibLfoToPitch); 1241 | double pitchRatio; 1242 | float tmpModLfoToPitch, tmpVibLfoToPitch, tmpModEnvToPitch; 1243 | 1244 | TSF_BOOL dynamicGain = (region->modLfoToVolume != 0); 1245 | float noteGain = 0, tmpModLfoToVolume; 1246 | 1247 | if (dynamicLowpass) tmpInitialFilterFc = (float)region->initialFilterFc, tmpModLfoToFilterFc = (float)region->modLfoToFilterFc, tmpModEnvToFilterFc = (float)region->modEnvToFilterFc; 1248 | else tmpInitialFilterFc = 0, tmpModLfoToFilterFc = 0, tmpModEnvToFilterFc = 0; 1249 | 1250 | if (dynamicPitchRatio) pitchRatio = 0, tmpModLfoToPitch = (float)region->modLfoToPitch, tmpVibLfoToPitch = (float)region->vibLfoToPitch, tmpModEnvToPitch = (float)region->modEnvToPitch; 1251 | else pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents) * v->pitchOutputFactor, tmpModLfoToPitch = 0, tmpVibLfoToPitch = 0, tmpModEnvToPitch = 0; 1252 | 1253 | if (dynamicGain) tmpModLfoToVolume = (float)region->modLfoToVolume * 0.1f; 1254 | else noteGain = tsf_decibelsToGain(v->noteGainDB), tmpModLfoToVolume = 0; 1255 | 1256 | while (numSamples) 1257 | { 1258 | float gainMono, gainLeft, gainRight; 1259 | int blockSamples = (numSamples > TSF_RENDER_EFFECTSAMPLEBLOCK ? TSF_RENDER_EFFECTSAMPLEBLOCK : numSamples); 1260 | numSamples -= blockSamples; 1261 | 1262 | if (dynamicLowpass) 1263 | { 1264 | float fres = tmpInitialFilterFc + v->modlfo.level * tmpModLfoToFilterFc + v->modenv.level * tmpModEnvToFilterFc; 1265 | float lowpassFc = (fres <= 13500 ? tsf_cents2Hertz(fres) / tmpSampleRate : 1.0f); 1266 | tmpLowpass.active = (lowpassFc < 0.499f); 1267 | if (tmpLowpass.active) tsf_voice_lowpass_setup(&tmpLowpass, lowpassFc); 1268 | } 1269 | 1270 | if (dynamicPitchRatio) 1271 | pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents + (v->modlfo.level * tmpModLfoToPitch + v->viblfo.level * tmpVibLfoToPitch + v->modenv.level * tmpModEnvToPitch)) * v->pitchOutputFactor; 1272 | 1273 | if (dynamicGain) 1274 | noteGain = tsf_decibelsToGain(v->noteGainDB + (v->modlfo.level * tmpModLfoToVolume)); 1275 | 1276 | gainMono = noteGain * v->ampenv.level; 1277 | 1278 | // Update EG. 1279 | tsf_voice_envelope_process(&v->ampenv, blockSamples, tmpSampleRate); 1280 | if (updateModEnv) tsf_voice_envelope_process(&v->modenv, blockSamples, tmpSampleRate); 1281 | 1282 | // Update LFOs. 1283 | if (updateModLFO) tsf_voice_lfo_process(&v->modlfo, blockSamples); 1284 | if (updateVibLFO) tsf_voice_lfo_process(&v->viblfo, blockSamples); 1285 | 1286 | switch (f->outputmode) 1287 | { 1288 | case TSF_STEREO_INTERLEAVED: 1289 | gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight; 1290 | while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) 1291 | { 1292 | unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); 1293 | 1294 | // Simple linear interpolation. 1295 | float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); 1296 | 1297 | // Low-pass filter. 1298 | if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); 1299 | 1300 | *outL++ += val * gainLeft; 1301 | *outL++ += val * gainRight; 1302 | 1303 | // Next sample. 1304 | tmpSourceSamplePosition += pitchRatio; 1305 | if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); 1306 | } 1307 | break; 1308 | 1309 | case TSF_STEREO_UNWEAVED: 1310 | gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight; 1311 | while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) 1312 | { 1313 | unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); 1314 | 1315 | // Simple linear interpolation. 1316 | float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); 1317 | 1318 | // Low-pass filter. 1319 | if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); 1320 | 1321 | *outL++ += val * gainLeft; 1322 | *outR++ += val * gainRight; 1323 | 1324 | // Next sample. 1325 | tmpSourceSamplePosition += pitchRatio; 1326 | if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); 1327 | } 1328 | break; 1329 | 1330 | case TSF_MONO: 1331 | while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) 1332 | { 1333 | unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); 1334 | 1335 | // Simple linear interpolation. 1336 | float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); 1337 | 1338 | // Low-pass filter. 1339 | if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); 1340 | 1341 | *outL++ += val * gainMono; 1342 | 1343 | // Next sample. 1344 | tmpSourceSamplePosition += pitchRatio; 1345 | if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); 1346 | } 1347 | break; 1348 | } 1349 | 1350 | if (tmpSourceSamplePosition >= tmpSampleEndDbl || v->ampenv.segment == TSF_SEGMENT_DONE) 1351 | { 1352 | tsf_voice_kill(v); 1353 | return; 1354 | } 1355 | } 1356 | 1357 | v->sourceSamplePosition = tmpSourceSamplePosition; 1358 | if (tmpLowpass.active || dynamicLowpass) v->lowpass = tmpLowpass; 1359 | } 1360 | 1361 | TSFDEF tsf* tsf_load(struct tsf_stream* stream) 1362 | { 1363 | tsf* res = TSF_NULL; 1364 | struct tsf_riffchunk chunkHead; 1365 | struct tsf_riffchunk chunkList; 1366 | struct tsf_hydra hydra; 1367 | void* rawBuffer = TSF_NULL; 1368 | float* floatBuffer = TSF_NULL; 1369 | tsf_u32 smplCount = 0; 1370 | 1371 | if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk")) 1372 | { 1373 | //if (e) *e = TSF_INVALID_NOSF2HEADER; 1374 | return res; 1375 | } 1376 | 1377 | // Read hydra and locate sample data. 1378 | TSF_MEMSET(&hydra, 0, sizeof(hydra)); 1379 | while (tsf_riffchunk_read(&chunkHead, &chunkList, stream)) 1380 | { 1381 | struct tsf_riffchunk chunk; 1382 | if (TSF_FourCCEquals(chunkList.id, "pdta")) 1383 | { 1384 | while (tsf_riffchunk_read(&chunkList, &chunk, stream)) 1385 | { 1386 | #define HandleChunk(chunkName) (TSF_FourCCEquals(chunk.id, #chunkName) && !(chunk.size % chunkName##SizeInFile)) \ 1387 | { \ 1388 | int num = chunk.size / chunkName##SizeInFile, i; \ 1389 | hydra.chunkName##Num = num; \ 1390 | hydra.chunkName##s = (struct tsf_hydra_##chunkName*)TSF_MALLOC(num * sizeof(struct tsf_hydra_##chunkName)); \ 1391 | if (!hydra.chunkName##s) goto out_of_memory; \ 1392 | for (i = 0; i < num; ++i) tsf_hydra_read_##chunkName(&hydra.chunkName##s[i], stream); \ 1393 | } 1394 | enum 1395 | { 1396 | phdrSizeInFile = 38, pbagSizeInFile = 4, pmodSizeInFile = 10, 1397 | pgenSizeInFile = 4, instSizeInFile = 22, ibagSizeInFile = 4, 1398 | imodSizeInFile = 10, igenSizeInFile = 4, shdrSizeInFile = 46 1399 | }; 1400 | if HandleChunk(phdr) else if HandleChunk(pbag) else if HandleChunk(pmod) 1401 | else if HandleChunk(pgen) else if HandleChunk(inst) else if HandleChunk(ibag) 1402 | else if HandleChunk(imod) else if HandleChunk(igen) else if HandleChunk(shdr) 1403 | else stream->skip(stream->data, chunk.size); 1404 | #undef HandleChunk 1405 | } 1406 | } 1407 | else if (TSF_FourCCEquals(chunkList.id, "sdta")) 1408 | { 1409 | while (tsf_riffchunk_read(&chunkList, &chunk, stream)) 1410 | { 1411 | if ((TSF_FourCCEquals(chunk.id, "smpl") 1412 | #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H 1413 | || TSF_FourCCEquals(chunk.id, "smpo") 1414 | #endif 1415 | ) && !rawBuffer && !floatBuffer && chunk.size >= sizeof(short)) 1416 | { 1417 | if (!tsf_load_samples(&rawBuffer, &floatBuffer, &smplCount, &chunk, stream)) goto out_of_memory; 1418 | } 1419 | else stream->skip(stream->data, chunk.size); 1420 | } 1421 | } 1422 | else stream->skip(stream->data, chunkList.size); 1423 | } 1424 | if (!hydra.phdrs || !hydra.pbags || !hydra.pmods || !hydra.pgens || !hydra.insts || !hydra.ibags || !hydra.imods || !hydra.igens || !hydra.shdrs) 1425 | { 1426 | //if (e) *e = TSF_INVALID_INCOMPLETE; 1427 | } 1428 | else if (!rawBuffer && !floatBuffer) 1429 | { 1430 | //if (e) *e = TSF_INVALID_NOSAMPLEDATA; 1431 | } 1432 | else 1433 | { 1434 | #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H 1435 | if (!floatBuffer && !tsf_decode_sf3_samples(rawBuffer, &floatBuffer, &smplCount, &hydra)) goto out_of_memory; 1436 | #endif 1437 | res = (tsf*)TSF_MALLOC(sizeof(tsf)); 1438 | if (res) TSF_MEMSET(res, 0, sizeof(tsf)); 1439 | if (!res || !tsf_load_presets(res, &hydra, smplCount)) goto out_of_memory; 1440 | res->outSampleRate = 44100.0f; 1441 | res->fontSamples = floatBuffer; 1442 | floatBuffer = TSF_NULL; // don't free below 1443 | } 1444 | if (0) 1445 | { 1446 | out_of_memory: 1447 | TSF_FREE(res); 1448 | res = TSF_NULL; 1449 | //if (e) *e = TSF_OUT_OF_MEMORY; 1450 | } 1451 | TSF_FREE(hydra.phdrs); TSF_FREE(hydra.pbags); TSF_FREE(hydra.pmods); 1452 | TSF_FREE(hydra.pgens); TSF_FREE(hydra.insts); TSF_FREE(hydra.ibags); 1453 | TSF_FREE(hydra.imods); TSF_FREE(hydra.igens); TSF_FREE(hydra.shdrs); 1454 | TSF_FREE(rawBuffer); TSF_FREE(floatBuffer); 1455 | return res; 1456 | } 1457 | 1458 | TSFDEF tsf* tsf_copy(tsf* f) 1459 | { 1460 | tsf* res; 1461 | if (!f) return TSF_NULL; 1462 | if (!f->refCount) 1463 | { 1464 | f->refCount = (int*)TSF_MALLOC(sizeof(int)); 1465 | if (!f->refCount) return TSF_NULL; 1466 | *f->refCount = 1; 1467 | } 1468 | res = (tsf*)TSF_MALLOC(sizeof(tsf)); 1469 | if (!res) return TSF_NULL; 1470 | TSF_MEMCPY(res, f, sizeof(tsf)); 1471 | res->voices = TSF_NULL; 1472 | res->voiceNum = 0; 1473 | res->channels = TSF_NULL; 1474 | (*res->refCount)++; 1475 | return res; 1476 | } 1477 | 1478 | TSFDEF void tsf_close(tsf* f) 1479 | { 1480 | if (!f) return; 1481 | if (!f->refCount || !--(*f->refCount)) 1482 | { 1483 | struct tsf_preset *preset = f->presets, *presetEnd = preset + f->presetNum; 1484 | for (; preset != presetEnd; preset++) TSF_FREE(preset->regions); 1485 | TSF_FREE(f->presets); 1486 | TSF_FREE(f->fontSamples); 1487 | TSF_FREE(f->refCount); 1488 | } 1489 | TSF_FREE(f->channels); 1490 | TSF_FREE(f->voices); 1491 | TSF_FREE(f); 1492 | } 1493 | 1494 | TSFDEF void tsf_reset(tsf* f) 1495 | { 1496 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1497 | for (; v != vEnd; v++) 1498 | if (v->playingPreset != -1 && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release)) 1499 | tsf_voice_endquick(f, v); 1500 | if (f->channels) { TSF_FREE(f->channels); f->channels = TSF_NULL; } 1501 | } 1502 | 1503 | TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number) 1504 | { 1505 | const struct tsf_preset *presets; 1506 | int i, iMax; 1507 | for (presets = f->presets, i = 0, iMax = f->presetNum; i < iMax; i++) 1508 | if (presets[i].preset == preset_number && presets[i].bank == bank) 1509 | return i; 1510 | return -1; 1511 | } 1512 | 1513 | TSFDEF int tsf_get_presetcount(const tsf* f) 1514 | { 1515 | return f->presetNum; 1516 | } 1517 | 1518 | TSFDEF const char* tsf_get_presetname(const tsf* f, int preset) 1519 | { 1520 | return (preset < 0 || preset >= f->presetNum ? TSF_NULL : f->presets[preset].presetName); 1521 | } 1522 | 1523 | TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number) 1524 | { 1525 | return tsf_get_presetname(f, tsf_get_presetindex(f, bank, preset_number)); 1526 | } 1527 | 1528 | TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db) 1529 | { 1530 | f->outputmode = outputmode; 1531 | f->outSampleRate = (float)(samplerate >= 1 ? samplerate : 44100.0f); 1532 | f->globalGainDB = global_gain_db; 1533 | } 1534 | 1535 | TSFDEF void tsf_set_volume(tsf* f, float global_volume) 1536 | { 1537 | f->globalGainDB = (global_volume == 1.0f ? 0 : -tsf_gainToDecibels(1.0f / global_volume)); 1538 | } 1539 | 1540 | TSFDEF int tsf_set_max_voices(tsf* f, int max_voices) 1541 | { 1542 | int i = f->voiceNum; 1543 | int newVoiceNum = (f->voiceNum > max_voices ? f->voiceNum : max_voices); 1544 | struct tsf_voice *newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, newVoiceNum * sizeof(struct tsf_voice)); 1545 | if (!newVoices) return 0; 1546 | f->voices = newVoices; 1547 | f->voiceNum = f->maxVoiceNum = newVoiceNum; 1548 | for (; i < max_voices; i++) 1549 | f->voices[i].playingPreset = -1; 1550 | return 1; 1551 | } 1552 | 1553 | TSFDEF int tsf_note_on(tsf* f, int preset_index, int key, float vel) 1554 | { 1555 | short midiVelocity = (short)(vel * 127); 1556 | unsigned int voicePlayIndex; 1557 | struct tsf_region *region, *regionEnd; 1558 | 1559 | if (preset_index < 0 || preset_index >= f->presetNum) return 1; 1560 | if (vel <= 0.0f) { tsf_note_off(f, preset_index, key); return 1; } 1561 | 1562 | // Play all matching regions. 1563 | voicePlayIndex = f->voicePlayIndex++; 1564 | for (region = f->presets[preset_index].regions, regionEnd = region + f->presets[preset_index].regionNum; region != regionEnd; region++) 1565 | { 1566 | struct tsf_voice *voice, *v, *vEnd; TSF_BOOL doLoop; float lowpassFilterQDB, lowpassFc; 1567 | if (key < region->lokey || key > region->hikey || midiVelocity < region->lovel || midiVelocity > region->hivel) continue; 1568 | 1569 | voice = TSF_NULL, v = f->voices, vEnd = v + f->voiceNum; 1570 | if (region->group) 1571 | { 1572 | for (; v != vEnd; v++) 1573 | if (v->playingPreset == preset_index && v->region->group == region->group) tsf_voice_endquick(f, v); 1574 | else if (v->playingPreset == -1 && !voice) voice = v; 1575 | } 1576 | else for (; v != vEnd; v++) if (v->playingPreset == -1) { voice = v; break; } 1577 | 1578 | if (!voice) 1579 | { 1580 | if (f->maxVoiceNum) 1581 | { 1582 | // Voices have been pre-allocated and limited to a maximum, try to kill a voice off in its release envelope 1583 | int bestKillReleaseSamplePos = -999999999; 1584 | for (v = f->voices; v != vEnd; v++) 1585 | { 1586 | if (v->ampenv.segment == TSF_SEGMENT_RELEASE) 1587 | { 1588 | // We're looking for the voice furthest into its release 1589 | int releaseSamplesDone = tsf_voice_envelope_release_samples(&v->ampenv, f->outSampleRate) - v->ampenv.samplesUntilNextSegment; 1590 | if (releaseSamplesDone > bestKillReleaseSamplePos) 1591 | { 1592 | bestKillReleaseSamplePos = releaseSamplesDone; 1593 | voice = v; 1594 | } 1595 | } 1596 | } 1597 | if (!voice) 1598 | continue; 1599 | tsf_voice_kill(voice); 1600 | } 1601 | else 1602 | { 1603 | // Allocate more voices so we don't need to kill one off. 1604 | struct tsf_voice* newVoices; 1605 | f->voiceNum += 4; 1606 | newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice)); 1607 | if (!newVoices) return 0; 1608 | f->voices = newVoices; 1609 | voice = &f->voices[f->voiceNum - 4]; 1610 | voice[1].playingPreset = voice[2].playingPreset = voice[3].playingPreset = -1; 1611 | } 1612 | } 1613 | 1614 | voice->region = region; 1615 | voice->playingPreset = preset_index; 1616 | voice->playingKey = key; 1617 | voice->playIndex = voicePlayIndex; 1618 | voice->heldSustain = 0; 1619 | voice->noteGainDB = f->globalGainDB - region->attenuation - tsf_gainToDecibels(1.0f / vel); 1620 | 1621 | if (f->channels) 1622 | { 1623 | f->channels->setupVoice(f, voice); 1624 | } 1625 | else 1626 | { 1627 | tsf_voice_calcpitchratio(voice, 0, f->outSampleRate); 1628 | // 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). 1629 | voice->panFactorLeft = TSF_SQRTF(0.5f - region->pan); 1630 | voice->panFactorRight = TSF_SQRTF(0.5f + region->pan); 1631 | } 1632 | 1633 | // Offset/end. 1634 | voice->sourceSamplePosition = region->offset; 1635 | 1636 | // Loop. 1637 | doLoop = (region->loop_mode != TSF_LOOPMODE_NONE && region->loop_start < region->loop_end); 1638 | voice->loopStart = (doLoop ? region->loop_start : 0); 1639 | voice->loopEnd = (doLoop ? region->loop_end : 0); 1640 | 1641 | // Setup envelopes. 1642 | tsf_voice_envelope_setup(&voice->ampenv, ®ion->ampenv, key, midiVelocity, TSF_TRUE, f->outSampleRate); 1643 | tsf_voice_envelope_setup(&voice->modenv, ®ion->modenv, key, midiVelocity, TSF_FALSE, f->outSampleRate); 1644 | 1645 | // Setup lowpass filter. 1646 | lowpassFc = (region->initialFilterFc <= 13500 ? tsf_cents2Hertz((float)region->initialFilterFc) / f->outSampleRate : 1.0f); 1647 | lowpassFilterQDB = region->initialFilterQ / 10.0f; 1648 | voice->lowpass.QInv = 1.0 / TSF_POW(10.0, (lowpassFilterQDB / 20.0)); 1649 | voice->lowpass.z1 = voice->lowpass.z2 = 0; 1650 | voice->lowpass.active = (lowpassFc < 0.499f); 1651 | if (voice->lowpass.active) tsf_voice_lowpass_setup(&voice->lowpass, lowpassFc); 1652 | 1653 | // Setup LFO filters. 1654 | tsf_voice_lfo_setup(&voice->modlfo, region->delayModLFO, region->freqModLFO, f->outSampleRate); 1655 | tsf_voice_lfo_setup(&voice->viblfo, region->delayVibLFO, region->freqVibLFO, f->outSampleRate); 1656 | } 1657 | return 1; 1658 | } 1659 | 1660 | TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel) 1661 | { 1662 | int preset_index = tsf_get_presetindex(f, bank, preset_number); 1663 | if (preset_index == -1) return 0; 1664 | return tsf_note_on(f, preset_index, key, vel); 1665 | } 1666 | 1667 | TSFDEF void tsf_note_off(tsf* f, int preset_index, int key) 1668 | { 1669 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL; 1670 | for (; v != vEnd; v++) 1671 | { 1672 | //Find the first and last entry in the voices list with matching preset, key and look up the smallest play index 1673 | if (v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue; 1674 | else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v; 1675 | else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v; 1676 | } 1677 | if (!vMatchFirst) return; 1678 | for (v = vMatchFirst; v <= vMatchLast; v++) 1679 | { 1680 | //Stop all voices with matching preset, key and the smallest play index which was enumerated above 1681 | if (v != vMatchFirst && v != vMatchLast && 1682 | (v->playIndex != vMatchFirst->playIndex || v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue; 1683 | tsf_voice_end(f, v); 1684 | } 1685 | } 1686 | 1687 | TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key) 1688 | { 1689 | int preset_index = tsf_get_presetindex(f, bank, preset_number); 1690 | if (preset_index == -1) return 0; 1691 | tsf_note_off(f, preset_index, key); 1692 | return 1; 1693 | } 1694 | 1695 | TSFDEF void tsf_note_off_all(tsf* f) 1696 | { 1697 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1698 | for (; v != vEnd; v++) if (v->playingPreset != -1 && v->ampenv.segment < TSF_SEGMENT_RELEASE) 1699 | tsf_voice_end(f, v); 1700 | } 1701 | 1702 | TSFDEF int tsf_active_voice_count(tsf* f) 1703 | { 1704 | int count = 0; 1705 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1706 | for (; v != vEnd; v++) if (v->playingPreset != -1) count++; 1707 | return count; 1708 | } 1709 | 1710 | TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing) 1711 | { 1712 | float outputSamples[TSF_RENDER_SHORTBUFFERBLOCK]; 1713 | int channels = (f->outputmode == TSF_MONO ? 1 : 2), maxChannelSamples = TSF_RENDER_SHORTBUFFERBLOCK / channels; 1714 | while (samples > 0) 1715 | { 1716 | int channelSamples = (samples > maxChannelSamples ? maxChannelSamples : samples); 1717 | short* bufferEnd = buffer + channelSamples * channels; 1718 | float *floatSamples = outputSamples; 1719 | tsf_render_float(f, floatSamples, channelSamples, TSF_FALSE); 1720 | samples -= channelSamples; 1721 | 1722 | if (flag_mixing) 1723 | while (buffer != bufferEnd) 1724 | { 1725 | float v = *floatSamples++; 1726 | int vi = *buffer + (v < -1.00004566f ? (int)-32768 : (v > 1.00001514f ? (int)32767 : (int)(v * 32767.5f))); 1727 | *buffer++ = (vi < -32768 ? (short)-32768 : (vi > 32767 ? (short)32767 : (short)vi)); 1728 | } 1729 | else 1730 | while (buffer != bufferEnd) 1731 | { 1732 | float v = *floatSamples++; 1733 | *buffer++ = (v < -1.00004566f ? (short)-32768 : (v > 1.00001514f ? (short)32767 : (short)(v * 32767.5f))); 1734 | } 1735 | } 1736 | } 1737 | 1738 | TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing) 1739 | { 1740 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1741 | if (!flag_mixing) TSF_MEMSET(buffer, 0, (f->outputmode == TSF_MONO ? 1 : 2) * sizeof(float) * samples); 1742 | for (; v != vEnd; v++) 1743 | if (v->playingPreset != -1) 1744 | tsf_voice_render(f, v, buffer, samples); 1745 | } 1746 | 1747 | static void tsf_channel_setup_voice(tsf* f, struct tsf_voice* v) 1748 | { 1749 | struct tsf_channel* c = &f->channels->channels[f->channels->activeChannel]; 1750 | float newpan = v->region->pan + c->panOffset; 1751 | v->playingChannel = f->channels->activeChannel; 1752 | v->noteGainDB += c->gainDB; 1753 | tsf_voice_calcpitchratio(v, (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)), f->outSampleRate); 1754 | if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; } 1755 | else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; } 1756 | else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); } 1757 | } 1758 | 1759 | static struct tsf_channel* tsf_channel_init(tsf* f, int channel) 1760 | { 1761 | int i; 1762 | if (f->channels && channel < f->channels->channelNum) return &f->channels->channels[channel]; 1763 | if (!f->channels) 1764 | { 1765 | f->channels = (struct tsf_channels*)TSF_MALLOC(sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); 1766 | if (!f->channels) return TSF_NULL; 1767 | f->channels->setupVoice = &tsf_channel_setup_voice; 1768 | f->channels->channelNum = 0; 1769 | f->channels->activeChannel = 0; 1770 | } 1771 | else 1772 | { 1773 | struct tsf_channels *newChannels = (struct tsf_channels*)TSF_REALLOC(f->channels, sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); 1774 | if (!newChannels) return TSF_NULL; 1775 | f->channels = newChannels; 1776 | } 1777 | i = f->channels->channelNum; 1778 | f->channels->channelNum = channel + 1; 1779 | for (; i <= channel; i++) 1780 | { 1781 | struct tsf_channel* c = &f->channels->channels[i]; 1782 | c->presetIndex = c->bank = 0; 1783 | c->pitchWheel = c->midiPan = 8192; 1784 | c->midiVolume = c->midiExpression = 16383; 1785 | c->midiRPN = 0xFFFF; 1786 | c->midiData = c->sustain = 0; 1787 | c->panOffset = 0.0f; 1788 | c->gainDB = 0.0f; 1789 | c->pitchRange = 2.0f; 1790 | c->tuning = 0.0f; 1791 | } 1792 | return &f->channels->channels[channel]; 1793 | } 1794 | 1795 | static void tsf_channel_applypitch(tsf* f, int channel, struct tsf_channel* c) 1796 | { 1797 | struct tsf_voice *v, *vEnd; 1798 | float pitchShift = (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)); 1799 | for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) 1800 | if (v->playingPreset != -1 && v->playingChannel == channel) 1801 | tsf_voice_calcpitchratio(v, pitchShift, f->outSampleRate); 1802 | } 1803 | 1804 | TSFDEF int tsf_channel_set_presetindex(tsf* f, int channel, int preset_index) 1805 | { 1806 | struct tsf_channel *c = tsf_channel_init(f, channel); 1807 | if (!c) return 0; 1808 | c->presetIndex = (unsigned short)preset_index; 1809 | return 1; 1810 | } 1811 | 1812 | TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums) 1813 | { 1814 | int preset_index; 1815 | struct tsf_channel *c = tsf_channel_init(f, channel); 1816 | if (!c) return 0; 1817 | if (flag_mididrums) 1818 | { 1819 | preset_index = tsf_get_presetindex(f, 128 | (c->bank & 0x7FFF), preset_number); 1820 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, preset_number); 1821 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, 0); 1822 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number); 1823 | } 1824 | else preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number); 1825 | if (preset_index == -1) preset_index = tsf_get_presetindex(f, 0, preset_number); 1826 | if (preset_index != -1) 1827 | { 1828 | c->presetIndex = (unsigned short)preset_index; 1829 | return 1; 1830 | } 1831 | return 0; 1832 | } 1833 | 1834 | TSFDEF int tsf_channel_set_bank(tsf* f, int channel, int bank) 1835 | { 1836 | struct tsf_channel *c = tsf_channel_init(f, channel); 1837 | if (!c) return 0; 1838 | c->bank = (unsigned short)bank; 1839 | return 1; 1840 | } 1841 | 1842 | TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number) 1843 | { 1844 | int preset_index; 1845 | struct tsf_channel *c = tsf_channel_init(f, channel); 1846 | if (!c) return 0; 1847 | preset_index = tsf_get_presetindex(f, bank, preset_number); 1848 | if (preset_index == -1) return 0; 1849 | c->presetIndex = (unsigned short)preset_index; 1850 | c->bank = (unsigned short)bank; 1851 | return 1; 1852 | } 1853 | 1854 | TSFDEF int tsf_channel_set_pan(tsf* f, int channel, float pan) 1855 | { 1856 | struct tsf_voice *v, *vEnd; 1857 | struct tsf_channel *c = tsf_channel_init(f, channel); 1858 | if (!c) return 0; 1859 | for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) 1860 | if (v->playingPreset != -1 && v->playingChannel == channel) 1861 | { 1862 | float newpan = v->region->pan + pan - 0.5f; 1863 | if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; } 1864 | else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; } 1865 | else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); } 1866 | } 1867 | c->panOffset = pan - 0.5f; 1868 | return 1; 1869 | } 1870 | 1871 | TSFDEF int tsf_channel_set_volume(tsf* f, int channel, float volume) 1872 | { 1873 | float gainDB = tsf_gainToDecibels(volume), gainDBChange; 1874 | struct tsf_voice *v, *vEnd; 1875 | struct tsf_channel *c = tsf_channel_init(f, channel); 1876 | if (!c) return 0; 1877 | if (gainDB == c->gainDB) return 1; 1878 | for (v = f->voices, vEnd = v + f->voiceNum, gainDBChange = gainDB - c->gainDB; v != vEnd; v++) 1879 | if (v->playingPreset != -1 && v->playingChannel == channel) 1880 | v->noteGainDB += gainDBChange; 1881 | c->gainDB = gainDB; 1882 | return 1; 1883 | } 1884 | 1885 | TSFDEF int tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel) 1886 | { 1887 | struct tsf_channel *c = tsf_channel_init(f, channel); 1888 | if (!c) return 0; 1889 | if (c->pitchWheel == pitch_wheel) return 1; 1890 | c->pitchWheel = (unsigned short)pitch_wheel; 1891 | tsf_channel_applypitch(f, channel, c); 1892 | return 1; 1893 | } 1894 | 1895 | TSFDEF int tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range) 1896 | { 1897 | struct tsf_channel *c = tsf_channel_init(f, channel); 1898 | if (!c) return 0; 1899 | if (c->pitchRange == pitch_range) return 1; 1900 | c->pitchRange = pitch_range; 1901 | if (c->pitchWheel != 8192) tsf_channel_applypitch(f, channel, c); 1902 | return 1; 1903 | } 1904 | 1905 | TSFDEF int tsf_channel_set_tuning(tsf* f, int channel, float tuning) 1906 | { 1907 | struct tsf_channel *c = tsf_channel_init(f, channel); 1908 | if (!c) return 0; 1909 | if (c->tuning == tuning) return 1; 1910 | c->tuning = tuning; 1911 | tsf_channel_applypitch(f, channel, c); 1912 | return 1; 1913 | } 1914 | 1915 | TSFDEF int tsf_channel_set_sustain(tsf* f, int channel, int flag_sustain) 1916 | { 1917 | struct tsf_channel *c = tsf_channel_init(f, channel); 1918 | if (!c) return 0; 1919 | if (!c->sustain == !flag_sustain) return 1; 1920 | c->sustain = (unsigned short)(flag_sustain != 0); 1921 | //Turning on sustain does no action now, just starts note_off behaving differently 1922 | if (flag_sustain) return 1; 1923 | //Turning off sustain, actually end voices that got a note_off and were set to heldSustain status 1924 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1925 | for (; v != vEnd; v++) 1926 | if (v->playingPreset != -1 && v->playingChannel == channel && v->ampenv.segment < TSF_SEGMENT_RELEASE && v->heldSustain) 1927 | tsf_voice_end(f, v); 1928 | return 1; 1929 | } 1930 | 1931 | TSFDEF int tsf_channel_note_on(tsf* f, int channel, int key, float vel) 1932 | { 1933 | if (!f->channels || channel >= f->channels->channelNum) return 1; 1934 | f->channels->activeChannel = channel; 1935 | if (!vel) 1936 | { 1937 | tsf_channel_note_off(f, channel, key); 1938 | return 1; 1939 | } 1940 | return tsf_note_on(f, f->channels->channels[channel].presetIndex, key, vel); 1941 | } 1942 | 1943 | TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key) 1944 | { 1945 | unsigned sustain; 1946 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL; 1947 | for (; v != vEnd; v++) 1948 | { 1949 | //Find the first and last entry in the voices list with matching channel, key and look up the smallest play index 1950 | if (v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE || v->heldSustain) continue; 1951 | else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v; 1952 | else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v; 1953 | } 1954 | if (!vMatchFirst) return; 1955 | for (sustain = f->channels->channels[channel].sustain, v = vMatchFirst; v <= vMatchLast; v++) 1956 | { 1957 | //Stop all voices with matching channel, key and the smallest play index which was enumerated above 1958 | if (v != vMatchFirst && v != vMatchLast && 1959 | (v->playIndex != vMatchFirst->playIndex || v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue; 1960 | //Don't turn off if sustain is active, just mark as held by sustain so we don't forget it 1961 | if (sustain) 1962 | v->heldSustain = 1; 1963 | else 1964 | tsf_voice_end(f, v); 1965 | } 1966 | } 1967 | 1968 | TSFDEF void tsf_channel_note_off_all(tsf* f, int channel) 1969 | { 1970 | //Ignore sustain channel settings, note_off_all overrides 1971 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1972 | for (; v != vEnd; v++) 1973 | if (v->playingPreset != -1 && v->playingChannel == channel && v->ampenv.segment < TSF_SEGMENT_RELEASE) 1974 | tsf_voice_end(f, v); 1975 | } 1976 | 1977 | TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel) 1978 | { 1979 | struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; 1980 | for (; v != vEnd; v++) 1981 | if (v->playingPreset != -1 && v->playingChannel == channel && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release)) 1982 | tsf_voice_endquick(f, v); 1983 | } 1984 | 1985 | TSFDEF int tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value) 1986 | { 1987 | struct tsf_channel* c = tsf_channel_init(f, channel); 1988 | if (!c) return 0; 1989 | switch (controller) 1990 | { 1991 | case 7 /*VOLUME_MSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME; 1992 | case 39 /*VOLUME_LSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x3F80) | control_value); goto TCMC_SET_VOLUME; 1993 | case 11 /*EXPRESSION_MSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME; 1994 | case 43 /*EXPRESSION_LSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x3F80) | control_value); goto TCMC_SET_VOLUME; 1995 | case 10 /*PAN_MSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x7F ) | (control_value << 7)); goto TCMC_SET_PAN; 1996 | case 42 /*PAN_LSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x3F80) | control_value); goto TCMC_SET_PAN; 1997 | case 6 /*DATA_ENTRY_MSB*/ : c->midiData = (unsigned short)((c->midiData & 0x7F) | (control_value << 7)); goto TCMC_SET_DATA; 1998 | case 38 /*DATA_ENTRY_LSB*/ : c->midiData = (unsigned short)((c->midiData & 0x3F80) | control_value); goto TCMC_SET_DATA; 1999 | case 0 /*BANK_SELECT_MSB*/ : c->bank = (unsigned short)(0x8000 | control_value); return 1; //bank select MSB alone acts like LSB 2000 | case 32 /*BANK_SELECT_LSB*/ : c->bank = (unsigned short)((c->bank & 0x8000 ? ((c->bank & 0x7F) << 7) : 0) | control_value); return 1; 2001 | case 101 /*RPN_MSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x7F ) | (control_value << 7)); return 1; 2002 | case 100 /*RPN_LSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x3F80) | control_value); return 1; 2003 | case 98 /*NRPN_LSB*/ : c->midiRPN = 0xFFFF; return 1; 2004 | case 99 /*NRPN_MSB*/ : c->midiRPN = 0xFFFF; return 1; 2005 | case 64 /*SUSTAIN*/ : tsf_channel_set_sustain(f, channel, (int)(control_value >= 64)); return 1; 2006 | case 120 /*ALL_SOUND_OFF*/ : tsf_channel_sounds_off_all(f, channel); return 1; 2007 | case 123 /*ALL_NOTES_OFF*/ : tsf_channel_note_off_all(f, channel); return 1; 2008 | case 121 /*ALL_CTRL_OFF*/ : 2009 | c->midiVolume = c->midiExpression = 16383; 2010 | c->midiPan = 8192; 2011 | c->bank = 0; 2012 | c->midiRPN = 0xFFFF; 2013 | c->midiData = 0; 2014 | tsf_channel_set_volume(f, channel, 1.0f); 2015 | tsf_channel_set_pan(f, channel, 0.5f); 2016 | tsf_channel_set_pitchrange(f, channel, 2.0f); 2017 | tsf_channel_set_tuning(f, channel, 0); 2018 | return 1; 2019 | } 2020 | return 1; 2021 | TCMC_SET_VOLUME: 2022 | //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI 2023 | tsf_channel_set_volume(f, channel, TSF_POWF((c->midiVolume / 16383.0f) * (c->midiExpression / 16383.0f), 3.0f)); 2024 | return 1; 2025 | TCMC_SET_PAN: 2026 | tsf_channel_set_pan(f, channel, c->midiPan / 16383.0f); 2027 | return 1; 2028 | TCMC_SET_DATA: 2029 | if (c->midiRPN == 0) tsf_channel_set_pitchrange(f, channel, (c->midiData >> 7) + 0.01f * (c->midiData & 0x7F)); 2030 | else if (c->midiRPN == 1) tsf_channel_set_tuning(f, channel, (int)c->tuning + ((float)c->midiData - 8192.0f) / 8192.0f); //fine tune 2031 | 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 2032 | return 1; 2033 | } 2034 | 2035 | TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel) 2036 | { 2037 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].presetIndex : 0); 2038 | } 2039 | 2040 | TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel) 2041 | { 2042 | return (f->channels && channel < f->channels->channelNum ? (f->channels->channels[channel].bank & 0x7FFF) : 0); 2043 | } 2044 | 2045 | TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel) 2046 | { 2047 | return (f->channels && channel < f->channels->channelNum ? f->presets[f->channels->channels[channel].presetIndex].preset : 0); 2048 | } 2049 | 2050 | TSFDEF float tsf_channel_get_pan(tsf* f, int channel) 2051 | { 2052 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].panOffset - 0.5f : 0.5f); 2053 | } 2054 | 2055 | TSFDEF float tsf_channel_get_volume(tsf* f, int channel) 2056 | { 2057 | return (f->channels && channel < f->channels->channelNum ? tsf_decibelsToGain(f->channels->channels[channel].gainDB) : 1.0f); 2058 | } 2059 | 2060 | TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel) 2061 | { 2062 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchWheel : 8192); 2063 | } 2064 | 2065 | TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel) 2066 | { 2067 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchRange : 2.0f); 2068 | } 2069 | 2070 | TSFDEF float tsf_channel_get_tuning(tsf* f, int channel) 2071 | { 2072 | return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].tuning : 0.0f); 2073 | } 2074 | 2075 | #ifdef __cplusplus 2076 | } 2077 | #endif 2078 | 2079 | #endif //TSF_IMPLEMENTATION 2080 | --------------------------------------------------------------------------------