├── LICENSE ├── README.md ├── SFZero.cpp ├── SFZero.h ├── juce_module_info └── sfzero ├── RIFF.cpp ├── RIFF.h ├── SF2.cpp ├── SF2.h ├── SF2Generator.cpp ├── SF2Generator.h ├── SF2Reader.cpp ├── SF2Reader.h ├── SF2Sound.cpp ├── SF2Sound.h ├── SF2WinTypes.h ├── SFZCommon.h ├── SFZDebug.cpp ├── SFZDebug.h ├── SFZEG.cpp ├── SFZEG.h ├── SFZReader.cpp ├── SFZReader.h ├── SFZRegion.cpp ├── SFZRegion.h ├── SFZSample.cpp ├── SFZSample.h ├── SFZSound.cpp ├── SFZSound.h ├── SFZSynth.cpp ├── SFZSynth.h ├── SFZVoice.cpp ├── SFZVoice.h └── sf2-chunks ├── generators.h ├── ibag.h ├── igen.h ├── imod.h ├── inst.h ├── iver.h ├── pbag.h ├── pgen.h ├── phdr.h ├── pmod.h └── shdr.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Steve Folta 2 | Converted to Juce module (C) 2016 Leo Olivers 3 | Forked from https://github.com/stevefolta/SFZero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SFZero, the Juce module version (module only) 2 | 3 | This is a fork of the [original SFZero by Steve Folta](https://github.com/stevefolta/SFZero), with the following changes: 4 | 5 | * has been converted to a Juce module, so you can easily consume it from your own projects (you still get the sample player plugin, but it now includes that module) 6 | * requires Juce 4.1 or higher 7 | * supports Juce 4.1 module format 8 | * now also supports new Juce 4.2 module format (thanks to Loki Davison) 9 | * conveniently sits within its own `sfzero::` namespace 10 | * has a tidied-up code base, so it now builds with as few warnings as possible on all platforms and on both 32/64 bit architectures. I also simplified logging, added support for synchronous sample loading, and fixed a few bugs. 11 | * the SFZero Juce module and sample plugin have been separated and the Juce module is now available as a git submodule for easy inclusion in other repositories 12 | 13 | For more information, please see also this [blog article](http://www.mucoder.net/blog/2016/03/24/sfzero.html) 14 | 15 | Please note that, in order to build, SFZero requires [Juce](http://www.juce.com). 16 | 17 | Before building the sample plugin, it's necessary to 18 | 19 | * get the sample plugin source code from [https://github.com/altalogix/SFZero](https://github.com/altalogix/SFZero) 20 | * get the module source code from [https://github.com/altalogix/SFZeroModule](https://github.com/altalogix/SFZeroModule) 21 | * copy the SFZeroModule folder as a childfolder to your Juce modules folder. 22 | * load `plugin/SFZero.jucer` into your IntroJucer tool and save the project again. This should regenerate the project build definitions with the proper links to your Juce module location. 23 | 24 | If you just want to use the Juce module and not the sample plugin, it suffices to include the contents of [https://github.com/altalogix/SFZeroModule](https://github.com/altalogix/SFZeroModule) within a SFZero child folder of your Juce modules folder. 25 | 26 | 27 | -------------------------------------------------------------------------------- /SFZero.cpp: -------------------------------------------------------------------------------- 1 | // Your project must contain an AppConfig.h file with your project-specific settings in it, 2 | // and your header search path must make it accessible to the module's files. 3 | 4 | //#include "AppConfig.h" 5 | #include "SFZero.h" 6 | #include "sfzero/RIFF.cpp" 7 | #include "sfzero/SF2.cpp" 8 | #include "sfzero/SF2Generator.cpp" 9 | #include "sfzero/SF2Reader.cpp" 10 | #include "sfzero/SF2Sound.cpp" 11 | #include "sfzero/SFZDebug.cpp" 12 | #include "sfzero/SFZEG.cpp" 13 | #include "sfzero/SFZReader.cpp" 14 | #include "sfzero/SFZRegion.cpp" 15 | #include "sfzero/SFZSample.cpp" 16 | #include "sfzero/SFZSound.cpp" 17 | #include "sfzero/SFZSynth.cpp" 18 | #include "sfzero/SFZVoice.cpp" 19 | -------------------------------------------------------------------------------- /SFZero.h: -------------------------------------------------------------------------------- 1 | /* 2 | BEGIN_JUCE_MODULE_DECLARATION 3 | ID: SFZero 4 | vendor: altalogix 5 | version: 2.0.2 6 | name: SFZero sfz player 7 | description: SFZero .szf/.sf2 soundfont player; forked from https://github.com/stevefolta/SFZero and converted to Juce module by Leo Olivers 8 | website: https://github.com/altalogix/SFZero 9 | dependencies: juce_gui_basics, juce_audio_basics, juce_audio_processors 10 | license: MIT 11 | END_JUCE_MODULE_DECLARATION 12 | */ 13 | 14 | #ifndef INCLUDED_SFZERO_H 15 | #define INCLUDED_SFZERO_H 16 | 17 | #include "sfzero/RIFF.h" 18 | #include "sfzero/SF2.h" 19 | #include "sfzero/SF2Generator.h" 20 | #include "sfzero/SF2Reader.h" 21 | #include "sfzero/SF2Sound.h" 22 | #include "sfzero/SF2WinTypes.h" 23 | #include "sfzero/SFZCommon.h" 24 | #include "sfzero/SFZDebug.h" 25 | #include "sfzero/SFZEG.h" 26 | #include "sfzero/SFZReader.h" 27 | #include "sfzero/SFZRegion.h" 28 | #include "sfzero/SFZSample.h" 29 | #include "sfzero/SFZSound.h" 30 | #include "sfzero/SFZSynth.h" 31 | #include "sfzero/SFZVoice.h" 32 | 33 | 34 | #endif // INCLUDED_SFZERO_H 35 | 36 | -------------------------------------------------------------------------------- /juce_module_info: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SFZero", 3 | "name": "SFZero soundfont player", 4 | "version": "2.0.2", 5 | "description": "SFZero .szf/.sf2 soundfont player; forked from https://github.com/stevefolta/SFZero and converted to Juce module by Leo Olivers", 6 | "website": "https://github.com/altalogix/SFZero", 7 | "license": "MIT", 8 | 9 | "dependencies": [ 10 | { 11 | "id": "juce_gui_basics", 12 | "version": "4.1.0" 13 | }, 14 | { 15 | "id": "juce_audio_basics", 16 | "version": "4.1.0" 17 | }, 18 | { 19 | "id": "juce_audio_processors", 20 | "version": "4.1.0" 21 | } 22 | ], 23 | 24 | "include": "SFZero.h", 25 | 26 | "compile": [ { "file": "SFZero.cpp" } ], 27 | 28 | "browse": [ "sfzero/*" ] 29 | } 30 | -------------------------------------------------------------------------------- /sfzero/RIFF.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "RIFF.h" 8 | 9 | void sfzero::RIFFChunk::readFrom(juce::InputStream *file) 10 | { 11 | file->read(&id, sizeof(sfzero::fourcc)); 12 | size = static_cast(file->readInt()); 13 | start = file->getPosition(); 14 | 15 | if (FourCCEquals(id, "RIFF")) 16 | { 17 | type = RIFF; 18 | file->read(&id, sizeof(sfzero::fourcc)); 19 | start += sizeof(sfzero::fourcc); 20 | size -= sizeof(sfzero::fourcc); 21 | } 22 | else if (FourCCEquals(id, "LIST")) 23 | { 24 | type = LIST; 25 | file->read(&id, sizeof(sfzero::fourcc)); 26 | start += sizeof(sfzero::fourcc); 27 | size -= sizeof(sfzero::fourcc); 28 | } 29 | else 30 | { 31 | type = Custom; 32 | } 33 | } 34 | 35 | void sfzero::RIFFChunk::seek(juce::InputStream *file) { file->setPosition(start); } 36 | void sfzero::RIFFChunk::seekAfter(juce::InputStream *file) 37 | { 38 | juce::int64 next = start + size; 39 | 40 | if (next % 2 != 0) 41 | { 42 | next += 1; 43 | } 44 | file->setPosition(next); 45 | } 46 | 47 | juce::String sfzero::RIFFChunk::readString(juce::InputStream *file) 48 | { 49 | juce::MemoryBlock memoryBlock(size); 50 | file->read(memoryBlock.getData(), static_cast(memoryBlock.getSize())); 51 | return memoryBlock.toString(); 52 | } 53 | -------------------------------------------------------------------------------- /sfzero/RIFF.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef RIFF_H_INCLUDED 8 | #define RIFF_H_INCLUDED 9 | 10 | #include "SF2WinTypes.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | struct RIFFChunk 16 | { 17 | enum Type 18 | { 19 | RIFF, 20 | LIST, 21 | Custom 22 | }; 23 | 24 | fourcc id; 25 | dword size; 26 | Type type; 27 | juce::int64 start; 28 | 29 | void readFrom(juce::InputStream *file); 30 | void seek(juce::InputStream *file); 31 | void seekAfter(juce::InputStream *file); 32 | 33 | juce::int64 end() { return (start + size); } 34 | juce::String readString(juce::InputStream *file); 35 | }; 36 | } 37 | 38 | #endif // RIFF_H_INCLUDED 39 | -------------------------------------------------------------------------------- /sfzero/SF2.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SF2.h" 8 | #include "RIFF.h" 9 | 10 | #define readAbyte(name, file) name = (byte)file->readByte(); 11 | #define readAchar(name, file) name = file->readByte(); 12 | #define readAdword(name, file) name = (dword)file->readInt(); 13 | #define readAword(name, file) name = (word)file->readShort(); 14 | #define readAshort(name, file) name = file->readShort(); 15 | #define readAchar20(name, file) file->read(name, 20); 16 | #define readAgenAmountType(name, file) name.shortAmount = file->readShort(); 17 | 18 | #define SF2Field(type, name) readA##type(name, file) 19 | 20 | void sfzero::SF2::iver::readFrom(juce::InputStream *file) 21 | { 22 | #include "sf2-chunks/iver.h" 23 | } 24 | 25 | void sfzero::SF2::phdr::readFrom(juce::InputStream *file) 26 | { 27 | #include "sf2-chunks/phdr.h" 28 | } 29 | 30 | void sfzero::SF2::pbag::readFrom(juce::InputStream *file) 31 | { 32 | #include "sf2-chunks/pbag.h" 33 | } 34 | 35 | void sfzero::SF2::pmod::readFrom(juce::InputStream *file) 36 | { 37 | #include "sf2-chunks/pmod.h" 38 | } 39 | 40 | void sfzero::SF2::pgen::readFrom(juce::InputStream *file) 41 | { 42 | #include "sf2-chunks/pgen.h" 43 | } 44 | 45 | void sfzero::SF2::inst::readFrom(juce::InputStream *file) 46 | { 47 | #include "sf2-chunks/inst.h" 48 | } 49 | 50 | void sfzero::SF2::ibag::readFrom(juce::InputStream *file) 51 | { 52 | #include "sf2-chunks/ibag.h" 53 | } 54 | 55 | void sfzero::SF2::imod::readFrom(juce::InputStream *file) 56 | { 57 | #include "sf2-chunks/imod.h" 58 | } 59 | 60 | void sfzero::SF2::igen::readFrom(juce::InputStream *file) 61 | { 62 | #include "sf2-chunks/igen.h" 63 | } 64 | 65 | void sfzero::SF2::shdr::readFrom(juce::InputStream *file) 66 | { 67 | #include "sf2-chunks/shdr.h" 68 | } 69 | 70 | sfzero::SF2::Hydra::Hydra() 71 | : phdrItems(nullptr), pbagItems(nullptr), pmodItems(nullptr), pgenItems(nullptr), instItems(nullptr), ibagItems(nullptr), 72 | imodItems(nullptr), igenItems(nullptr), shdrItems(nullptr), phdrNumItems(0), pbagNumItems(0), pmodNumItems(0), pgenNumItems(0), 73 | instNumItems(0), ibagNumItems(0), imodNumItems(0), igenNumItems(0), shdrNumItems(0) 74 | { 75 | } 76 | sfzero::SF2::Hydra::~Hydra() 77 | { 78 | delete phdrItems; 79 | delete pbagItems; 80 | delete pmodItems; 81 | delete pgenItems; 82 | delete instItems; 83 | delete ibagItems; 84 | delete imodItems; 85 | delete igenItems; 86 | delete shdrItems; 87 | } 88 | 89 | void sfzero::SF2::Hydra::readFrom(juce::InputStream *file, juce::int64 pdtaChunkEnd) 90 | { 91 | int i, numItems; 92 | 93 | #define HandleChunk(chunkName) \ 94 | if (FourCCEquals(chunk.id, #chunkName)) \ 95 | { \ 96 | numItems = chunk.size / SF2::chunkName::sizeInFile; \ 97 | chunkName##NumItems = numItems; \ 98 | chunkName##Items = new SF2::chunkName[numItems]; \ 99 | for (i = 0; i < numItems; ++i) \ 100 | { \ 101 | chunkName##Items[i].readFrom(file); \ 102 | } \ 103 | } \ 104 | else 105 | 106 | while (file->getPosition() < pdtaChunkEnd) 107 | { 108 | sfzero::RIFFChunk chunk; 109 | chunk.readFrom(file); 110 | 111 | HandleChunk(phdr) HandleChunk(pbag) HandleChunk(pmod) HandleChunk(pgen) HandleChunk(inst) HandleChunk(ibag) HandleChunk(imod) 112 | HandleChunk(igen) HandleChunk(shdr) 113 | { 114 | } 115 | chunk.seekAfter(file); 116 | } 117 | } 118 | 119 | bool sfzero::SF2::Hydra::isComplete() 120 | { 121 | return phdrItems && pbagItems && pmodItems && pgenItems && instItems && ibagItems && imodItems && igenItems && shdrItems; 122 | } 123 | -------------------------------------------------------------------------------- /sfzero/SF2.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SF2_H_INCLUDED 8 | #define SF2_H_INCLUDED 9 | 10 | #include "SF2WinTypes.h" 11 | 12 | #define SF2Field(type, name) type name; 13 | 14 | namespace sfzero 15 | { 16 | 17 | namespace SF2 18 | { 19 | 20 | struct rangesType 21 | { 22 | byte lo, hi; 23 | }; 24 | 25 | union genAmountType { 26 | rangesType range; 27 | short shortAmount; 28 | word wordAmount; 29 | }; 30 | 31 | struct iver 32 | { 33 | #include "sf2-chunks/iver.h" 34 | void readFrom(juce::InputStream *file); 35 | }; 36 | 37 | struct phdr 38 | { 39 | #include "sf2-chunks/phdr.h" 40 | void readFrom(juce::InputStream *file); 41 | 42 | static const int sizeInFile = 38; 43 | }; 44 | 45 | struct pbag 46 | { 47 | #include "sf2-chunks/pbag.h" 48 | void readFrom(juce::InputStream *file); 49 | 50 | static const int sizeInFile = 4; 51 | }; 52 | 53 | struct pmod 54 | { 55 | #include "sf2-chunks/pmod.h" 56 | void readFrom(juce::InputStream *file); 57 | 58 | static const int sizeInFile = 10; 59 | }; 60 | 61 | struct pgen 62 | { 63 | #include "sf2-chunks/pgen.h" 64 | void readFrom(juce::InputStream *file); 65 | 66 | static const int sizeInFile = 4; 67 | }; 68 | 69 | struct inst 70 | { 71 | #include "sf2-chunks/inst.h" 72 | void readFrom(juce::InputStream *file); 73 | 74 | static const int sizeInFile = 22; 75 | }; 76 | 77 | struct ibag 78 | { 79 | #include "sf2-chunks/ibag.h" 80 | void readFrom(juce::InputStream *file); 81 | 82 | static const int sizeInFile = 4; 83 | }; 84 | 85 | struct imod 86 | { 87 | #include "sf2-chunks/imod.h" 88 | void readFrom(juce::InputStream *file); 89 | 90 | static const int sizeInFile = 10; 91 | }; 92 | 93 | struct igen 94 | { 95 | #include "sf2-chunks/igen.h" 96 | void readFrom(juce::InputStream *file); 97 | 98 | static const int sizeInFile = 4; 99 | }; 100 | 101 | struct shdr 102 | { 103 | #include "sf2-chunks/shdr.h" 104 | void readFrom(juce::InputStream *file); 105 | 106 | static const int sizeInFile = 46; 107 | }; 108 | 109 | struct Hydra 110 | { 111 | phdr *phdrItems; 112 | pbag *pbagItems; 113 | pmod *pmodItems; 114 | pgen *pgenItems; 115 | inst *instItems; 116 | ibag *ibagItems; 117 | imod *imodItems; 118 | igen *igenItems; 119 | shdr *shdrItems; 120 | 121 | int phdrNumItems, pbagNumItems, pmodNumItems, pgenNumItems; 122 | int instNumItems, ibagNumItems, imodNumItems, igenNumItems; 123 | int shdrNumItems; 124 | 125 | Hydra(); 126 | ~Hydra(); 127 | 128 | void readFrom(juce::InputStream *file, juce::int64 pdtaChunkEnd); 129 | bool isComplete(); 130 | }; 131 | } 132 | } 133 | 134 | #undef SF2Field 135 | 136 | #endif // SF2_H_INCLUDED 137 | -------------------------------------------------------------------------------- /sfzero/SF2Generator.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SF2Generator.h" 8 | 9 | #define SF2GeneratorValue(name, type) \ 10 | { \ 11 | #name, sfzero::SF2Generator::type \ 12 | } 13 | 14 | static const sfzero::SF2Generator generators[] = { 15 | 16 | #include "sf2-chunks/generators.h" 17 | 18 | }; 19 | 20 | #undef SF2GeneratorValue 21 | 22 | const sfzero::SF2Generator *sfzero::GeneratorFor(int index) 23 | { 24 | static const int numGenerators = sizeof(generators) / sizeof(generators[0]); 25 | 26 | if (index >= numGenerators) 27 | { 28 | return nullptr; 29 | } 30 | return &generators[index]; 31 | } 32 | -------------------------------------------------------------------------------- /sfzero/SF2Generator.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SF2GENERATOR_H_INCLUDED 8 | #define SF2GENERATOR_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | #define SF2GeneratorValue(name, type) name 13 | 14 | namespace sfzero 15 | { 16 | 17 | struct SF2Generator 18 | { 19 | enum Type 20 | { 21 | Word, 22 | Short, 23 | Range 24 | }; 25 | 26 | const char *name; 27 | Type type; 28 | 29 | enum 30 | { 31 | #include "sf2-chunks/generators.h" 32 | }; 33 | }; 34 | 35 | const SF2Generator *GeneratorFor(int index); 36 | 37 | #undef SF2GeneratorValue 38 | } 39 | 40 | #endif // SF2GENERATOR_H_INCLUDED 41 | -------------------------------------------------------------------------------- /sfzero/SF2Reader.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SF2Reader.h" 8 | #include "RIFF.h" 9 | #include "SF2.h" 10 | #include "SF2Generator.h" 11 | #include "SF2Sound.h" 12 | 13 | sfzero::SF2Reader::SF2Reader(sfzero::SF2Sound *soundIn, const juce::File &fileIn) : sound_(soundIn) 14 | { 15 | file_ = fileIn.createInputStream(); 16 | } 17 | 18 | sfzero::SF2Reader::~SF2Reader() { delete file_; } 19 | 20 | void sfzero::SF2Reader::read() 21 | { 22 | if (file_ == nullptr) 23 | { 24 | sound_->addError("Couldn't open file."); 25 | return; 26 | } 27 | 28 | // Read the hydra. 29 | sfzero::SF2::Hydra hydra; 30 | file_->setPosition(0); 31 | sfzero::RIFFChunk riffChunk; 32 | riffChunk.readFrom(file_); 33 | while (file_->getPosition() < riffChunk.end()) 34 | { 35 | sfzero::RIFFChunk chunk; 36 | chunk.readFrom(file_); 37 | if (FourCCEquals(chunk.id, "pdta")) 38 | { 39 | hydra.readFrom(file_, chunk.end()); 40 | break; 41 | } 42 | chunk.seekAfter(file_); 43 | } 44 | if (!hydra.isComplete()) 45 | { 46 | sound_->addError("Invalid SF2 file (missing or incomplete hydra)."); 47 | return; 48 | } 49 | 50 | // Read each preset. 51 | for (int whichPreset = 0; whichPreset < hydra.phdrNumItems - 1; ++whichPreset) 52 | { 53 | sfzero::SF2::phdr *phdr = &hydra.phdrItems[whichPreset]; 54 | sfzero::SF2Sound::Preset *preset = new sfzero::SF2Sound::Preset(phdr->presetName, phdr->bank, phdr->preset); 55 | sound_->addPreset(preset); 56 | 57 | // Zones. 58 | //*** TODO: Handle global zone (modulators only). 59 | int zoneEnd = phdr[1].presetBagNdx; 60 | for (int whichZone = phdr->presetBagNdx; whichZone < zoneEnd; ++whichZone) 61 | { 62 | sfzero::SF2::pbag *pbag = &hydra.pbagItems[whichZone]; 63 | sfzero::Region presetRegion; 64 | presetRegion.clearForRelativeSF2(); 65 | 66 | // Generators. 67 | int genEnd = pbag[1].genNdx; 68 | for (int whichGen = pbag->genNdx; whichGen < genEnd; ++whichGen) 69 | { 70 | sfzero::SF2::pgen *pgen = &hydra.pgenItems[whichGen]; 71 | 72 | // Instrument. 73 | if (pgen->genOper == sfzero::SF2Generator::instrument) 74 | { 75 | sfzero::word whichInst = pgen->genAmount.wordAmount; 76 | if (whichInst < hydra.instNumItems) 77 | { 78 | sfzero::Region instRegion; 79 | instRegion.clearForSF2(); 80 | // Preset generators are supposed to be "relative" modifications of 81 | // the instrument settings, but that makes no sense for ranges. 82 | // For those, we'll have the instrument's generator take 83 | // precedence, though that may not be correct. 84 | instRegion.lokey = presetRegion.lokey; 85 | instRegion.hikey = presetRegion.hikey; 86 | instRegion.lovel = presetRegion.lovel; 87 | instRegion.hivel = presetRegion.hivel; 88 | 89 | sfzero::SF2::inst *inst = &hydra.instItems[whichInst]; 90 | int firstZone = inst->instBagNdx; 91 | int zoneEnd2 = inst[1].instBagNdx; 92 | for (int whichZone2 = firstZone; whichZone2 < zoneEnd2; ++whichZone2) 93 | { 94 | sfzero::SF2::ibag *ibag = &hydra.ibagItems[whichZone2]; 95 | 96 | // Generators. 97 | sfzero::Region zoneRegion = instRegion; 98 | bool hadSampleID = false; 99 | int genEnd2 = ibag[1].instGenNdx; 100 | for (int whichGen2 = ibag->instGenNdx; whichGen2 < genEnd2; ++whichGen2) 101 | { 102 | sfzero::SF2::igen *igen = &hydra.igenItems[whichGen2]; 103 | if (igen->genOper == sfzero::SF2Generator::sampleID) 104 | { 105 | int whichSample = igen->genAmount.wordAmount; 106 | sfzero::SF2::shdr *shdr = &hydra.shdrItems[whichSample]; 107 | zoneRegion.addForSF2(&presetRegion); 108 | zoneRegion.sf2ToSFZ(); 109 | zoneRegion.offset += shdr->start; 110 | zoneRegion.end += shdr->end; 111 | zoneRegion.loop_start += shdr->startLoop; 112 | zoneRegion.loop_end += shdr->endLoop; 113 | if (shdr->endLoop > 0) 114 | { 115 | zoneRegion.loop_end -= 1; 116 | } 117 | if (zoneRegion.pitch_keycenter == -1) 118 | { 119 | zoneRegion.pitch_keycenter = shdr->originalPitch; 120 | } 121 | zoneRegion.tune += shdr->pitchCorrection; 122 | 123 | // Pin initialAttenuation to max +6dB. 124 | if (zoneRegion.volume > 6.0) 125 | { 126 | zoneRegion.volume = 6.0; 127 | sound_->addUnsupportedOpcode("extreme gain in initialAttenuation"); 128 | } 129 | 130 | sfzero::Region *newRegion = new sfzero::Region(); 131 | *newRegion = zoneRegion; 132 | newRegion->sample = sound_->sampleFor(shdr->sampleRate); 133 | preset->addRegion(newRegion); 134 | hadSampleID = true; 135 | } 136 | else 137 | { 138 | addGeneratorToRegion(igen->genOper, &igen->genAmount, &zoneRegion); 139 | } 140 | } 141 | 142 | // Handle instrument's global zone. 143 | if ((whichZone2 == firstZone) && !hadSampleID) 144 | { 145 | instRegion = zoneRegion; 146 | } 147 | 148 | // Modulators. 149 | int modEnd = ibag[1].instModNdx; 150 | int whichMod = ibag->instModNdx; 151 | if (whichMod < modEnd) 152 | { 153 | sound_->addUnsupportedOpcode("any modulator"); 154 | } 155 | } 156 | } 157 | else 158 | { 159 | sound_->addError("Instrument out of range."); 160 | } 161 | } 162 | // Other generators. 163 | else 164 | { 165 | addGeneratorToRegion(pgen->genOper, &pgen->genAmount, &presetRegion); 166 | } 167 | } 168 | 169 | // Modulators. 170 | int modEnd = pbag[1].modNdx; 171 | int whichMod = pbag->modNdx; 172 | if (whichMod < modEnd) 173 | { 174 | sound_->addUnsupportedOpcode("any modulator"); 175 | } 176 | } 177 | } 178 | } 179 | 180 | juce::AudioSampleBuffer *sfzero::SF2Reader::readSamples(double *progressVar, juce::Thread *thread) 181 | { 182 | static const int bufferSize = 32768; 183 | 184 | if (file_ == nullptr) 185 | { 186 | sound_->addError("Couldn't open file."); 187 | return nullptr; 188 | } 189 | 190 | // Find the "sdta" chunk. 191 | file_->setPosition(0); 192 | sfzero::RIFFChunk riffChunk; 193 | riffChunk.readFrom(file_); 194 | bool found = false; 195 | sfzero::RIFFChunk chunk; 196 | while (file_->getPosition() < riffChunk.end()) 197 | { 198 | chunk.readFrom(file_); 199 | if (FourCCEquals(chunk.id, "sdta")) 200 | { 201 | found = true; 202 | break; 203 | } 204 | chunk.seekAfter(file_); 205 | } 206 | juce::int64 sdtaEnd = chunk.end(); 207 | found = false; 208 | while (file_->getPosition() < sdtaEnd) 209 | { 210 | chunk.readFrom(file_); 211 | if (FourCCEquals(chunk.id, "smpl")) 212 | { 213 | found = true; 214 | break; 215 | } 216 | chunk.seekAfter(file_); 217 | } 218 | if (!found) 219 | { 220 | sound_->addError("SF2 is missing its \"smpl\" chunk."); 221 | return nullptr; 222 | } 223 | 224 | // Allocate the AudioSampleBuffer. 225 | int numSamples = chunk.size / sizeof(short); 226 | juce::AudioSampleBuffer *sampleBuffer = new juce::AudioSampleBuffer(1, numSamples); 227 | 228 | // Read and convert. 229 | short *buffer = new short[bufferSize]; 230 | int samplesLeft = numSamples; 231 | float *out = sampleBuffer->getWritePointer(0); 232 | while (samplesLeft > 0) 233 | { 234 | // Read the buffer. 235 | int samplesToRead = bufferSize; 236 | if (samplesToRead > samplesLeft) 237 | { 238 | samplesToRead = samplesLeft; 239 | } 240 | file_->read(buffer, samplesToRead * sizeof(short)); 241 | 242 | // Convert from signed 16-bit to float. 243 | int samplesToConvert = samplesToRead; 244 | short *in = buffer; 245 | for (; samplesToConvert > 0; --samplesToConvert) 246 | { 247 | // If we ever need to compile for big-endian platforms, we'll need to 248 | // byte-swap here. 249 | *out++ = *in++ / 32767.0f; 250 | } 251 | 252 | samplesLeft -= samplesToRead; 253 | 254 | if (progressVar) 255 | { 256 | *progressVar = static_cast(numSamples - samplesLeft) / numSamples; 257 | } 258 | if (thread && thread->threadShouldExit()) 259 | { 260 | delete[] buffer; 261 | delete sampleBuffer; 262 | return nullptr; 263 | } 264 | } 265 | delete[] buffer; 266 | 267 | if (progressVar) 268 | { 269 | *progressVar = 1.0; 270 | } 271 | 272 | return sampleBuffer; 273 | } 274 | 275 | void sfzero::SF2Reader::addGeneratorToRegion(sfzero::word genOper, sfzero::SF2::genAmountType *amount, sfzero::Region *region) 276 | { 277 | switch (genOper) 278 | { 279 | case sfzero::SF2Generator::startAddrsOffset: 280 | region->offset += amount->shortAmount; 281 | break; 282 | 283 | case sfzero::SF2Generator::endAddrsOffset: 284 | region->end += amount->shortAmount; 285 | break; 286 | 287 | case sfzero::SF2Generator::startloopAddrsOffset: 288 | region->loop_start += amount->shortAmount; 289 | break; 290 | 291 | case sfzero::SF2Generator::endloopAddrsOffset: 292 | region->loop_end += amount->shortAmount; 293 | break; 294 | 295 | case sfzero::SF2Generator::startAddrsCoarseOffset: 296 | region->offset += amount->shortAmount * 32768; 297 | break; 298 | 299 | case sfzero::SF2Generator::endAddrsCoarseOffset: 300 | region->end += amount->shortAmount * 32768; 301 | break; 302 | 303 | case sfzero::SF2Generator::pan: 304 | region->pan = amount->shortAmount * (2.0f / 10.0f); 305 | break; 306 | 307 | case sfzero::SF2Generator::delayVolEnv: 308 | region->ampeg.delay = amount->shortAmount; 309 | break; 310 | 311 | case sfzero::SF2Generator::attackVolEnv: 312 | region->ampeg.attack = amount->shortAmount; 313 | break; 314 | 315 | case sfzero::SF2Generator::holdVolEnv: 316 | region->ampeg.hold = amount->shortAmount; 317 | break; 318 | 319 | case sfzero::SF2Generator::decayVolEnv: 320 | region->ampeg.decay = amount->shortAmount; 321 | break; 322 | 323 | case sfzero::SF2Generator::sustainVolEnv: 324 | region->ampeg.sustain = amount->shortAmount; 325 | break; 326 | 327 | case sfzero::SF2Generator::releaseVolEnv: 328 | region->ampeg.release = amount->shortAmount; 329 | break; 330 | 331 | case sfzero::SF2Generator::keyRange: 332 | region->lokey = amount->range.lo; 333 | region->hikey = amount->range.hi; 334 | break; 335 | 336 | case sfzero::SF2Generator::velRange: 337 | region->lovel = amount->range.lo; 338 | region->hivel = amount->range.hi; 339 | break; 340 | 341 | case sfzero::SF2Generator::startloopAddrsCoarseOffset: 342 | region->loop_start += amount->shortAmount * 32768; 343 | break; 344 | 345 | case sfzero::SF2Generator::initialAttenuation: 346 | // The spec says "initialAttenuation" is in centibels. But everyone 347 | // seems to treat it as millibels. 348 | region->volume += -amount->shortAmount / 100.0f; 349 | break; 350 | 351 | case sfzero::SF2Generator::endloopAddrsCoarseOffset: 352 | region->loop_end += amount->shortAmount * 32768; 353 | break; 354 | 355 | case sfzero::SF2Generator::coarseTune: 356 | region->transpose += amount->shortAmount; 357 | break; 358 | 359 | case sfzero::SF2Generator::fineTune: 360 | region->tune += amount->shortAmount; 361 | break; 362 | 363 | case sfzero::SF2Generator::sampleModes: 364 | { 365 | sfzero::Region::LoopMode loopModes[] = {sfzero::Region::no_loop, sfzero::Region::loop_continuous, sfzero::Region::no_loop, 366 | sfzero::Region::loop_sustain}; 367 | region->loop_mode = loopModes[amount->wordAmount & 0x03]; 368 | } 369 | break; 370 | 371 | case sfzero::SF2Generator::scaleTuning: 372 | region->pitch_keytrack = amount->shortAmount; 373 | break; 374 | 375 | case sfzero::SF2Generator::exclusiveClass: 376 | region->off_by = amount->wordAmount; 377 | region->group = static_cast(region->off_by); 378 | break; 379 | 380 | case sfzero::SF2Generator::overridingRootKey: 381 | region->pitch_keycenter = amount->shortAmount; 382 | break; 383 | 384 | case sfzero::SF2Generator::endOper: 385 | // Ignore. 386 | break; 387 | 388 | case sfzero::SF2Generator::modLfoToPitch: 389 | case sfzero::SF2Generator::vibLfoToPitch: 390 | case sfzero::SF2Generator::modEnvToPitch: 391 | case sfzero::SF2Generator::initialFilterFc: 392 | case sfzero::SF2Generator::initialFilterQ: 393 | case sfzero::SF2Generator::modLfoToFilterFc: 394 | case sfzero::SF2Generator::modEnvToFilterFc: 395 | case sfzero::SF2Generator::modLfoToVolume: 396 | case sfzero::SF2Generator::unused1: 397 | case sfzero::SF2Generator::chorusEffectsSend: 398 | case sfzero::SF2Generator::reverbEffectsSend: 399 | case sfzero::SF2Generator::unused2: 400 | case sfzero::SF2Generator::unused3: 401 | case sfzero::SF2Generator::unused4: 402 | case sfzero::SF2Generator::delayModLFO: 403 | case sfzero::SF2Generator::freqModLFO: 404 | case sfzero::SF2Generator::delayVibLFO: 405 | case sfzero::SF2Generator::freqVibLFO: 406 | case sfzero::SF2Generator::delayModEnv: 407 | case sfzero::SF2Generator::attackModEnv: 408 | case sfzero::SF2Generator::holdModEnv: 409 | case sfzero::SF2Generator::decayModEnv: 410 | case sfzero::SF2Generator::sustainModEnv: 411 | case sfzero::SF2Generator::releaseModEnv: 412 | case sfzero::SF2Generator::keynumToModEnvHold: 413 | case sfzero::SF2Generator::keynumToModEnvDecay: 414 | case sfzero::SF2Generator::keynumToVolEnvHold: 415 | case sfzero::SF2Generator::keynumToVolEnvDecay: 416 | case sfzero::SF2Generator::instrument: 417 | // Only allowed in certain places, where we already special-case it. 418 | case sfzero::SF2Generator::reserved1: 419 | case sfzero::SF2Generator::keynum: 420 | case sfzero::SF2Generator::velocity: 421 | case sfzero::SF2Generator::reserved2: 422 | case sfzero::SF2Generator::sampleID: 423 | // Only allowed in certain places, where we already special-case it. 424 | case sfzero::SF2Generator::reserved3: 425 | case sfzero::SF2Generator::unused5: 426 | { 427 | const sfzero::SF2Generator *generator = sfzero::GeneratorFor(static_cast(genOper)); 428 | sound_->addUnsupportedOpcode(generator->name); 429 | } 430 | break; 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /sfzero/SF2Reader.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SF2READER_H_INCLUDED 8 | #define SF2READER_H_INCLUDED 9 | 10 | #include "SF2.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | class SF2Sound; 16 | class Sample; 17 | struct Region; 18 | 19 | class SF2Reader 20 | { 21 | public: 22 | SF2Reader(SF2Sound *sound, const juce::File &file); 23 | virtual ~SF2Reader(); 24 | 25 | void read(); 26 | juce::AudioSampleBuffer *readSamples(double *progressVar = nullptr, juce::Thread *thread = nullptr); 27 | 28 | private: 29 | SF2Sound *sound_; 30 | juce::FileInputStream *file_; 31 | 32 | void addGeneratorToRegion(word genOper, SF2::genAmountType *amount, Region *region); 33 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SF2Reader) 34 | }; 35 | } 36 | 37 | #endif // SF2READER_H_INCLUDED 38 | -------------------------------------------------------------------------------- /sfzero/SF2Sound.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SF2Sound.h" 8 | #include "SF2Reader.h" 9 | #include "SFZSample.h" 10 | 11 | sfzero::SF2Sound::SF2Sound(const juce::File &file) : sfzero::Sound(file), selectedPreset_(0) {} 12 | 13 | sfzero::SF2Sound::~SF2Sound() 14 | { 15 | // "presets" owns the regions, so clear them out of "regions" so ~SFZSound() 16 | // doesn't try to delete them. 17 | getRegions().clear(); 18 | 19 | // The samples all share a single buffer, so make sure they don't all delete 20 | // it. 21 | juce::AudioSampleBuffer *buffer = nullptr; 22 | for (juce::HashMap::Iterator i(samplesByRate_); i.next();) 23 | { 24 | buffer = i.getValue()->detachBuffer(); 25 | } 26 | delete buffer; 27 | } 28 | 29 | class PresetComparator 30 | { 31 | public: 32 | static int compareElements(const sfzero::SF2Sound::Preset *first, const sfzero::SF2Sound::Preset *second) 33 | { 34 | int cmp = first->bank - second->bank; 35 | 36 | if (cmp != 0) 37 | { 38 | return cmp; 39 | } 40 | return first->preset - second->preset; 41 | } 42 | }; 43 | 44 | void sfzero::SF2Sound::loadRegions() 45 | { 46 | sfzero::SF2Reader reader(this, getFile()); 47 | 48 | reader.read(); 49 | 50 | // Sort the presets. 51 | PresetComparator comparator; 52 | presets_.sort(comparator); 53 | 54 | useSubsound(0); 55 | } 56 | 57 | void sfzero::SF2Sound::loadSamples(juce::AudioFormatManager * /*formatManager*/, double *progressVar, juce::Thread *thread) 58 | { 59 | sfzero::SF2Reader reader(this, getFile()); 60 | juce::AudioSampleBuffer *buffer = reader.readSamples(progressVar, thread); 61 | 62 | if (buffer) 63 | { 64 | // All the SFZSamples will share the buffer. 65 | for (juce::HashMap::Iterator i(samplesByRate_); i.next();) 66 | { 67 | i.getValue()->setBuffer(buffer); 68 | } 69 | } 70 | 71 | if (progressVar) 72 | { 73 | *progressVar = 1.0; 74 | } 75 | } 76 | 77 | void sfzero::SF2Sound::addPreset(sfzero::SF2Sound::Preset *preset) { presets_.add(preset); } 78 | 79 | int sfzero::SF2Sound::numSubsounds() { return presets_.size(); } 80 | 81 | juce::String sfzero::SF2Sound::subsoundName(int whichSubsound) 82 | { 83 | Preset *preset = presets_[whichSubsound]; 84 | juce::String result; 85 | 86 | if (preset->bank != 0) 87 | { 88 | result += preset->bank; 89 | result += "/"; 90 | } 91 | result += preset->preset; 92 | result += ": "; 93 | result += preset->name; 94 | return result; 95 | } 96 | 97 | void sfzero::SF2Sound::useSubsound(int whichSubsound) 98 | { 99 | selectedPreset_ = whichSubsound; 100 | getRegions().clear(); 101 | getRegions().addArray(presets_[whichSubsound]->regions); 102 | } 103 | 104 | int sfzero::SF2Sound::selectedSubsound() { return selectedPreset_; } 105 | 106 | sfzero::Sample *sfzero::SF2Sound::sampleFor(double sampleRate) 107 | { 108 | sfzero::Sample *sample = samplesByRate_[static_cast(sampleRate)]; 109 | 110 | if (sample == nullptr) 111 | { 112 | sample = new sfzero::Sample(sampleRate); 113 | samplesByRate_.set(static_cast(sampleRate), sample); 114 | } 115 | return sample; 116 | } 117 | 118 | void sfzero::SF2Sound::setSamplesBuffer(juce::AudioSampleBuffer *buffer) 119 | { 120 | for (juce::HashMap::Iterator i(samplesByRate_); i.next();) 121 | { 122 | i.getValue()->setBuffer(buffer); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /sfzero/SF2Sound.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SF2SOUND_H_INCLUDED 8 | #define SF2SOUND_H_INCLUDED 9 | 10 | #include "SFZSound.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | class SF2Sound : public Sound 16 | { 17 | public: 18 | explicit SF2Sound(const juce::File &file); 19 | virtual ~SF2Sound(); 20 | 21 | void loadRegions() override; 22 | void loadSamples(juce::AudioFormatManager *formatManager, double *progressVar = nullptr, juce::Thread *thread = nullptr) override; 23 | 24 | struct Preset 25 | { 26 | juce::String name; 27 | int bank; 28 | int preset; 29 | juce::OwnedArray regions; 30 | 31 | Preset(juce::String nameIn, int bankIn, int presetIn) : name(nameIn), bank(bankIn), preset(presetIn) {} 32 | ~Preset() {} 33 | void addRegion(Region *region) { regions.add(region); } 34 | }; 35 | void addPreset(Preset *preset); 36 | 37 | int numSubsounds() override; 38 | juce::String subsoundName(int whichSubsound) override; 39 | void useSubsound(int whichSubsound) override; 40 | int selectedSubsound() override; 41 | 42 | Sample *sampleFor(double sampleRate); 43 | void setSamplesBuffer(juce::AudioSampleBuffer *buffer); 44 | 45 | private: 46 | juce::OwnedArray presets_; 47 | juce::HashMap samplesByRate_; 48 | int selectedPreset_; 49 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SF2Sound) 50 | }; 51 | } 52 | 53 | #endif // SF2SOUND_H_INCLUDED 54 | -------------------------------------------------------------------------------- /sfzero/SF2WinTypes.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SF2WINTYPES_H_INCLUDED 8 | #define SF2WINTYPES_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | #define FourCCEquals(value1, value2) \ 13 | (value1[0] == value2[0] && value1[1] == value2[1] && value1[2] == value2[2] && value1[3] == value2[3]) 14 | // Useful in printfs: 15 | #define FourCCArgs(value) (value)[0], (value)[1], (value)[2], (value)[3] 16 | 17 | namespace sfzero 18 | { 19 | typedef char fourcc[4]; 20 | typedef unsigned char byte; 21 | typedef unsigned long dword; 22 | typedef unsigned short word; 23 | 24 | // Special types for SF2 fields. 25 | typedef char char20[20]; 26 | } 27 | 28 | #endif // SF2WINTYPES_H_INCLUDED 29 | -------------------------------------------------------------------------------- /sfzero/SFZCommon.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZCOMMON_H_INCLUDED 8 | #define SFZCOMMON_H_INCLUDED 9 | 10 | #include "AppConfig.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #endif // SFZCOMMON_H_INCLUDED 18 | -------------------------------------------------------------------------------- /sfzero/SFZDebug.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZDebug.h" 8 | #include 9 | 10 | #ifdef JUCE_DEBUG 11 | 12 | void sfzero::dbgprintf(const char *msg, ...) 13 | { 14 | va_list args; 15 | 16 | va_start(args, msg); 17 | 18 | char output[256]; 19 | vsnprintf(output, 256, msg, args); 20 | 21 | va_end(args); 22 | } 23 | 24 | #endif // JUCE_DEBUG 25 | -------------------------------------------------------------------------------- /sfzero/SFZDebug.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZDEBUG_H_INCLUDED 8 | #define SFZDEBUG_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | #ifdef JUCE_DEBUG 13 | 14 | namespace sfzero 15 | { 16 | void dbgprintf(const char *msg, ...); 17 | } 18 | 19 | #endif 20 | 21 | #endif // SFZDEBUG_H_INCLUDED 22 | -------------------------------------------------------------------------------- /sfzero/SFZEG.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZEG.h" 8 | 9 | static const float fastReleaseTime = 0.01f; 10 | 11 | sfzero::EG::EG() 12 | : segment_(), sampleRate_(0), exponentialDecay_(false), level_(0), slope_(0), samplesUntilNextSegment_(0), segmentIsExponential_(false) 13 | { 14 | } 15 | 16 | void sfzero::EG::setExponentialDecay(bool newExponentialDecay) { exponentialDecay_ = newExponentialDecay; } 17 | 18 | void sfzero::EG::startNote(const EGParameters *newParameters, float floatVelocity, double newSampleRate, 19 | const EGParameters *velMod) 20 | { 21 | parameters_ = *newParameters; 22 | if (velMod) 23 | { 24 | parameters_.delay += floatVelocity * velMod->delay; 25 | parameters_.attack += floatVelocity * velMod->attack; 26 | parameters_.hold += floatVelocity * velMod->hold; 27 | parameters_.decay += floatVelocity * velMod->decay; 28 | parameters_.sustain += floatVelocity * velMod->sustain; 29 | if (parameters_.sustain < 0.0) 30 | { 31 | parameters_.sustain = 0.0; 32 | } 33 | else if (parameters_.sustain > 100.0) 34 | { 35 | parameters_.sustain = 100.0; 36 | } 37 | parameters_.release += floatVelocity * velMod->release; 38 | } 39 | sampleRate_ = newSampleRate; 40 | 41 | startDelay(); 42 | } 43 | 44 | void sfzero::EG::nextSegment() 45 | { 46 | switch (segment_) 47 | { 48 | case Delay: 49 | startAttack(); 50 | break; 51 | 52 | case Attack: 53 | startHold(); 54 | break; 55 | 56 | case Hold: 57 | startDecay(); 58 | break; 59 | 60 | case Decay: 61 | startSustain(); 62 | break; 63 | 64 | case Sustain: 65 | // Shouldn't be called. 66 | break; 67 | 68 | case Release: 69 | default: 70 | segment_ = Done; 71 | break; 72 | } 73 | } 74 | 75 | void sfzero::EG::noteOff() { startRelease(); } 76 | 77 | void sfzero::EG::fastRelease() 78 | { 79 | segment_ = Release; 80 | samplesUntilNextSegment_ = static_cast(fastReleaseTime * sampleRate_); 81 | slope_ = -level_ / samplesUntilNextSegment_; 82 | segmentIsExponential_ = false; 83 | } 84 | 85 | void sfzero::EG::startDelay() 86 | { 87 | if (parameters_.delay <= 0) 88 | { 89 | startAttack(); 90 | } 91 | else 92 | { 93 | segment_ = Delay; 94 | level_ = 0.0; 95 | slope_ = 0.0; 96 | samplesUntilNextSegment_ = static_cast(parameters_.delay * sampleRate_); 97 | segmentIsExponential_ = false; 98 | } 99 | } 100 | 101 | void sfzero::EG::startAttack() 102 | { 103 | if (parameters_.attack <= 0) 104 | { 105 | startHold(); 106 | } 107 | else 108 | { 109 | segment_ = Attack; 110 | level_ = parameters_.start / 100.0f; 111 | samplesUntilNextSegment_ = static_cast(parameters_.attack * sampleRate_); 112 | slope_ = 1.0f / samplesUntilNextSegment_; 113 | segmentIsExponential_ = false; 114 | } 115 | } 116 | 117 | void sfzero::EG::startHold() 118 | { 119 | if (parameters_.hold <= 0) 120 | { 121 | level_ = 1.0; 122 | startDecay(); 123 | } 124 | else 125 | { 126 | segment_ = Hold; 127 | samplesUntilNextSegment_ = static_cast(parameters_.hold * sampleRate_); 128 | level_ = 1.0; 129 | slope_ = 0.0; 130 | segmentIsExponential_ = false; 131 | } 132 | } 133 | 134 | void sfzero::EG::startDecay() 135 | { 136 | if (parameters_.decay <= 0) 137 | { 138 | startSustain(); 139 | } 140 | else 141 | { 142 | segment_ = Decay; 143 | samplesUntilNextSegment_ = static_cast(parameters_.decay * sampleRate_); 144 | level_ = 1.0; 145 | if (exponentialDecay_) 146 | { 147 | // I don't truly understand this; just following what LinuxSampler does. 148 | float mysterySlope = -9.226f / samplesUntilNextSegment_; 149 | slope_ = exp(mysterySlope); 150 | segmentIsExponential_ = true; 151 | if (parameters_.sustain > 0.0) 152 | { 153 | // Again, this is following LinuxSampler's example, which is similar to 154 | // SF2-style decay, where "decay" specifies the time it would take to 155 | // get to zero, not to the sustain level. The SFZ spec is not that 156 | // specific about what "decay" means, so perhaps it's really supposed 157 | // to specify the time to reach the sustain level. 158 | samplesUntilNextSegment_ = static_cast(log((parameters_.sustain / 100.0) / level_) / mysterySlope); 159 | if (samplesUntilNextSegment_ <= 0) 160 | { 161 | startSustain(); 162 | } 163 | } 164 | } 165 | else 166 | { 167 | slope_ = (parameters_.sustain / 100.0f - 1.0f) / samplesUntilNextSegment_; 168 | segmentIsExponential_ = false; 169 | } 170 | } 171 | } 172 | 173 | void sfzero::EG::startSustain() 174 | { 175 | if (parameters_.sustain <= 0) 176 | { 177 | startRelease(); 178 | } 179 | else 180 | { 181 | segment_ = Sustain; 182 | level_ = parameters_.sustain / 100.0f; 183 | slope_ = 0.0; 184 | samplesUntilNextSegment_ = 0x7FFFFFFF; 185 | segmentIsExponential_ = false; 186 | } 187 | } 188 | 189 | void sfzero::EG::startRelease() 190 | { 191 | float release = parameters_.release; 192 | 193 | if (release <= 0) 194 | { 195 | // Enforce a short release, to prevent clicks. 196 | release = fastReleaseTime; 197 | } 198 | 199 | segment_ = Release; 200 | samplesUntilNextSegment_ = static_cast(release * sampleRate_); 201 | if (exponentialDecay_) 202 | { 203 | // I don't truly understand this; just following what LinuxSampler does. 204 | float mysterySlope = -9.226f / samplesUntilNextSegment_; 205 | slope_ = exp(mysterySlope); 206 | segmentIsExponential_ = true; 207 | } 208 | else 209 | { 210 | slope_ = -level_ / samplesUntilNextSegment_; 211 | segmentIsExponential_ = false; 212 | } 213 | } 214 | 215 | const float sfzero::EG::BottomLevel = 0.001f; 216 | -------------------------------------------------------------------------------- /sfzero/SFZEG.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZEG_H_INCLUDED 8 | #define SFZEG_H_INCLUDED 9 | 10 | #include "SFZRegion.h" 11 | 12 | namespace sfzero 13 | { 14 | class EG 15 | { 16 | public: 17 | EG(); 18 | virtual ~EG() {} 19 | 20 | void setExponentialDecay(bool newExponentialDecay); 21 | void startNote(const EGParameters *parameters, float floatVelocity, double sampleRate, const EGParameters *velMod = nullptr); 22 | void nextSegment(); 23 | void noteOff(); 24 | void fastRelease(); 25 | bool isDone() { return (segment_ == Done); } 26 | bool isReleasing() { return (segment_ == Release); } 27 | int segmentIndex() { return static_cast(segment_); } 28 | float getLevel() const { return level_; } 29 | void setLevel(float v) { level_ = v; } 30 | float getSlope() const { return slope_; } 31 | void setSlope(float v) { slope_ = v; } 32 | int getSamplesUntilNextSegment() const { return samplesUntilNextSegment_; } 33 | void setSamplesUntilNextSegment(int v) { samplesUntilNextSegment_ = v; } 34 | bool getSegmentIsExponential() const { return segmentIsExponential_; } 35 | void setSegmentIsExponential(bool v) { segmentIsExponential_ = v; } 36 | 37 | private: 38 | enum Segment 39 | { 40 | Delay, 41 | Attack, 42 | Hold, 43 | Decay, 44 | Sustain, 45 | Release, 46 | Done 47 | }; 48 | 49 | void startDelay(); 50 | void startAttack(); 51 | void startHold(); 52 | void startDecay(); 53 | void startSustain(); 54 | void startRelease(); 55 | 56 | Segment segment_; 57 | EGParameters parameters_; 58 | double sampleRate_; 59 | bool exponentialDecay_; 60 | float level_; 61 | float slope_; 62 | int samplesUntilNextSegment_; 63 | bool segmentIsExponential_; 64 | static const float BottomLevel; 65 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EG) 66 | }; 67 | } 68 | 69 | #endif // SFZEG_H_INCLUDED 70 | -------------------------------------------------------------------------------- /sfzero/SFZReader.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZReader.h" 8 | #include "SFZRegion.h" 9 | #include "SFZSound.h" 10 | 11 | sfzero::Reader::Reader(sfzero::Sound *soundIn) : sound_(soundIn), line_(1) {} 12 | 13 | sfzero::Reader::~Reader() {} 14 | 15 | void sfzero::Reader::read(const juce::File &file) 16 | { 17 | juce::MemoryBlock contents; 18 | bool ok = file.loadFileAsData(contents); 19 | 20 | if (!ok) 21 | { 22 | sound_->addError("Couldn't read \"" + file.getFullPathName() + "\""); 23 | return; 24 | } 25 | 26 | read(static_cast(contents.getData()), static_cast(contents.getSize())); 27 | } 28 | 29 | void sfzero::Reader::read(const char *text, unsigned int length) 30 | { 31 | const char *p = text; 32 | const char *end = text + length; 33 | char c = 0; 34 | 35 | sfzero::Region curGroup; 36 | sfzero::Region curRegion; 37 | sfzero::Region *buildingRegion = nullptr; 38 | bool inControl = false; 39 | juce::String defaultPath; 40 | 41 | while (p < end) 42 | { 43 | // We're at the start of a line; skip any whitespace. 44 | while (p < end) 45 | { 46 | c = *p; 47 | if ((c != ' ') && (c != '\t')) 48 | { 49 | break; 50 | } 51 | p += 1; 52 | } 53 | if (p >= end) 54 | { 55 | break; 56 | } 57 | 58 | // Check if it's a comment line. 59 | if (c == '/') 60 | { 61 | // Skip to end of line. 62 | while (p < end) 63 | { 64 | c = *++p; 65 | if ((c == '\n') || (c == '\r')) 66 | { 67 | break; 68 | } 69 | } 70 | p = handleLineEnd(p); 71 | continue; 72 | } 73 | 74 | // Check if it's a blank line. 75 | if ((c == '\r') || (c == '\n')) 76 | { 77 | p = handleLineEnd(p); 78 | continue; 79 | } 80 | 81 | // Handle elements on the line. 82 | while (p < end) 83 | { 84 | c = *p; 85 | 86 | // Tag. 87 | if (c == '<') 88 | { 89 | p += 1; 90 | const char *tagStart = p; 91 | while (p < end) 92 | { 93 | c = *p++; 94 | if ((c == '\n') || (c == '\r')) 95 | { 96 | error("Unterminated tag"); 97 | goto fatalError; 98 | } 99 | else if (c == '>') 100 | { 101 | break; 102 | } 103 | } 104 | if (p >= end) 105 | { 106 | error("Unterminated tag"); 107 | goto fatalError; 108 | } 109 | sfzero::StringSlice tag(tagStart, p - 1); 110 | if (tag == "region") 111 | { 112 | if (buildingRegion && (buildingRegion == &curRegion)) 113 | { 114 | finishRegion(&curRegion); 115 | } 116 | curRegion = curGroup; 117 | buildingRegion = &curRegion; 118 | inControl = false; 119 | } 120 | else if (tag == "group") 121 | { 122 | if (buildingRegion && (buildingRegion == &curRegion)) 123 | { 124 | finishRegion(&curRegion); 125 | } 126 | curGroup.clear(); 127 | buildingRegion = &curGroup; 128 | inControl = false; 129 | } 130 | else if (tag == "control") 131 | { 132 | if (buildingRegion && (buildingRegion == &curRegion)) 133 | { 134 | finishRegion(&curRegion); 135 | } 136 | curGroup.clear(); 137 | buildingRegion = nullptr; 138 | inControl = true; 139 | } 140 | else 141 | { 142 | error("Illegal tag"); 143 | } 144 | } 145 | // Comment. 146 | else if (c == '/') 147 | { 148 | // Skip to end of line. 149 | while (p < end) 150 | { 151 | c = *p; 152 | if ((c == '\r') || (c == '\n')) 153 | { 154 | break; 155 | } 156 | p += 1; 157 | } 158 | } 159 | // Parameter. 160 | else 161 | { 162 | // Get the parameter name. 163 | const char *parameterStart = p; 164 | while (p < end) 165 | { 166 | c = *p++; 167 | if ((c == '=') || (c == ' ') || (c == '\t') || (c == '\r') || (c == '\n')) 168 | { 169 | break; 170 | } 171 | } 172 | if ((p >= end) || (c != '=')) 173 | { 174 | error("Malformed parameter"); 175 | goto nextElement; 176 | } 177 | sfzero::StringSlice opcode(parameterStart, p - 1); 178 | if (inControl) 179 | { 180 | if (opcode == "default_path") 181 | { 182 | p = readPathInto(&defaultPath, p, end); 183 | } 184 | else 185 | { 186 | const char *valueStart = p; 187 | while (p < end) 188 | { 189 | c = *p; 190 | if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')) 191 | { 192 | break; 193 | } 194 | p++; 195 | } 196 | juce::String value(valueStart, p - valueStart); 197 | juce::String fauxOpcode = juce::String(opcode.getStart(), opcode.length()) + " (in )"; 198 | sound_->addUnsupportedOpcode(fauxOpcode); 199 | } 200 | } 201 | else if (opcode == "sample") 202 | { 203 | juce::String path; 204 | p = readPathInto(&path, p, end); 205 | if (!path.isEmpty()) 206 | { 207 | if (buildingRegion) 208 | { 209 | buildingRegion->sample = sound_->addSample(path, defaultPath); 210 | } 211 | else 212 | { 213 | error("Adding sample outside a group or region"); 214 | } 215 | } 216 | else 217 | { 218 | error("Empty sample path"); 219 | } 220 | } 221 | else 222 | { 223 | const char *valueStart = p; 224 | while (p < end) 225 | { 226 | c = *p; 227 | if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')) 228 | { 229 | break; 230 | } 231 | p++; 232 | } 233 | juce::String value(valueStart, p - valueStart); 234 | if (buildingRegion == nullptr) 235 | { 236 | error("Setting a parameter outside a region or group"); 237 | } 238 | else if (opcode == "lokey") 239 | { 240 | buildingRegion->lokey = keyValue(value); 241 | } 242 | else if (opcode == "hikey") 243 | { 244 | buildingRegion->hikey = keyValue(value); 245 | } 246 | else if (opcode == "key") 247 | { 248 | buildingRegion->hikey = buildingRegion->lokey = buildingRegion->pitch_keycenter = keyValue(value); 249 | } 250 | else if (opcode == "lovel") 251 | { 252 | buildingRegion->lovel = value.getIntValue(); 253 | } 254 | else if (opcode == "hivel") 255 | { 256 | buildingRegion->hivel = value.getIntValue(); 257 | } 258 | else if (opcode == "trigger") 259 | { 260 | buildingRegion->trigger = static_cast(triggerValue(value)); 261 | } 262 | else if (opcode == "group") 263 | { 264 | buildingRegion->group = static_cast(value.getLargeIntValue()); 265 | } 266 | else if (opcode == "off_by") 267 | { 268 | buildingRegion->off_by = value.getLargeIntValue(); 269 | } 270 | else if (opcode == "offset") 271 | { 272 | buildingRegion->offset = value.getLargeIntValue(); 273 | } 274 | else if (opcode == "end") 275 | { 276 | juce::int64 end2 = value.getLargeIntValue(); 277 | if (end2 < 0) 278 | { 279 | buildingRegion->negative_end = true; 280 | } 281 | else 282 | { 283 | buildingRegion->end = end2; 284 | } 285 | } 286 | else if (opcode == "loop_mode") 287 | { 288 | bool modeIsSupported = value == "no_loop" || value == "one_shot" || value == "loop_continuous"; 289 | if (modeIsSupported) 290 | { 291 | buildingRegion->loop_mode = static_cast(loopModeValue(value)); 292 | } 293 | else 294 | { 295 | juce::String fauxOpcode = juce::String(opcode.getStart(), opcode.length()) + "=" + value; 296 | sound_->addUnsupportedOpcode(fauxOpcode); 297 | } 298 | } 299 | else if (opcode == "loop_start") 300 | { 301 | buildingRegion->loop_start = value.getLargeIntValue(); 302 | } 303 | else if (opcode == "loop_end") 304 | { 305 | buildingRegion->loop_end = value.getLargeIntValue(); 306 | } 307 | else if (opcode == "transpose") 308 | { 309 | buildingRegion->transpose = value.getIntValue(); 310 | } 311 | else if (opcode == "tune") 312 | { 313 | buildingRegion->tune = value.getIntValue(); 314 | } 315 | else if (opcode == "pitch_keycenter") 316 | { 317 | buildingRegion->pitch_keycenter = keyValue(value); 318 | } 319 | else if (opcode == "pitch_keytrack") 320 | { 321 | buildingRegion->pitch_keytrack = value.getIntValue(); 322 | } 323 | else if (opcode == "bend_up") 324 | { 325 | buildingRegion->bend_up = value.getIntValue(); 326 | } 327 | else if (opcode == "bend_down") 328 | { 329 | buildingRegion->bend_down = value.getIntValue(); 330 | } 331 | else if (opcode == "volume") 332 | { 333 | buildingRegion->volume = value.getFloatValue(); 334 | } 335 | else if (opcode == "pan") 336 | { 337 | buildingRegion->pan = value.getFloatValue(); 338 | } 339 | else if (opcode == "amp_veltrack") 340 | { 341 | buildingRegion->amp_veltrack = value.getFloatValue(); 342 | } 343 | else if (opcode == "ampeg_delay") 344 | { 345 | buildingRegion->ampeg.delay = value.getFloatValue(); 346 | } 347 | else if (opcode == "ampeg_start") 348 | { 349 | buildingRegion->ampeg.start = value.getFloatValue(); 350 | } 351 | else if (opcode == "ampeg_attack") 352 | { 353 | buildingRegion->ampeg.attack = value.getFloatValue(); 354 | } 355 | else if (opcode == "ampeg_hold") 356 | { 357 | buildingRegion->ampeg.hold = value.getFloatValue(); 358 | } 359 | else if (opcode == "ampeg_decay") 360 | { 361 | buildingRegion->ampeg.decay = value.getFloatValue(); 362 | } 363 | else if (opcode == "ampeg_sustain") 364 | { 365 | buildingRegion->ampeg.sustain = value.getFloatValue(); 366 | } 367 | else if (opcode == "ampeg_release") 368 | { 369 | buildingRegion->ampeg.release = value.getFloatValue(); 370 | } 371 | else if (opcode == "ampeg_vel2delay") 372 | { 373 | buildingRegion->ampeg_veltrack.delay = value.getFloatValue(); 374 | } 375 | else if (opcode == "ampeg_vel2attack") 376 | { 377 | buildingRegion->ampeg_veltrack.attack = value.getFloatValue(); 378 | } 379 | else if (opcode == "ampeg_vel2hold") 380 | { 381 | buildingRegion->ampeg_veltrack.hold = value.getFloatValue(); 382 | } 383 | else if (opcode == "ampeg_vel2decay") 384 | { 385 | buildingRegion->ampeg_veltrack.decay = value.getFloatValue(); 386 | } 387 | else if (opcode == "ampeg_vel2sustain") 388 | { 389 | buildingRegion->ampeg_veltrack.sustain = value.getFloatValue(); 390 | } 391 | else if (opcode == "ampeg_vel2release") 392 | { 393 | buildingRegion->ampeg_veltrack.release = value.getFloatValue(); 394 | } 395 | else if (opcode == "default_path") 396 | { 397 | error("\"default_path\" outside of tag"); 398 | } 399 | else 400 | { 401 | sound_->addUnsupportedOpcode(juce::String(opcode.getStart(), opcode.length())); 402 | } 403 | } 404 | } 405 | 406 | // Skip to next element. 407 | nextElement: 408 | c = 0; 409 | while (p < end) 410 | { 411 | c = *p; 412 | if ((c != ' ') && (c != '\t')) 413 | { 414 | break; 415 | } 416 | p += 1; 417 | } 418 | if ((c == '\r') || (c == '\n')) 419 | { 420 | p = handleLineEnd(p); 421 | break; 422 | } 423 | } 424 | } 425 | 426 | fatalError: 427 | if (buildingRegion && (buildingRegion == &curRegion)) 428 | { 429 | finishRegion(buildingRegion); 430 | } 431 | } 432 | 433 | const char *sfzero::Reader::handleLineEnd(const char *p) 434 | { 435 | // Check for DOS-style line ending. 436 | char lineEndChar = *p++; 437 | 438 | if ((lineEndChar == '\r') && (*p == '\n')) 439 | { 440 | p += 1; 441 | } 442 | line_ += 1; 443 | return p; 444 | } 445 | 446 | const char *sfzero::Reader::readPathInto(juce::String *pathOut, const char *pIn, const char *endIn) 447 | { 448 | // Paths are kind of funny to parse because they can contain whitespace. 449 | const char *p = pIn; 450 | const char *end = endIn; 451 | const char *pathStart = p; 452 | const char *potentialEnd = nullptr; 453 | 454 | while (p < end) 455 | { 456 | char c = *p; 457 | if (c == ' ') 458 | { 459 | // Is this space part of the path? Or the start of the next opcode? We 460 | // don't know yet. 461 | potentialEnd = p; 462 | p += 1; 463 | // Skip any more spaces. 464 | while (p < end && *p == ' ') 465 | { 466 | p += 1; 467 | } 468 | } 469 | else if ((c == '\n') || (c == '\r') || (c == '\t')) 470 | { 471 | break; 472 | } 473 | else if (c == '=') 474 | { 475 | // We've been looking at an opcode; we need to rewind to 476 | // potentialEnd. 477 | p = potentialEnd; 478 | break; 479 | } 480 | p += 1; 481 | } 482 | if (p > pathStart) 483 | { 484 | // Can't do this: 485 | // juce::String path(CharPointer_UTF8(pathStart), CharPointer_UTF8(p)); 486 | // It won't compile for some unfathomable reason. 487 | juce::CharPointer_UTF8 end2(p); 488 | juce::String path(juce::CharPointer_UTF8(pathStart), end2); 489 | *pathOut = path; 490 | } 491 | else 492 | { 493 | *pathOut = juce::String::empty; 494 | } 495 | return p; 496 | } 497 | 498 | int sfzero::Reader::keyValue(const juce::String &str) 499 | { 500 | auto chars = str.toRawUTF8(); 501 | 502 | char c = chars[0]; 503 | 504 | if ((c >= '0') && (c <= '9')) 505 | { 506 | return str.getIntValue(); 507 | } 508 | 509 | int note = 0; 510 | static const int notes[] = { 511 | 12 + 0, 12 + 2, 3, 5, 7, 8, 10, 512 | }; 513 | if ((c >= 'A') && (c <= 'G')) 514 | { 515 | note = notes[c - 'A']; 516 | } 517 | else if ((c >= 'a') && (c <= 'g')) 518 | { 519 | note = notes[c - 'a']; 520 | } 521 | int octaveStart = 1; 522 | 523 | c = chars[1]; 524 | if ((c == 'b') || (c == '#')) 525 | { 526 | octaveStart += 1; 527 | if (c == 'b') 528 | { 529 | note -= 1; 530 | } 531 | else 532 | { 533 | note += 1; 534 | } 535 | } 536 | 537 | int octave = str.substring(octaveStart).getIntValue(); 538 | // A3 == 57. 539 | int result = octave * 12 + note + (57 - 4 * 12); 540 | return result; 541 | } 542 | 543 | int sfzero::Reader::triggerValue(const juce::String &str) 544 | { 545 | if (str == "release") 546 | { 547 | return sfzero::Region::release; 548 | } 549 | if (str == "first") 550 | { 551 | return sfzero::Region::first; 552 | } 553 | if (str == "legato") 554 | { 555 | return sfzero::Region::legato; 556 | } 557 | return sfzero::Region::attack; 558 | } 559 | 560 | int sfzero::Reader::loopModeValue(const juce::String &str) 561 | { 562 | if (str == "no_loop") 563 | { 564 | return sfzero::Region::no_loop; 565 | } 566 | if (str == "one_shot") 567 | { 568 | return sfzero::Region::one_shot; 569 | } 570 | if (str == "loop_continuous") 571 | { 572 | return sfzero::Region::loop_continuous; 573 | } 574 | if (str == "loop_sustain") 575 | { 576 | return sfzero::Region::loop_sustain; 577 | } 578 | return sfzero::Region::sample_loop; 579 | } 580 | 581 | void sfzero::Reader::finishRegion(sfzero::Region *region) 582 | { 583 | sfzero::Region *newRegion = new sfzero::Region(); 584 | 585 | *newRegion = *region; 586 | sound_->addRegion(newRegion); 587 | } 588 | 589 | void sfzero::Reader::error(const juce::String &message) 590 | { 591 | juce::String fullMessage = message; 592 | 593 | fullMessage += " (line " + juce::String(line_) + ")."; 594 | sound_->addError(fullMessage); 595 | } 596 | -------------------------------------------------------------------------------- /sfzero/SFZReader.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZREADER_H_INCLUDED 8 | #define SFZREADER_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | struct Region; 16 | class Sound; 17 | 18 | class Reader 19 | { 20 | public: 21 | explicit Reader(Sound *sound); 22 | ~Reader(); 23 | 24 | void read(const juce::File &file); 25 | void read(const char *text, unsigned int length); 26 | 27 | private: 28 | const char *handleLineEnd(const char *p); 29 | const char *readPathInto(juce::String *pathOut, const char *p, const char *end); 30 | int keyValue(const juce::String &str); 31 | int triggerValue(const juce::String &str); 32 | int loopModeValue(const juce::String &str); 33 | void finishRegion(Region *region); 34 | void error(const juce::String &message); 35 | 36 | Sound *sound_; 37 | int line_; 38 | 39 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Reader) 40 | }; 41 | 42 | class StringSlice 43 | { 44 | public: 45 | StringSlice(const char *startIn, const char *endIn) : start_(startIn), end_(endIn) {} 46 | virtual ~StringSlice() {} 47 | 48 | unsigned int length() { return static_cast(end_ - start_); } 49 | bool operator==(const char *other) { return (strncmp(start_, other, length()) == 0); } 50 | bool operator!=(const char *other) { return (strncmp(start_, other, length()) != 0); } 51 | const char *getStart() const { return start_; } 52 | const char *getEnd() const { return end_; } 53 | private: 54 | const char *start_; 55 | const char *end_; 56 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StringSlice) 57 | }; 58 | } 59 | 60 | #endif // SFZREADER_H_INCLUDED 61 | -------------------------------------------------------------------------------- /sfzero/SFZRegion.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZRegion.h" 8 | #include "SFZSample.h" 9 | 10 | void sfzero::EGParameters::clear() 11 | { 12 | delay = 0.0; 13 | start = 0.0; 14 | attack = 0.0; 15 | hold = 0.0; 16 | decay = 0.0; 17 | sustain = 100.0; 18 | release = 0.0; 19 | } 20 | 21 | void sfzero::EGParameters::clearMod() 22 | { 23 | // Clear for velocity or other modification. 24 | delay = start = attack = hold = decay = sustain = release = 0.0; 25 | } 26 | 27 | sfzero::Region::Region() { clear(); } 28 | 29 | void sfzero::Region::clear() 30 | { 31 | memset(this, 0, sizeof(*this)); 32 | hikey = 127; 33 | hivel = 127; 34 | pitch_keycenter = 60; // C4 35 | pitch_keytrack = 100; 36 | bend_up = 200; 37 | bend_down = -200; 38 | volume = pan = 0.0; 39 | amp_veltrack = 100.0; 40 | ampeg.clear(); 41 | ampeg_veltrack.clearMod(); 42 | } 43 | 44 | void sfzero::Region::clearForSF2() 45 | { 46 | clear(); 47 | pitch_keycenter = -1; 48 | loop_mode = no_loop; 49 | 50 | // SF2 defaults in timecents. 51 | ampeg.delay = -12000.0; 52 | ampeg.attack = -12000.0; 53 | ampeg.hold = -12000.0; 54 | ampeg.decay = -12000.0; 55 | ampeg.sustain = 0.0; 56 | ampeg.release = -12000.0; 57 | } 58 | 59 | void sfzero::Region::clearForRelativeSF2() 60 | { 61 | clear(); 62 | pitch_keytrack = 0; 63 | amp_veltrack = 0.0; 64 | ampeg.sustain = 0.0; 65 | } 66 | 67 | void sfzero::Region::addForSF2(sfzero::Region *other) 68 | { 69 | offset += other->offset; 70 | end += other->end; 71 | loop_start += other->loop_start; 72 | loop_end += other->loop_end; 73 | transpose += other->transpose; 74 | tune += other->tune; 75 | pitch_keytrack += other->pitch_keytrack; 76 | volume += other->volume; 77 | pan += other->pan; 78 | 79 | ampeg.delay += other->ampeg.delay; 80 | ampeg.attack += other->ampeg.attack; 81 | ampeg.hold += other->ampeg.hold; 82 | ampeg.decay += other->ampeg.decay; 83 | ampeg.sustain += other->ampeg.sustain; 84 | ampeg.release += other->ampeg.release; 85 | } 86 | 87 | void sfzero::Region::sf2ToSFZ() 88 | { 89 | // EG times need to be converted from timecents to seconds. 90 | ampeg.delay = timecents2Secs(static_cast(ampeg.delay)); 91 | ampeg.attack = timecents2Secs(static_cast(ampeg.attack)); 92 | ampeg.hold = timecents2Secs(static_cast(ampeg.hold)); 93 | ampeg.decay = timecents2Secs(static_cast(ampeg.decay)); 94 | if (ampeg.sustain < 0.0f) 95 | { 96 | ampeg.sustain = 0.0f; 97 | } 98 | ampeg.sustain = 100.0f * juce::Decibels::decibelsToGain(-ampeg.sustain / 10.0f); 99 | ampeg.release = timecents2Secs(static_cast(ampeg.release)); 100 | 101 | // Pin very short EG segments. Timecents don't get to zero, and our EG is 102 | // happier with zero values. 103 | if (ampeg.delay < 0.01f) 104 | { 105 | ampeg.delay = 0.0f; 106 | } 107 | if (ampeg.attack < 0.01f) 108 | { 109 | ampeg.attack = 0.0f; 110 | } 111 | if (ampeg.hold < 0.01f) 112 | { 113 | ampeg.hold = 0.0f; 114 | } 115 | if (ampeg.decay < 0.01f) 116 | { 117 | ampeg.decay = 0.0f; 118 | } 119 | if (ampeg.release < 0.01f) 120 | { 121 | ampeg.release = 0.0f; 122 | } 123 | 124 | // Pin values to their ranges. 125 | if (pan < -100.0f) 126 | { 127 | pan = -100.0f; 128 | } 129 | else if (pan > 100.0f) 130 | { 131 | pan = 100.0f; 132 | } 133 | } 134 | 135 | juce::String sfzero::Region::dump() 136 | { 137 | juce::String info = juce::String::formatted("%d - %d, vel %d - %d", lokey, hikey, lovel, hivel); 138 | if (sample) 139 | { 140 | info << sample->getShortName(); 141 | } 142 | info << "\n"; 143 | return info; 144 | } 145 | 146 | float sfzero::Region::timecents2Secs(int timecents) { return static_cast(pow(2.0, timecents / 1200.0)); } 147 | -------------------------------------------------------------------------------- /sfzero/SFZRegion.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZREGION_H_INCLUDED 8 | #define SFZREGION_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | class Sample; 16 | 17 | // Region is designed to be able to be bitwise-copied. 18 | 19 | struct EGParameters 20 | { 21 | float delay, start, attack, hold, decay, sustain, release; 22 | 23 | void clear(); 24 | void clearMod(); 25 | }; 26 | 27 | struct Region 28 | { 29 | enum Trigger 30 | { 31 | attack, 32 | release, 33 | first, 34 | legato 35 | }; 36 | 37 | enum LoopMode 38 | { 39 | sample_loop, 40 | no_loop, 41 | one_shot, 42 | loop_continuous, 43 | loop_sustain 44 | }; 45 | 46 | enum OffMode 47 | { 48 | fast, 49 | normal 50 | }; 51 | 52 | Region(); 53 | void clear(); 54 | void clearForSF2(); 55 | void clearForRelativeSF2(); 56 | void addForSF2(Region *other); 57 | void sf2ToSFZ(); 58 | juce::String dump(); 59 | 60 | bool matches(int note, int velocity, Trigger trig) 61 | { 62 | return (note >= lokey && note <= hikey && velocity >= lovel && velocity <= hivel && 63 | (trig == this->trigger || (this->trigger == attack && (trig == first || trig == legato)))); 64 | } 65 | 66 | Sample *sample; 67 | int lokey, hikey; 68 | int lovel, hivel; 69 | Trigger trigger; 70 | int group; 71 | juce::int64 off_by; 72 | OffMode off_mode; 73 | 74 | juce::int64 offset; 75 | juce::int64 end; 76 | bool negative_end; 77 | LoopMode loop_mode; 78 | juce::int64 loop_start, loop_end; 79 | int transpose; 80 | int tune; 81 | int pitch_keycenter, pitch_keytrack; 82 | int bend_up, bend_down; 83 | 84 | float volume, pan; 85 | float amp_veltrack; 86 | 87 | EGParameters ampeg, ampeg_veltrack; 88 | 89 | static float timecents2Secs(int timecents); 90 | }; 91 | } 92 | 93 | #endif // SFZREGION_H_INCLUDED 94 | -------------------------------------------------------------------------------- /sfzero/SFZSample.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZSample.h" 8 | #include "SFZDebug.h" 9 | 10 | bool sfzero::Sample::load(juce::AudioFormatManager *formatManager) 11 | { 12 | juce::AudioFormatReader *reader = formatManager->createReaderFor(file_); 13 | 14 | if (reader == nullptr) 15 | { 16 | return false; 17 | } 18 | sampleRate_ = reader->sampleRate; 19 | sampleLength_ = reader->lengthInSamples; 20 | // Read some extra samples, which will be filled with zeros, so interpolation 21 | // can be done without having to check for the edge all the time. 22 | jassert(sampleLength_ < std::numeric_limits::max()); 23 | 24 | buffer_ = new juce::AudioSampleBuffer(reader->numChannels, static_cast(sampleLength_ + 4)); 25 | reader->read(buffer_, 0, static_cast(sampleLength_ + 4), 0, true, true); 26 | 27 | juce::StringPairArray *metadata = &reader->metadataValues; 28 | int numLoops = metadata->getValue("NumSampleLoops", "0").getIntValue(); 29 | if (numLoops > 0) 30 | { 31 | loopStart_ = metadata->getValue("Loop0Start", "0").getLargeIntValue(); 32 | loopEnd_ = metadata->getValue("Loop0End", "0").getLargeIntValue(); 33 | } 34 | delete reader; 35 | return true; 36 | } 37 | 38 | sfzero::Sample::~Sample() { delete buffer_; } 39 | 40 | juce::String sfzero::Sample::getShortName() { return (file_.getFileName()); } 41 | 42 | void sfzero::Sample::setBuffer(juce::AudioSampleBuffer *newBuffer) 43 | { 44 | buffer_ = newBuffer; 45 | sampleLength_ = buffer_->getNumSamples(); 46 | } 47 | 48 | juce::AudioSampleBuffer *sfzero::Sample::detachBuffer() 49 | { 50 | juce::AudioSampleBuffer *result = buffer_; 51 | buffer_ = nullptr; 52 | return result; 53 | } 54 | 55 | juce::String sfzero::Sample::dump() { return file_.getFullPathName() + "\n"; } 56 | 57 | #ifdef JUCE_DEBUG 58 | void sfzero::Sample::checkIfZeroed(const char *where) 59 | { 60 | if (buffer_ == nullptr) 61 | { 62 | sfzero::dbgprintf("SFZSample::checkIfZeroed(%s): no buffer!", where); 63 | return; 64 | } 65 | 66 | int samplesLeft = buffer_->getNumSamples(); 67 | juce::int64 nonzero = 0, zero = 0; 68 | const float *p = buffer_->getReadPointer(0); 69 | for (; samplesLeft > 0; --samplesLeft) 70 | { 71 | if (*p++ == 0.0) 72 | { 73 | zero += 1; 74 | } 75 | else 76 | { 77 | nonzero += 1; 78 | } 79 | } 80 | if (nonzero > 0) 81 | { 82 | sfzero::dbgprintf("Buffer not zeroed at %s (%lu vs. %lu).", where, nonzero, zero); 83 | } 84 | else 85 | { 86 | sfzero::dbgprintf("Buffer zeroed at %s! (%lu zeros)", where, zero); 87 | } 88 | } 89 | 90 | #endif // JUCE_DEBUG 91 | -------------------------------------------------------------------------------- /sfzero/SFZSample.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZSAMPLE_H_INCLUDED 8 | #define SFZSAMPLE_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | class Sample 16 | { 17 | public: 18 | explicit Sample(const juce::File &fileIn) : file_(fileIn), buffer_(nullptr), sampleRate_(0), sampleLength_(0), loopStart_(0), loopEnd_(0) {} 19 | explicit Sample(double sampleRateIn) : buffer_(nullptr), sampleRate_(sampleRateIn), sampleLength_(0), loopStart_(0), loopEnd_(0) {} 20 | virtual ~Sample(); 21 | 22 | bool load(juce::AudioFormatManager *formatManager); 23 | 24 | juce::File getFile() { return (file_); } 25 | juce::AudioSampleBuffer *getBuffer() { return (buffer_); } 26 | double getSampleRate() { return (sampleRate_); } 27 | juce::String getShortName(); 28 | void setBuffer(juce::AudioSampleBuffer *newBuffer); 29 | juce::AudioSampleBuffer *detachBuffer(); 30 | juce::String dump(); 31 | juce::uint64 getSampleLength() const { return sampleLength_; } 32 | juce::uint64 getLoopStart() const { return loopStart_; } 33 | juce::uint64 getLoopEnd() const { return loopEnd_; } 34 | 35 | #ifdef JUCE_DEBUG 36 | void checkIfZeroed(const char *where); 37 | 38 | #endif 39 | 40 | private: 41 | juce::File file_; 42 | juce::AudioSampleBuffer *buffer_; 43 | double sampleRate_; 44 | juce::uint64 sampleLength_, loopStart_, loopEnd_; 45 | 46 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Sample) 47 | }; 48 | } 49 | 50 | #endif // SFZSAMPLE_H_INCLUDED 51 | -------------------------------------------------------------------------------- /sfzero/SFZSound.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZSound.h" 8 | #include "SFZReader.h" 9 | #include "SFZRegion.h" 10 | #include "SFZSample.h" 11 | 12 | sfzero::Sound::Sound(const juce::File &fileIn) : file_(fileIn) {} 13 | sfzero::Sound::~Sound() 14 | { 15 | int numRegions = regions_.size(); 16 | 17 | for (int i = 0; i < numRegions; ++i) 18 | { 19 | delete regions_[i]; 20 | regions_.set(i, nullptr); 21 | } 22 | 23 | for (juce::HashMap::Iterator i(samples_); i.next();) 24 | { 25 | delete i.getValue(); 26 | } 27 | } 28 | 29 | bool sfzero::Sound::appliesToNote(int /*midiNoteNumber*/) 30 | { 31 | // Just say yes; we can't truly know unless we're told the velocity as well. 32 | return true; 33 | } 34 | 35 | bool sfzero::Sound::appliesToChannel(int /*midiChannel*/) { return true; } 36 | void sfzero::Sound::addRegion(sfzero::Region *region) { regions_.add(region); } 37 | sfzero::Sample *sfzero::Sound::addSample(juce::String path, juce::String defaultPath) 38 | { 39 | path = path.replaceCharacter('\\', '/'); 40 | defaultPath = defaultPath.replaceCharacter('\\', '/'); 41 | juce::File sampleFile; 42 | if (defaultPath.isEmpty()) 43 | { 44 | sampleFile = file_.getSiblingFile(path); 45 | } 46 | else 47 | { 48 | juce::File defaultDir = file_.getSiblingFile(defaultPath); 49 | sampleFile = defaultDir.getChildFile(path); 50 | } 51 | juce::String samplePath = sampleFile.getFullPathName(); 52 | sfzero::Sample *sample = samples_[samplePath]; 53 | if (sample == nullptr) 54 | { 55 | sample = new sfzero::Sample(sampleFile); 56 | samples_.set(samplePath, sample); 57 | } 58 | return sample; 59 | } 60 | 61 | void sfzero::Sound::addError(const juce::String &message) { errors_.add(message); } 62 | 63 | void sfzero::Sound::addUnsupportedOpcode(const juce::String &opcode) 64 | { 65 | if (!unsupportedOpcodes_.contains(opcode)) 66 | { 67 | unsupportedOpcodes_.set(opcode, opcode); 68 | juce::String warning = "unsupported opcode: "; 69 | warning << opcode; 70 | warnings_.add(warning); 71 | } 72 | } 73 | 74 | void sfzero::Sound::loadRegions() 75 | { 76 | sfzero::Reader reader(this); 77 | 78 | reader.read(file_); 79 | } 80 | 81 | void sfzero::Sound::loadSamples(juce::AudioFormatManager *formatManager, double *progressVar, juce::Thread *thread) 82 | { 83 | if (progressVar) 84 | { 85 | *progressVar = 0.0; 86 | } 87 | 88 | double numSamplesLoaded = 1.0, numSamples = samples_.size(); 89 | for (juce::HashMap::Iterator i(samples_); i.next();) 90 | { 91 | sfzero::Sample *sample = i.getValue(); 92 | bool ok = sample->load(formatManager); 93 | if (!ok) 94 | { 95 | addError("Couldn't load sample \"" + sample->getShortName() + "\""); 96 | } 97 | 98 | numSamplesLoaded += 1.0; 99 | if (progressVar) 100 | { 101 | *progressVar = numSamplesLoaded / numSamples; 102 | } 103 | if (thread && thread->threadShouldExit()) 104 | { 105 | return; 106 | } 107 | } 108 | 109 | if (progressVar) 110 | { 111 | *progressVar = 1.0; 112 | } 113 | } 114 | 115 | sfzero::Region *sfzero::Sound::getRegionFor(int note, int velocity, sfzero::Region::Trigger trigger) 116 | { 117 | int numRegions = regions_.size(); 118 | 119 | for (int i = 0; i < numRegions; ++i) 120 | { 121 | sfzero::Region *region = regions_[i]; 122 | if (region->matches(note, velocity, trigger)) 123 | { 124 | return region; 125 | } 126 | } 127 | 128 | return nullptr; 129 | } 130 | 131 | int sfzero::Sound::getNumRegions() { return regions_.size(); } 132 | 133 | sfzero::Region *sfzero::Sound::regionAt(int index) { return regions_[index]; } 134 | 135 | int sfzero::Sound::numSubsounds() { return 1; } 136 | 137 | juce::String sfzero::Sound::subsoundName(int /*whichSubsound*/) { return juce::String::empty; } 138 | 139 | void sfzero::Sound::useSubsound(int /*whichSubsound*/) {} 140 | 141 | int sfzero::Sound::selectedSubsound() { return 0; } 142 | 143 | juce::String sfzero::Sound::dump() 144 | { 145 | juce::String info; 146 | auto &errors = getErrors(); 147 | if (errors.size() > 0) 148 | { 149 | info << errors.size() << " errors: \n"; 150 | info << errors.joinIntoString("\n"); 151 | info << "\n"; 152 | } 153 | else 154 | { 155 | info << "no errors.\n\n"; 156 | } 157 | 158 | auto &warnings = getWarnings(); 159 | if (warnings.size() > 0) 160 | { 161 | info << warnings.size() << " warnings: \n"; 162 | info << warnings.joinIntoString("\n"); 163 | } 164 | else 165 | { 166 | info << "no warnings.\n"; 167 | } 168 | 169 | if (regions_.size() > 0) 170 | { 171 | info << regions_.size() << " regions: \n"; 172 | for (int i = 0; i < regions_.size(); ++i) 173 | { 174 | info << regions_[i]->dump(); 175 | } 176 | } 177 | else 178 | { 179 | info << "no regions.\n"; 180 | } 181 | 182 | if (samples_.size() > 0) 183 | { 184 | info << samples_.size() << " samples: \n"; 185 | for (juce::HashMap::Iterator i(samples_); i.next();) 186 | { 187 | info << i.getValue()->dump(); 188 | } 189 | } 190 | else 191 | { 192 | info << "no samples.\n"; 193 | } 194 | return info; 195 | } 196 | -------------------------------------------------------------------------------- /sfzero/SFZSound.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZSOUND_H_INCLUDED 8 | #define SFZSOUND_H_INCLUDED 9 | 10 | #include "SFZRegion.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | class Sample; 16 | 17 | class Sound : public juce::SynthesiserSound 18 | { 19 | public: 20 | explicit Sound(const juce::File &file); 21 | virtual ~Sound(); 22 | 23 | typedef juce::ReferenceCountedObjectPtr Ptr; 24 | 25 | bool appliesToNote(int midiNoteNumber) override; 26 | bool appliesToChannel(int midiChannel) override; 27 | 28 | void addRegion(Region *region); // Takes ownership of the region. 29 | Sample *addSample(juce::String path, juce::String defaultPath = juce::String::empty); 30 | void addError(const juce::String &message); 31 | void addUnsupportedOpcode(const juce::String &opcode); 32 | 33 | virtual void loadRegions(); 34 | virtual void loadSamples(juce::AudioFormatManager *formatManager, double *progressVar = nullptr, 35 | juce::Thread *thread = nullptr); 36 | 37 | Region *getRegionFor(int note, int velocity, Region::Trigger trigger = Region::attack); 38 | int getNumRegions(); 39 | Region *regionAt(int index); 40 | 41 | const juce::StringArray &getErrors() { return errors_; } 42 | const juce::StringArray &getWarnings() { return warnings_; } 43 | 44 | virtual int numSubsounds(); 45 | virtual juce::String subsoundName(int whichSubsound); 46 | virtual void useSubsound(int whichSubsound); 47 | virtual int selectedSubsound(); 48 | 49 | juce::String dump(); 50 | juce::Array &getRegions() { return regions_; } 51 | juce::File &getFile() { return file_; } 52 | 53 | private: 54 | juce::File file_; 55 | juce::Array regions_; 56 | juce::HashMap samples_; 57 | juce::StringArray errors_; 58 | juce::StringArray warnings_; 59 | juce::HashMap unsupportedOpcodes_; 60 | 61 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Sound) 62 | }; 63 | } 64 | 65 | #endif // SFZSOUND_H_INCLUDED 66 | -------------------------------------------------------------------------------- /sfzero/SFZSynth.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZSynth.h" 8 | #include "SFZSound.h" 9 | #include "SFZVoice.h" 10 | 11 | sfzero::Synth::Synth() : Synthesiser() {} 12 | 13 | void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) 14 | { 15 | int i; 16 | 17 | const juce::ScopedLock locker(lock); 18 | 19 | int midiVelocity = static_cast(velocity * 127); 20 | 21 | // First, stop any currently-playing sounds in the group. 22 | //*** Currently, this only pays attention to the first matching region. 23 | int group = 0; 24 | sfzero::Sound *sound = dynamic_cast(getSound(0)); 25 | 26 | if (sound) 27 | { 28 | sfzero::Region *region = sound->getRegionFor(midiNoteNumber, midiVelocity); 29 | if (region) 30 | { 31 | group = region->group; 32 | } 33 | } 34 | if (group != 0) 35 | { 36 | for (i = voices.size(); --i >= 0;) 37 | { 38 | sfzero::Voice *voice = dynamic_cast(voices.getUnchecked(i)); 39 | if (voice == nullptr) 40 | { 41 | continue; 42 | } 43 | if (voice->getOffBy() == group) 44 | { 45 | voice->stopNoteForGroup(); 46 | } 47 | } 48 | } 49 | 50 | // Are any notes playing? (Needed for first/legato trigger handling.) 51 | // Also stop any voices still playing this note. 52 | bool anyNotesPlaying = false; 53 | for (i = voices.size(); --i >= 0;) 54 | { 55 | sfzero::Voice *voice = dynamic_cast(voices.getUnchecked(i)); 56 | if (voice == nullptr) 57 | { 58 | continue; 59 | } 60 | if (voice->isPlayingChannel(midiChannel)) 61 | { 62 | if (voice->isPlayingNoteDown()) 63 | { 64 | if (voice->getCurrentlyPlayingNote() == midiNoteNumber) 65 | { 66 | if (!voice->isPlayingOneShot()) 67 | { 68 | voice->stopNoteQuick(); 69 | } 70 | } 71 | else 72 | { 73 | anyNotesPlaying = true; 74 | } 75 | } 76 | } 77 | } 78 | 79 | // Play *all* matching regions. 80 | sfzero::Region::Trigger trigger = (anyNotesPlaying ? sfzero::Region::legato : sfzero::Region::first); 81 | if (sound) 82 | { 83 | int numRegions = sound->getNumRegions(); 84 | for (i = 0; i < numRegions; ++i) 85 | { 86 | sfzero::Region *region = sound->regionAt(i); 87 | if (region->matches(midiNoteNumber, midiVelocity, trigger)) 88 | { 89 | sfzero::Voice *voice = 90 | dynamic_cast(findFreeVoice(sound, midiNoteNumber, midiChannel, isNoteStealingEnabled())); 91 | if (voice) 92 | { 93 | voice->setRegion(region); 94 | startVoice(voice, sound, midiChannel, midiNoteNumber, velocity); 95 | } 96 | } 97 | } 98 | } 99 | 100 | noteVelocities_[midiNoteNumber] = midiVelocity; 101 | } 102 | 103 | void sfzero::Synth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) 104 | { 105 | const juce::ScopedLock locker(lock); 106 | 107 | Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff); 108 | 109 | // Start release region. 110 | sfzero::Sound *sound = dynamic_cast(getSound(0)); 111 | if (sound) 112 | { 113 | sfzero::Region *region = sound->getRegionFor(midiNoteNumber, noteVelocities_[midiNoteNumber], sfzero::Region::release); 114 | if (region) 115 | { 116 | sfzero::Voice *voice = dynamic_cast(findFreeVoice(sound, midiNoteNumber, midiChannel, false)); 117 | if (voice) 118 | { 119 | // Synthesiser is too locked-down (ivars are private rt protected), so 120 | // we have to use a "setRegion()" mechanism. 121 | voice->setRegion(region); 122 | startVoice(voice, sound, midiChannel, midiNoteNumber, noteVelocities_[midiNoteNumber] / 127.0f); 123 | } 124 | } 125 | } 126 | } 127 | 128 | int sfzero::Synth::numVoicesUsed() 129 | { 130 | int numUsed = 0; 131 | 132 | for (int i = voices.size(); --i >= 0;) 133 | { 134 | if (voices.getUnchecked(i)->getCurrentlyPlayingNote() >= 0) 135 | { 136 | numUsed += 1; 137 | } 138 | } 139 | return numUsed; 140 | } 141 | 142 | juce::String sfzero::Synth::voiceInfoString() 143 | { 144 | enum 145 | { 146 | maxShownVoices = 20, 147 | }; 148 | 149 | juce::StringArray lines; 150 | int numUsed = 0, numShown = 0; 151 | for (int i = voices.size(); --i >= 0;) 152 | { 153 | sfzero::Voice *voice = dynamic_cast(voices.getUnchecked(i)); 154 | if (voice->getCurrentlyPlayingNote() < 0) 155 | { 156 | continue; 157 | } 158 | numUsed += 1; 159 | if (numShown >= maxShownVoices) 160 | { 161 | continue; 162 | } 163 | lines.add(voice->infoString()); 164 | } 165 | lines.insert(0, "voices used: " + juce::String(numUsed)); 166 | return lines.joinIntoString("\n"); 167 | } 168 | -------------------------------------------------------------------------------- /sfzero/SFZSynth.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZSYNTH_H_INCLUDED 8 | #define SFZSYNTH_H_INCLUDED 9 | 10 | #include "SFZCommon.h" 11 | 12 | namespace sfzero 13 | { 14 | 15 | class Synth : public juce::Synthesiser 16 | { 17 | public: 18 | Synth(); 19 | virtual ~Synth() {} 20 | 21 | void noteOn(int midiChannel, int midiNoteNumber, float velocity) override; 22 | void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) override; 23 | 24 | int numVoicesUsed(); 25 | juce::String voiceInfoString(); 26 | 27 | private: 28 | int noteVelocities_[128]; 29 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Synth) 30 | }; 31 | } 32 | 33 | #endif // SFZSYNTH_H_INCLUDED 34 | -------------------------------------------------------------------------------- /sfzero/SFZVoice.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #include "SFZDebug.h" 8 | #include "SFZRegion.h" 9 | #include "SFZSample.h" 10 | #include "SFZSound.h" 11 | #include "SFZVoice.h" 12 | #include 13 | 14 | static const float globalGain = -1.0; 15 | 16 | sfzero::Voice::Voice() 17 | : region_(nullptr), trigger_(0), curMidiNote_(0), curPitchWheel_(0), pitchRatio_(0), noteGainLeft_(0), noteGainRight_(0), 18 | sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0) 19 | { 20 | ampeg_.setExponentialDecay(true); 21 | } 22 | 23 | sfzero::Voice::~Voice() {} 24 | 25 | bool sfzero::Voice::canPlaySound(juce::SynthesiserSound *sound) { return dynamic_cast(sound) != nullptr; } 26 | 27 | void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, juce::SynthesiserSound *soundIn, 28 | int currentPitchWheelPosition) 29 | { 30 | sfzero::Sound *sound = dynamic_cast(soundIn); 31 | 32 | if (sound == nullptr) 33 | { 34 | killNote(); 35 | return; 36 | } 37 | 38 | int velocity = static_cast(floatVelocity * 127.0); 39 | curVelocity_ = velocity; 40 | if (region_ == nullptr) 41 | { 42 | region_ = sound->getRegionFor(midiNoteNumber, velocity); 43 | } 44 | if ((region_ == nullptr) || (region_->sample == nullptr) || (region_->sample->getBuffer() == nullptr)) 45 | { 46 | killNote(); 47 | return; 48 | } 49 | if (region_->negative_end) 50 | { 51 | killNote(); 52 | return; 53 | } 54 | 55 | // Pitch. 56 | curMidiNote_ = midiNoteNumber; 57 | curPitchWheel_ = currentPitchWheelPosition; 58 | calcPitchRatio(); 59 | 60 | // Gain. 61 | double noteGainDB = globalGain + region_->volume; 62 | // Thanks to for explaining the 63 | // velocity curve in a way that I could understand, although they mean 64 | // "log10" when they say "log". 65 | double velocityGainDB = -20.0 * log10((127.0 * 127.0) / (velocity * velocity)); 66 | velocityGainDB *= region_->amp_veltrack / 100.0; 67 | noteGainDB += velocityGainDB; 68 | noteGainLeft_ = noteGainRight_ = static_cast(juce::Decibels::decibelsToGain(noteGainDB)); 69 | // The SFZ spec is silent about the pan curve, but a 3dB pan law seems 70 | // common. This sqrt() curve matches what Dimension LE does; Alchemy Free 71 | // seems closer to sin(adjustedPan * pi/2). 72 | double adjustedPan = (region_->pan + 100.0) / 200.0; 73 | noteGainLeft_ *= static_cast(sqrt(1.0 - adjustedPan)); 74 | noteGainRight_ *= static_cast(sqrt(adjustedPan)); 75 | ampeg_.startNote(®ion_->ampeg, floatVelocity, getSampleRate(), ®ion_->ampeg_veltrack); 76 | 77 | // Offset/end. 78 | sourceSamplePosition_ = static_cast(region_->offset); 79 | sampleEnd_ = region_->sample->getSampleLength(); 80 | if ((region_->end > 0) && (region_->end < sampleEnd_)) 81 | { 82 | sampleEnd_ = region_->end + 1; 83 | } 84 | 85 | // Loop. 86 | loopStart_ = loopEnd_ = 0; 87 | sfzero::Region::LoopMode loopMode = region_->loop_mode; 88 | if (loopMode == sfzero::Region::sample_loop) 89 | { 90 | if (region_->sample->getLoopStart() < region_->sample->getLoopEnd()) 91 | { 92 | loopMode = sfzero::Region::loop_continuous; 93 | } 94 | else 95 | { 96 | loopMode = sfzero::Region::no_loop; 97 | } 98 | } 99 | if ((loopMode != sfzero::Region::no_loop) && (loopMode != sfzero::Region::one_shot)) 100 | { 101 | if (region_->loop_start < region_->loop_end) 102 | { 103 | loopStart_ = region_->loop_start; 104 | loopEnd_ = region_->loop_end; 105 | } 106 | else 107 | { 108 | loopStart_ = region_->sample->getLoopStart(); 109 | loopEnd_ = region_->sample->getLoopEnd(); 110 | } 111 | } 112 | numLoops_ = 0; 113 | } 114 | 115 | void sfzero::Voice::stopNote(float /*velocity*/, bool allowTailOff) 116 | { 117 | if (!allowTailOff || (region_ == nullptr)) 118 | { 119 | killNote(); 120 | return; 121 | } 122 | 123 | if (region_->loop_mode != sfzero::Region::one_shot) 124 | { 125 | ampeg_.noteOff(); 126 | } 127 | if (region_->loop_mode == sfzero::Region::loop_sustain) 128 | { 129 | // Continue playing, but stop looping. 130 | loopEnd_ = loopStart_; 131 | } 132 | } 133 | 134 | void sfzero::Voice::stopNoteForGroup() 135 | { 136 | if (region_->off_mode == sfzero::Region::fast) 137 | { 138 | ampeg_.fastRelease(); 139 | } 140 | else 141 | { 142 | ampeg_.noteOff(); 143 | } 144 | } 145 | 146 | void sfzero::Voice::stopNoteQuick() { ampeg_.fastRelease(); } 147 | void sfzero::Voice::pitchWheelMoved(int newValue) 148 | { 149 | if (region_ == nullptr) 150 | { 151 | return; 152 | } 153 | 154 | curPitchWheel_ = newValue; 155 | calcPitchRatio(); 156 | } 157 | 158 | void sfzero::Voice::controllerMoved(int /*controllerNumber*/, int /*newValue*/) { /***/} 159 | void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) 160 | { 161 | if (region_ == nullptr) 162 | { 163 | return; 164 | } 165 | 166 | juce::AudioSampleBuffer *buffer = region_->sample->getBuffer(); 167 | const float *inL = buffer->getReadPointer(0, 0); 168 | const float *inR = buffer->getNumChannels() > 1 ? buffer->getReadPointer(1, 0) : nullptr; 169 | 170 | float *outL = outputBuffer.getWritePointer(0, startSample); 171 | float *outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer(1, startSample) : nullptr; 172 | 173 | int bufferNumSamples = buffer->getNumSamples(); // leoo 174 | 175 | // Cache some values, to give them at least some chance of ending up in 176 | // registers. 177 | double sourceSamplePosition = this->sourceSamplePosition_; 178 | float ampegGain = ampeg_.getLevel(); 179 | float ampegSlope = ampeg_.getSlope(); 180 | int samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment(); 181 | bool ampSegmentIsExponential = ampeg_.getSegmentIsExponential(); 182 | float loopStart = static_cast(this->loopStart_); 183 | float loopEnd = static_cast(this->loopEnd_); 184 | float sampleEnd = static_cast(this->sampleEnd_); 185 | 186 | while (--numSamples >= 0) 187 | { 188 | int pos = static_cast(sourceSamplePosition); 189 | jassert(pos >= 0 && pos < bufferNumSamples); // leoo 190 | float alpha = static_cast(sourceSamplePosition - pos); 191 | float invAlpha = 1.0f - alpha; 192 | int nextPos = pos + 1; 193 | if ((loopStart < loopEnd) && (nextPos > loopEnd)) 194 | { 195 | nextPos = static_cast(loopStart); 196 | } 197 | 198 | // Simple linear interpolation with buffer overrun check 199 | float nextL = nextPos < bufferNumSamples ? inL[nextPos] : inL[pos]; 200 | float nextR = inR ? (nextPos < bufferNumSamples ? inR[nextPos] : inR[pos]) : nextL; 201 | float l = (inL[pos] * invAlpha + nextL * alpha); 202 | float r = inR ? (inR[pos] * invAlpha + nextR * alpha) : l; 203 | 204 | //// Simple linear interpolation, old version (possible buffer overrun with non-loop??) 205 | // float l = (inL[pos] * invAlpha + inL[nextPos] * alpha); 206 | // float r = inR ? (inR[pos] * invAlpha + inR[nextPos] * alpha) : l; 207 | 208 | float gainLeft = noteGainLeft_ * ampegGain; 209 | float gainRight = noteGainRight_ * ampegGain; 210 | l *= gainLeft; 211 | r *= gainRight; 212 | // Shouldn't we dither here? 213 | 214 | if (outR) 215 | { 216 | *outL++ += l; 217 | *outR++ += r; 218 | } 219 | else 220 | { 221 | *outL++ += (l + r) * 0.5f; 222 | } 223 | 224 | // Next sample. 225 | sourceSamplePosition += pitchRatio_; 226 | if ((loopStart < loopEnd) && (sourceSamplePosition > loopEnd)) 227 | { 228 | sourceSamplePosition = loopStart; 229 | numLoops_ += 1; 230 | } 231 | 232 | // Update EG. 233 | if (ampSegmentIsExponential) 234 | { 235 | ampegGain *= ampegSlope; 236 | } 237 | else 238 | { 239 | ampegGain += ampegSlope; 240 | } 241 | if (--samplesUntilNextAmpSegment < 0) 242 | { 243 | ampeg_.setLevel(ampegGain); 244 | ampeg_.nextSegment(); 245 | ampegGain = ampeg_.getLevel(); 246 | ampegSlope = ampeg_.getSlope(); 247 | samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment(); 248 | ampSegmentIsExponential = ampeg_.getSegmentIsExponential(); 249 | } 250 | 251 | if ((sourceSamplePosition >= sampleEnd) || ampeg_.isDone()) 252 | { 253 | killNote(); 254 | break; 255 | } 256 | } 257 | 258 | this->sourceSamplePosition_ = sourceSamplePosition; 259 | ampeg_.setLevel(ampegGain); 260 | ampeg_.setSamplesUntilNextSegment(samplesUntilNextAmpSegment); 261 | } 262 | 263 | bool sfzero::Voice::isPlayingNoteDown() { return region_ && region_->trigger != sfzero::Region::release; } 264 | 265 | bool sfzero::Voice::isPlayingOneShot() { return region_ && region_->loop_mode == sfzero::Region::one_shot; } 266 | 267 | int sfzero::Voice::getGroup() { return region_ ? region_->group : 0; } 268 | 269 | juce::uint64 sfzero::Voice::getOffBy() { return region_ ? region_->off_by : 0; } 270 | 271 | void sfzero::Voice::setRegion(sfzero::Region *nextRegion) { region_ = nextRegion; } 272 | 273 | juce::String sfzero::Voice::infoString() 274 | { 275 | const char *egSegmentNames[] = {"delay", "attack", "hold", "decay", "sustain", "release", "done"}; 276 | 277 | const static int numEGSegments(sizeof(egSegmentNames) / sizeof(egSegmentNames[0])); 278 | 279 | const char *egSegmentName = "-Invalid-"; 280 | int egSegmentIndex = ampeg_.segmentIndex(); 281 | if ((egSegmentIndex >= 0) && (egSegmentIndex < numEGSegments)) 282 | { 283 | egSegmentName = egSegmentNames[egSegmentIndex]; 284 | } 285 | 286 | juce::String info; 287 | info << "note: " << curMidiNote_ << ", vel: " << curVelocity_ << ", pan: " << region_->pan << ", eg: " << egSegmentName 288 | << ", loops: " << numLoops_; 289 | return info; 290 | } 291 | 292 | void sfzero::Voice::calcPitchRatio() 293 | { 294 | double note = curMidiNote_; 295 | 296 | note += region_->transpose; 297 | note += region_->tune / 100.0; 298 | 299 | double adjustedPitch = region_->pitch_keycenter + (note - region_->pitch_keycenter) * (region_->pitch_keytrack / 100.0); 300 | if (curPitchWheel_ != 8192) 301 | { 302 | double wheel = ((2.0 * curPitchWheel_ / 16383.0) - 1.0); 303 | if (wheel > 0) 304 | { 305 | adjustedPitch += wheel * region_->bend_up / 100.0; 306 | } 307 | else 308 | { 309 | adjustedPitch += wheel * region_->bend_down / -100.0; 310 | } 311 | } 312 | double targetFreq = fractionalMidiNoteInHz(adjustedPitch); 313 | double naturalFreq = juce::MidiMessage::getMidiNoteInHertz(region_->pitch_keycenter); 314 | pitchRatio_ = (targetFreq * region_->sample->getSampleRate()) / (naturalFreq * getSampleRate()); 315 | } 316 | 317 | void sfzero::Voice::killNote() 318 | { 319 | region_ = nullptr; 320 | clearCurrentNote(); 321 | } 322 | 323 | double sfzero::Voice::fractionalMidiNoteInHz(double note, double freqOfA) 324 | { 325 | // Like MidiMessage::getMidiNoteInHertz(), but with a float note. 326 | note -= 69; 327 | // Now 0 = A 328 | return freqOfA * pow(2.0, note / 12.0); 329 | } 330 | -------------------------------------------------------------------------------- /sfzero/SFZVoice.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | * Original code copyright (C) 2012 Steve Folta 3 | * Converted to Juce module (C) 2016 Leo Olivers 4 | * Forked from https://github.com/stevefolta/SFZero 5 | * For license info please see the LICENSE file distributed with this source code 6 | *************************************************************************************/ 7 | #ifndef SFZVOICE_H_INCLUDED 8 | #define SFZVOICE_H_INCLUDED 9 | 10 | #include "SFZEG.h" 11 | 12 | namespace sfzero 13 | { 14 | struct Region; 15 | 16 | class Voice : public juce::SynthesiserVoice 17 | { 18 | public: 19 | Voice(); 20 | virtual ~Voice(); 21 | 22 | bool canPlaySound(juce::SynthesiserSound *sound) override; 23 | void startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound *sound, int currentPitchWheelPosition) override; 24 | void stopNote(float velocity, bool allowTailOff) override; 25 | void stopNoteForGroup(); 26 | void stopNoteQuick(); 27 | void pitchWheelMoved(int newValue) override; 28 | void controllerMoved(int controllerNumber, int newValue) override; 29 | void renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) override; 30 | bool isPlayingNoteDown(); 31 | bool isPlayingOneShot(); 32 | 33 | int getGroup(); 34 | juce::uint64 getOffBy(); 35 | 36 | // Set the region to be used by the next startNote(). 37 | void setRegion(Region *nextRegion); 38 | 39 | juce::String infoString(); 40 | 41 | private: 42 | Region *region_; 43 | int trigger_; 44 | int curMidiNote_, curPitchWheel_; 45 | double pitchRatio_; 46 | float noteGainLeft_, noteGainRight_; 47 | double sourceSamplePosition_; 48 | EG ampeg_; 49 | juce::int64 sampleEnd_; 50 | juce::int64 loopStart_, loopEnd_; 51 | 52 | // Info only. 53 | int numLoops_; 54 | int curVelocity_; 55 | 56 | void calcPitchRatio(); 57 | void killNote(); 58 | double fractionalMidiNoteInHz(double note, double freqOfA = 440.0); 59 | 60 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Voice) 61 | }; 62 | } 63 | 64 | #endif // SFZVOICE_H_INCLUDED 65 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/generators.h: -------------------------------------------------------------------------------- 1 | SF2GeneratorValue(startAddrsOffset, Short), SF2GeneratorValue(endAddrsOffset, Short), 2 | SF2GeneratorValue(startloopAddrsOffset, Short), SF2GeneratorValue(endloopAddrsOffset, Short), 3 | SF2GeneratorValue(startAddrsCoarseOffset, Short), SF2GeneratorValue(modLfoToPitch, Short), 4 | SF2GeneratorValue(vibLfoToPitch, Short), SF2GeneratorValue(modEnvToPitch, Short), SF2GeneratorValue(initialFilterFc, Short), 5 | SF2GeneratorValue(initialFilterQ, Short), SF2GeneratorValue(modLfoToFilterFc, Short), 6 | SF2GeneratorValue(modEnvToFilterFc, Short), SF2GeneratorValue(endAddrsCoarseOffset, Short), 7 | SF2GeneratorValue(modLfoToVolume, Short), SF2GeneratorValue(unused1, Short), SF2GeneratorValue(chorusEffectsSend, Short), 8 | SF2GeneratorValue(reverbEffectsSend, Short), SF2GeneratorValue(pan, Short), SF2GeneratorValue(unused2, Short), 9 | SF2GeneratorValue(unused3, Short), SF2GeneratorValue(unused4, Short), SF2GeneratorValue(delayModLFO, Short), 10 | SF2GeneratorValue(freqModLFO, Short), SF2GeneratorValue(delayVibLFO, Short), SF2GeneratorValue(freqVibLFO, Short), 11 | SF2GeneratorValue(delayModEnv, Short), SF2GeneratorValue(attackModEnv, Short), SF2GeneratorValue(holdModEnv, Short), 12 | SF2GeneratorValue(decayModEnv, Short), SF2GeneratorValue(sustainModEnv, Short), SF2GeneratorValue(releaseModEnv, Short), 13 | SF2GeneratorValue(keynumToModEnvHold, Short), SF2GeneratorValue(keynumToModEnvDecay, Short), 14 | SF2GeneratorValue(delayVolEnv, Short), SF2GeneratorValue(attackVolEnv, Short), SF2GeneratorValue(holdVolEnv, Short), 15 | SF2GeneratorValue(decayVolEnv, Short), SF2GeneratorValue(sustainVolEnv, Short), SF2GeneratorValue(releaseVolEnv, Short), 16 | SF2GeneratorValue(keynumToVolEnvHold, Short), SF2GeneratorValue(keynumToVolEnvDecay, Short), 17 | SF2GeneratorValue(instrument, Word), SF2GeneratorValue(reserved1, Short), SF2GeneratorValue(keyRange, Range), 18 | SF2GeneratorValue(velRange, Range), SF2GeneratorValue(startloopAddrsCoarseOffset, Short), SF2GeneratorValue(keynum, Short), 19 | SF2GeneratorValue(velocity, Short), SF2GeneratorValue(initialAttenuation, Short), SF2GeneratorValue(reserved2, Short), 20 | SF2GeneratorValue(endloopAddrsCoarseOffset, Short), SF2GeneratorValue(coarseTune, Short), SF2GeneratorValue(fineTune, Short), 21 | SF2GeneratorValue(sampleID, Word), SF2GeneratorValue(sampleModes, Word), SF2GeneratorValue(reserved3, Short), 22 | SF2GeneratorValue(scaleTuning, Short), SF2GeneratorValue(exclusiveClass, Short), SF2GeneratorValue(overridingRootKey, Short), 23 | SF2GeneratorValue(unused5, Short), SF2GeneratorValue(endOper, Short), 24 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/ibag.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, instGenNdx) SF2Field(word, instModNdx) 2 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/igen.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, genOper) SF2Field(genAmountType, genAmount) 2 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/imod.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, modSrcOper) SF2Field(word, modDestOper) SF2Field(short, modAmount) SF2Field(word, modAmtSrcOper) 2 | SF2Field(word, modTransOper) 3 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/inst.h: -------------------------------------------------------------------------------- 1 | SF2Field(char20, instName) SF2Field(word, instBagNdx) 2 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/iver.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, major); 2 | SF2Field(word, minor); 3 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/pbag.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, genNdx) SF2Field(word, modNdx) 2 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/pgen.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, genOper) SF2Field(genAmountType, genAmount) 2 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/phdr.h: -------------------------------------------------------------------------------- 1 | SF2Field(char20, presetName) SF2Field(word, preset) SF2Field(word, bank) SF2Field(word, presetBagNdx) SF2Field(dword, library) 2 | SF2Field(dword, genre) SF2Field(dword, morphology) 3 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/pmod.h: -------------------------------------------------------------------------------- 1 | SF2Field(word, modSrcOper) SF2Field(word, modDestOper) SF2Field(short, modAmount) SF2Field(word, modAmtSrcOper) 2 | SF2Field(word, modTransOper) 3 | -------------------------------------------------------------------------------- /sfzero/sf2-chunks/shdr.h: -------------------------------------------------------------------------------- 1 | SF2Field(char20, sampleName) SF2Field(dword, start) SF2Field(dword, end) SF2Field(dword, startLoop) SF2Field(dword, endLoop) 2 | SF2Field(dword, sampleRate) SF2Field(byte, originalPitch) SF2Field(char, pitchCorrection) SF2Field(word, sampleLink) 3 | SF2Field(word, sampleType) 4 | --------------------------------------------------------------------------------