├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── LICENSE ├── sfizz-node.js ├── .github └── workflows │ └── build.yml ├── index.css ├── index.html ├── sfizz-processor.js ├── util └── WASMAudioBuffer.js ├── sfizz_webaudio.cpp ├── index.js └── ace └── mode-sfz.js /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sfizz"] 2 | path = sfizz 3 | url = https://github.com/paulfd/sfizz 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(sfizz-webaudio) 2 | 3 | set(SFIZZ_TESTS OFF CACHE BOOL "" FORCE) 4 | set(SFIZZ_LV2 OFF CACHE BOOL "" FORCE) 5 | set(SFIZZ_LV2_UI OFF CACHE BOOL "" FORCE) 6 | set(SFIZZ_VST OFF CACHE BOOL "" FORCE) 7 | set(SFIZZ_RENDER OFF CACHE BOOL "" FORCE) 8 | set(SFIZZ_JACK OFF CACHE BOOL "" FORCE) 9 | add_subdirectory(sfizz) 10 | 11 | add_executable(sfizz-webaudio) 12 | target_sources(sfizz-webaudio PRIVATE sfizz_webaudio.cpp) 13 | target_link_libraries(sfizz-webaudio PRIVATE sfizz::sfizz) 14 | set_target_properties(sfizz-webaudio PROPERTIES 15 | LINK_FLAGS "--bind -s ENVIRONMENT=web -s ALLOW_MEMORY_GROWTH=1 -s SINGLE_FILE=1 -s WASM=1 -s WASM_ASYNC_COMPILATION=0 -s EXPORTED_FUNCTIONS=\"['_malloc']\" -s EXPORT_ES6=1" 16 | OUTPUT_NAME sfizz.wasm 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## sfizz WebAudio/WebMidi demo 2 | 3 | Live demo (requires Chrome or one of its derivatives): https://sfz.tools/sfizz-webaudio/ 4 | 5 | This repository contains a HTML/Javascript/WebAssembly front-end for [sfizz](https://sfz.tools/sfizz), which allows prototyping of virtual instruments in the SFZ format. 6 | Right now only generators and embedded sample files are supported. This uses the `emscripten` branch off my sfizz's fork, available [here](https://github.com/paulfd/sfizz/tree/emscripten). 7 | Compared the main sfizz branch, the background loader is deactivated and all files are loaded in memory (since WebAssembly through browsers only allows access to a virtual file system). 8 | 9 | ### Building 10 | 11 | This assumes you have the [emsdk](https://github.com/emscripten-core/emsdk) installed and activated. 12 | ```sh 13 | mkdir build 14 | cd build 15 | emcmake cmake -DCMAKE_BUILD_TYPE=Release .. 16 | make -j 17 | ``` 18 | 19 | From the `build` directory, you may then host the result on `localhost:8000` using python as 20 | ```sh 21 | python3 -m http.server --directory .. 22 | ``` 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Paul Ferrand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do 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 | -------------------------------------------------------------------------------- /sfizz-node.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Paul Ferrand 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the "Software"), 5 | // to deal in the Software without restriction, including without limitation 6 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | // and/or sell copies of the Software, and to permit persons to whom the 8 | // Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | // DEALINGS IN THE SOFTWARE. 20 | 21 | export default class SfizzNode extends AudioWorkletNode { 22 | constructor(context) { 23 | super(context, 'sfizz', { numberOfOutputs: 2 }); 24 | } 25 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | env: 9 | BUILD_TYPE: Release 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | submodules: recursive 18 | - uses: mymindstorm/setup-emsdk@v11 19 | - name: Create Build Environment 20 | shell: bash 21 | working-directory: ${{runner.workspace}}/sfizz-webaudio 22 | run: cmake -E make_directory build 23 | - name: Configure CMake 24 | shell: bash 25 | working-directory: ${{runner.workspace}}/sfizz-webaudio/build 26 | run: | 27 | emcmake cmake "$GITHUB_WORKSPACE" -DCMAKE_BUILD_TYPE=Release 28 | - name: Build 29 | shell: bash 30 | working-directory: ${{runner.workspace}}/sfizz-webaudio/build 31 | run: | 32 | cmake --build . --config "$BUILD_TYPE" -j 2 33 | - name: Inspect directory 34 | working-directory: ${{runner.workspace}}/sfizz-webaudio 35 | run: | 36 | pwd 37 | ls -al 38 | ls -al build 39 | - name: Archive artifacts 40 | uses: actions/upload-artifact@v2 41 | with: 42 | name: sfizz-webaudio 43 | path: | 44 | index.html 45 | index.js 46 | index.css 47 | sfizz-node.js 48 | sfizz-processor.js 49 | build/sfizz.wasm.js 50 | util/WASMAudioBuffer.js 51 | 52 | upload: 53 | name: Create release and upload artifacts 54 | needs: 55 | - build 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Download artifacts 59 | uses: actions/download-artifact@v2 60 | - name: Inspect directory 61 | run: ls -alFR 62 | - name: Upload to GitHub pages 63 | uses: peaceiris/actions-gh-pages@v3 64 | with: 65 | github_token: ${{ secrets.GITHUB_TOKEN }} 66 | publish_branch: www 67 | publish_dir: ./sfizz-webaudio 68 | force_orphan: true 69 | - name: Rezip artifact 70 | uses: montudor/action-zip@v1 71 | with: 72 | args: zip -r sfizz-webaudio.zip sfizz-webaudio 73 | - name: Create release and upload artifacts 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | run: | 77 | wget -q https://github.com/TheAssassin/pyuploadtool/releases/download/continuous/pyuploadtool-x86_64.AppImage 78 | chmod +x pyuploadtool-x86_64.AppImage 79 | ./pyuploadtool-x86_64.AppImage sfizz-webaudio.zip -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | #control-row { 2 | display: flex; 3 | flex-direction: row; 4 | /* justify-content: center; */ 5 | width: 100%; 6 | padding: 5px; 7 | } 8 | 9 | #control-row > div { 10 | margin: 5px; 11 | } 12 | 13 | #left-control-column { 14 | display: flex; 15 | flex-direction: column; 16 | width: fit-content; 17 | } 18 | 19 | #left-control-column .ui.slider { 20 | width: 150px; 21 | } 22 | 23 | #left-control-column .ui.input { 24 | width: 80px; 25 | height: 30px; 26 | margin-left: 10px; 27 | } 28 | 29 | #left-control-column > div { 30 | width: fit-content; 31 | display: flex; 32 | flex-direction: row; 33 | align-items: center; 34 | } 35 | 36 | #left-control-column > div > span { 37 | width: 100px; 38 | } 39 | 40 | #right-control-column { 41 | display: flex; 42 | flex-direction: column; 43 | width: fit-content; 44 | } 45 | 46 | #right-control-column > div > span { 47 | position: relative; 48 | right: 0; 49 | left: auto; 50 | } 51 | 52 | #right-control-column > div { 53 | width:150px; 54 | margin-top: 5px; 55 | } 56 | 57 | #keyboard-buttons { 58 | position: relative; 59 | width: fit-content; 60 | margin-left: auto; 61 | margin-right: auto; 62 | } 63 | 64 | #keyboard-buttons > span { 65 | padding: 5px; 66 | } 67 | 68 | #keyboard { 69 | position: relative; 70 | margin-left: auto; 71 | margin-right: auto; 72 | margin-top: 10; 73 | height: 100px; 74 | } 75 | 76 | #editor { 77 | flex: 1; 78 | height: 500px; 79 | } 80 | 81 | .white { 82 | width: 20px; 83 | height: 100px; 84 | position: absolute; 85 | top: 0; 86 | border-right-style: none; 87 | border-width: 1px; 88 | border-color: black; 89 | background: white; 90 | display: flex; 91 | justify-content: center; 92 | color: black; 93 | align-items: flex-end; 94 | } 95 | 96 | .black { 97 | width: 10px; 98 | height: 60px; 99 | position: absolute; 100 | border-color: black; 101 | border-width: 1px; 102 | background: black; 103 | display: flex; 104 | justify-content: center; 105 | align-items: flex-end; 106 | color: white; 107 | } 108 | 109 | .black:hover { 110 | background-color: dimgrey; 111 | } 112 | 113 | .white:hover { 114 | background-color: lightgray; 115 | } 116 | 117 | #overlay { 118 | background: rgba(0, 0, 0, .9); 119 | color: #fff; 120 | cursor: pointer; 121 | width: 100%; 122 | height: 100%; 123 | z-index: 10; 124 | top: 0; 125 | left: 0; 126 | position: fixed; 127 | display: flex; 128 | justify-content: center; 129 | align-items: center; 130 | } 131 | 132 | .overlay-text { 133 | width: fit-content; 134 | margin: auto; 135 | z-index: 11; 136 | } 137 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
Click to enable WebAudio
29 |
30 |
31 |
32 |
33 |
// You can use Ctrl + S to reload <region> sample=*saw
34 |
35 | 36 |
1
region(s)
37 |
0
active voice(s)
38 |
39 |
40 |
41 |
42 |
43 | 44 | Octave 45 | 46 | 47 |
48 |
49 |
50 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 65 | -------------------------------------------------------------------------------- /sfizz-processor.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Paul Ferrand 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the "Software"), 5 | // to deal in the Software without restriction, including without limitation 6 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | // and/or sell copies of the Software, and to permit persons to whom the 8 | // Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | // DEALINGS IN THE SOFTWARE. 20 | 21 | import Module from './build/sfizz.wasm.js'; 22 | import WASMAudioBuffer from './util/WASMAudioBuffer.js'; 23 | 24 | const SfizzModule = new Module(); 25 | 26 | // Web Audio API's render block size 27 | const NUM_FRAMES = 128; 28 | 29 | class SfizzProcessor extends AudioWorkletProcessor { 30 | constructor() { 31 | super(); 32 | // Create an instance of Synthesizer and WASM memory helper. Then set up an 33 | // event handler for MIDI data from the main thread. 34 | this._synth = new SfizzModule.SfizzWrapper(sampleRate); 35 | this._leftBuffer = new WASMAudioBuffer(SfizzModule, NUM_FRAMES, 1, 1); 36 | this._rightBuffer = new WASMAudioBuffer(SfizzModule, NUM_FRAMES, 1, 1); 37 | this._activeVoices = 0; 38 | this.port.onmessage = this._handleMessage.bind(this); 39 | } 40 | 41 | process(inputs, outputs) { 42 | // Call the render function to fill the WASM buffer. Then clone the 43 | // rendered data to process() callback's output buffer. 44 | this._synth.render(this._leftBuffer.getPointer(), this._rightBuffer.getPointer(), NUM_FRAMES); 45 | outputs[0][0].set(this._leftBuffer.getF32Array()); 46 | outputs[1][0].set(this._rightBuffer.getF32Array()); 47 | const activeVoices = this._synth.numActiveVoices(); 48 | if (activeVoices != this._activeVoices) { 49 | this.port.postMessage({ activeVoices: this._synth.numActiveVoices() }); 50 | this._activeVoices = activeVoices; 51 | } 52 | return true; 53 | } 54 | 55 | _handleMessage(event) { 56 | const data = event.data; 57 | switch (data.type) { 58 | case 'note_on': 59 | this._synth.noteOn(0, data.number, data.value); 60 | break; 61 | case 'note_off': 62 | this._synth.noteOff(0, data.number, data.value); 63 | break; 64 | case 'cc': 65 | this._synth.cc(0, data.number, data.value); 66 | break; 67 | case 'aftertouch': 68 | this._synth.aftertouch(0, data.value); 69 | break; 70 | case 'pitch_wheel': 71 | this._synth.pitchWheel(0, data.value); 72 | break; 73 | case 'text': 74 | this._synth.load(data.sfz); 75 | const usedCCs = this._synth.usedCCs(); 76 | for (let i = 0; i < usedCCs.size(); i++) { 77 | const cc = usedCCs.get(i); 78 | var ccLabel = this._synth.ccLabel(cc); 79 | // Default names 80 | if (ccLabel == '') { 81 | switch(cc) { 82 | case 7: ccLabel = 'Volume'; break; 83 | case 10: ccLabel = 'Pan'; break; 84 | case 11: ccLabel = 'Expression'; break; 85 | } 86 | } 87 | 88 | const ccValue = this._synth.ccValue(cc); 89 | const ccDefault = this._synth.ccDefault(cc); 90 | this.port.postMessage({ cc: cc, label: ccLabel, value: ccValue, default: ccDefault }); 91 | } 92 | this.port.postMessage({ numRegions: this._synth.numRegions() }); 93 | break; 94 | case 'num_regions': 95 | this.port.postMessage({ numRegions: this._synth.numRegions() }); 96 | break; 97 | case 'active_voices': 98 | this.port.postMessage({ activeVoices: this._synth.numActiveVoices() }); 99 | break; 100 | default: 101 | console.log("Unknown message: ", event); 102 | } 103 | } 104 | } 105 | 106 | registerProcessor('sfizz', SfizzProcessor); -------------------------------------------------------------------------------- /util/WASMAudioBuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | // Basic byte unit of WASM heap. (16 bit = 2 bytes) 19 | const BYTES_PER_UNIT = Uint16Array.BYTES_PER_ELEMENT; 20 | 21 | // Byte per audio sample. (32 bit float) 22 | const BYTES_PER_SAMPLE = Float32Array.BYTES_PER_ELEMENT; 23 | 24 | // The max audio channel on Chrome is 32. 25 | const MAX_CHANNEL_COUNT = 32; 26 | 27 | /** 28 | * A WASM HEAP wrapper for AudioBuffer class. This breaks down the AudioBuffer 29 | * into an Array of Float32Array for the convinient WASM opearion. 30 | * 31 | * @class 32 | * @dependency Module A WASM module generated by the emscripten glue code. 33 | */ 34 | class WASMAudioBuffer { 35 | /** 36 | * @constructor 37 | * @param {object} wasmModule WASM module generated by Emscripten. 38 | * @param {number} length Buffer frame length. 39 | * @param {number} channelCount Number of channels. 40 | * @param {number=} maxChannelCount Maximum number of channels. 41 | */ 42 | constructor(wasmModule, length, channelCount, maxChannelCount) { 43 | // The |channelCount| must be greater than 0, and less than or equal to 44 | // the maximum channel count. 45 | this._isInitialized = false; 46 | this._module = wasmModule; 47 | this._length = length; 48 | this._maxChannelCount = maxChannelCount 49 | ? Math.min(maxChannelCount, MAX_CHANNEL_COUNT) 50 | : channelCount; 51 | this._channelCount = channelCount; 52 | this._allocateHeap(); 53 | this._isInitialized = true; 54 | } 55 | 56 | /** 57 | * Allocates memory in the WASM heap and set up Float32Array views for the 58 | * channel data. 59 | * 60 | * @private 61 | */ 62 | _allocateHeap() { 63 | const channelByteSize = this._length * BYTES_PER_SAMPLE; 64 | const dataByteSize = this._channelCount * channelByteSize; 65 | this._dataPtr = this._module._malloc(dataByteSize); 66 | this._channelData = []; 67 | for (let i = 0; i < this._channelCount; ++i) { 68 | let startByteOffset = this._dataPtr + i * channelByteSize; 69 | let endByteOffset = startByteOffset + channelByteSize; 70 | // Get the actual array index by dividing the byte offset by 2 bytes. 71 | this._channelData[i] = 72 | this._module.HEAPF32.subarray(startByteOffset >> BYTES_PER_UNIT, 73 | endByteOffset >> BYTES_PER_UNIT); 74 | } 75 | } 76 | 77 | /** 78 | * Adapt the current channel count to the new input buffer. 79 | * 80 | * @param {number} newChannelCount The new channel count. 81 | */ 82 | adaptChannel(newChannelCount) { 83 | if (newChannelCount < this._maxChannelCount) { 84 | this._channelCount = newChannelCount; 85 | } 86 | } 87 | 88 | /** 89 | * Getter for the buffer length in frames. 90 | * 91 | * @return {?number} Buffer length in frames. 92 | */ 93 | get length() { 94 | return this._isInitialized ? this._length : null; 95 | } 96 | 97 | /** 98 | * Getter for the number of channels. 99 | * 100 | * @return {?number} Buffer length in frames. 101 | */ 102 | get numberOfChannels() { 103 | return this._isInitialized ? this._channelCount : null; 104 | } 105 | 106 | /** 107 | * Getter for the maxixmum number of channels allowed for the instance. 108 | * 109 | * @return {?number} Buffer length in frames. 110 | */ 111 | get maxChannelCount() { 112 | return this._isInitialized ? this._maxChannelCount : null; 113 | } 114 | 115 | /** 116 | * Returns a Float32Array object for a given channel index. If the channel 117 | * index is undefined, it returns the reference to the entire array of channel 118 | * data. 119 | * 120 | * @param {number|undefined} channelIndex Channel index. 121 | * @return {?Array} a channel data array or an 122 | * array of channel data. 123 | */ 124 | getChannelData(channelIndex) { 125 | if (channelIndex >= this._channelCount) { 126 | return null; 127 | } 128 | 129 | return typeof channelIndex === 'undefined' 130 | ? this._channelData : this._channelData[channelIndex]; 131 | } 132 | 133 | getF32Array() { 134 | return this._channelData[0]; 135 | } 136 | 137 | /** 138 | * Returns the base address of the allocated memory space in the WASM heap. 139 | * 140 | * @return {number} WASM Heap address. 141 | */ 142 | getPointer() { 143 | return this._dataPtr; 144 | } 145 | 146 | /** 147 | * Frees the allocated memory space in the WASM heap. 148 | */ 149 | free() { 150 | this._isInitialized = false; 151 | this._module._free(this._dataPtr); 152 | this._module._free(this._pointerArrayPtr); // Useful? 153 | this._channelData = null; 154 | } 155 | } // class WASMAudioBuffer 156 | 157 | export default WASMAudioBuffer; 158 | -------------------------------------------------------------------------------- /sfizz_webaudio.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Paul Ferrand 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the "Software"), 5 | // to deal in the Software without restriction, including without limitation 6 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | // and/or sell copies of the Software, and to permit persons to whom the 8 | // Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | // DEALINGS IN THE SOFTWARE. 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "sfizz.hpp" 26 | #include "sfizz/utility/bit_array/BitArray.h" 27 | 28 | using namespace emscripten; 29 | 30 | class SfizzWrapper 31 | { 32 | public: 33 | SfizzWrapper(int32_t sampleRate) 34 | { 35 | synth.setReceiveCallback(*client, &SfizzWrapper::staticCallback); 36 | synth.setSampleRate(static_cast(sampleRate)); 37 | load(" sample=*saw"); 38 | } 39 | 40 | void load(std::string file) 41 | { 42 | synth.loadSfzString("file.sfz", file); 43 | synth.getCCLabels(); 44 | } 45 | 46 | void noteOn(int delay, uint8_t number, float value) { synth.hdNoteOn(delay, number, value); } 47 | void noteOff(int delay, uint8_t number, float value) { synth.hdNoteOff(delay, number, value); } 48 | void cc(int delay, uint8_t number, float value) { synth.hdcc(delay, number, value); } 49 | void aftertouch(int delay, float value) { synth.hdChannelAftertouch(delay, value); } 50 | void pitchWheel(int delay, float value) { synth.hdPitchWheel(delay, value); } 51 | 52 | int numRegions() const { return synth.getNumRegions(); } 53 | int numActiveVoices() const { return synth.getNumActiveVoices(); } 54 | std::vector usedCCs() const 55 | { 56 | const_cast(this)->synth.sendMessage(*client, 0, "/cc/slots", "", nullptr); 57 | return usedCCs_; 58 | } 59 | std::string ccLabel(int number) const 60 | { 61 | if (number < 0) 62 | return {}; 63 | 64 | lastLabel.clear(); 65 | std::stringstream path; 66 | path << "/cc" << number << "/label"; 67 | const_cast(this)->synth.sendMessage(*client, 0, path.str().c_str(), "", nullptr); 68 | return lastLabel; 69 | } 70 | float ccValue(int number) const 71 | { 72 | if (number < 0) 73 | return 0.0f; 74 | 75 | lastCCValue = 0.0f; 76 | std::stringstream path; 77 | path << "/cc" << number << "/value"; 78 | const_cast(this)->synth.sendMessage(*client, 0, path.str().c_str(), "", nullptr); 79 | return lastCCValue; 80 | } 81 | 82 | float ccDefault(int number) const 83 | { 84 | if (number < 0) 85 | return 0.0f; 86 | 87 | lastCCDefault = 0.0f; 88 | std::stringstream path; 89 | path << "/cc" << number << "/default"; 90 | const_cast(this)->synth.sendMessage(*client, 0, path.str().c_str(), "", nullptr); 91 | return lastCCDefault; 92 | } 93 | 94 | void render(uintptr_t left, uintptr_t right, int32_t numFrames) 95 | { 96 | // Use type cast to hide the raw pointer in function arguments. 97 | float *output_array[2] = { 98 | reinterpret_cast(left), 99 | reinterpret_cast(right)}; 100 | synth.renderBlock(output_array, numFrames); 101 | } 102 | 103 | static void staticCallback(void *data, int delay, const char *path, const char *sig, const sfizz_arg_t *args) 104 | { 105 | auto self = reinterpret_cast(data); 106 | self->clientCallback(delay, path, sig, args); 107 | } 108 | 109 | void clientCallback(int delay, const char *path, const char *sig, const sfizz_arg_t *args) 110 | { 111 | unsigned indices[8]; 112 | 113 | if (!strcmp(path, "/cc/slots") && !strcmp(sig, "b")) 114 | { 115 | usedCCs_.clear(); 116 | ConstBitSpan bits(args[0].b->data, 8 * args[0].b->size); 117 | for (unsigned cc = 0; cc < 512 && cc < bits.bit_size(); ++cc) 118 | { 119 | if (bits.test(cc)) 120 | { 121 | usedCCs_.push_back(cc); 122 | std::stringstream path; 123 | path << "/cc" << cc << "/label"; 124 | synth.sendMessage(*client, delay, path.str().c_str(), "", nullptr); 125 | path.clear(); 126 | path << "/cc" << cc << "/value"; 127 | synth.sendMessage(*client, delay, path.str().c_str(), "", nullptr); 128 | path.clear(); 129 | path << "/cc" << cc << "/default"; 130 | synth.sendMessage(*client, delay, path.str().c_str(), "", nullptr); 131 | } 132 | } 133 | } else if (matchOSC("/cc&/label", path, indices) && !strcmp(sig, "s")) { 134 | lastLabel = args[0].s; 135 | } else if (matchOSC("/cc&/value", path, indices) && !strcmp(sig, "f")) { 136 | lastCCValue = args[0].f; 137 | } else if (matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f")) { 138 | lastCCDefault = args[0].f; 139 | } 140 | } 141 | 142 | private: 143 | sfz::Sfizz synth; 144 | sfz::ClientPtr client = synth.createClient(this); 145 | std::vector usedCCs_; 146 | mutable std::string lastLabel; 147 | mutable float lastCCValue; 148 | mutable float lastCCDefault; 149 | 150 | bool matchOSC(const char *pattern, const char *path, unsigned *indices) 151 | { 152 | unsigned nthIndex = 0; 153 | 154 | while (const char *endp = std::strchr(pattern, '&')) 155 | { 156 | size_t length = endp - pattern; 157 | if (std::strncmp(pattern, path, length)) 158 | return false; 159 | pattern += length; 160 | path += length; 161 | 162 | length = 0; 163 | while (std::isdigit(path[length])) 164 | ++length; 165 | 166 | auto result = std::from_chars(path, path + length, indices[nthIndex++]); 167 | if (result.ec != std::errc()) 168 | return false; 169 | 170 | pattern += 1; 171 | path += length; 172 | } 173 | 174 | return !std::strcmp(path, pattern); 175 | } 176 | }; 177 | 178 | EMSCRIPTEN_BINDINGS(CLASS_Synthesizer) 179 | { 180 | register_vector("vector"); 181 | class_("SfizzWrapper") 182 | .constructor() 183 | .function("load", &SfizzWrapper::load) 184 | .function("noteOff", &SfizzWrapper::noteOff) 185 | .function("noteOn", &SfizzWrapper::noteOn) 186 | .function("cc", &SfizzWrapper::cc) 187 | .function("aftertouch", &SfizzWrapper::aftertouch) 188 | .function("pitchWheel", &SfizzWrapper::pitchWheel) 189 | .function("numRegions", &SfizzWrapper::numRegions) 190 | .function("numActiveVoices", &SfizzWrapper::numActiveVoices) 191 | .function("usedCCs", &SfizzWrapper::usedCCs) 192 | .function("ccLabel", &SfizzWrapper::ccLabel) 193 | .function("ccValue", &SfizzWrapper::ccValue) 194 | .function("ccDefault", &SfizzWrapper::ccDefault) 195 | .function("render", &SfizzWrapper::render, allow_raw_pointers()); 196 | } 197 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Paul Ferrand 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the "Software"), 5 | // to deal in the Software without restriction, including without limitation 6 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | // and/or sell copies of the Software, and to permit persons to whom the 8 | // Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | // DEALINGS IN THE SOFTWARE. 20 | 21 | import SfizzNode from './sfizz-node.js'; 22 | 23 | class SfizzApp { 24 | 25 | constructor() { 26 | this._kbKeyShortcuts = [ 27 | 'z', 's', 'x', 'd', 'c', 'v', 'g', 'b', 'h', 'n', 'j', 'm', 28 | 'q', '2', 'w', '3', 'e', 'r', '5', 't', '6', 'y', '7', 'u', 'i' 29 | ] 30 | this._overlay = null; 31 | this._keyboard = null; 32 | this._keyboardButtons = null; 33 | this._reloadButton = null; 34 | this._context = null; 35 | this._synthNode = null; 36 | this._volumeNode = null; 37 | this._toggleState = false; 38 | this._editor = null; 39 | this._ctrlDown = false; 40 | this._noteStates = new Array(128); 41 | for (let i = 0; i < 128; i++) { 42 | this._noteStates[i] = { midiState: false, mouseState: false, keyboardState: false, element: undefined, whiteKey: undefined }; 43 | } 44 | this._kbOctaveShift = 5; 45 | this._numRegions = null; 46 | this._activeVoices = null; 47 | this._leftControlColumn = null; 48 | this._sliders = new Array(512); 49 | for (let i = 0; i < 512; i++) { 50 | this._sliders[i] = { used: false, label: '', default: 0.0, element: undefined, input: undefined }; 51 | } 52 | } 53 | 54 | _initializeView() { 55 | this._overlay = document.getElementById('overlay'); 56 | this._reloadButton = document.getElementById('reload-text'); 57 | this._reloadButton.addEventListener( 58 | 'mouseup', () => this._reloadSfz()); 59 | this._keyboard = document.getElementById('keyboard'); 60 | this._keyboardButtons = document.getElementById('keyboard-buttons'); 61 | this._keyboardButtons = document.getElementById('keyboard-buttons'); 62 | this._numRegions = document.getElementById('num-regions'); 63 | this._activeVoices = document.getElementById('active-voices'); 64 | this._leftControlColumn = document.getElementById('left-control-column'); 65 | 66 | $('#octave-down').on('mousedown', () => { 67 | if (this._kbOctaveShift > 0) 68 | this._kbOctaveShift--; 69 | 70 | this._updateKeyboardLabels(); 71 | }); 72 | 73 | $('#octave-up').on('mousedown', () => { 74 | if (this._kbOctaveShift < 7) 75 | this._kbOctaveShift++; 76 | 77 | this._updateKeyboardLabels(); 78 | }); 79 | } 80 | 81 | _reloadSfz() { 82 | 83 | this._sliders.forEach((slider) => { 84 | slider.used = false; 85 | slider.element = undefined; 86 | slider.input = undefined; 87 | }); 88 | 89 | while (this._leftControlColumn.firstChild) 90 | this._leftControlColumn.removeChild(this._leftControlColumn.firstChild); 91 | 92 | this._post({ type: 'text', sfz: this._editor.getValue() }); 93 | } 94 | 95 | _setupKeyCaptures() { 96 | document.addEventListener('keydown', e => { 97 | if (e.ctrlKey && e.key === 's') { 98 | e.preventDefault(); // Prevent the Save dialog to open 99 | this._reloadSfz(); 100 | return; 101 | } 102 | 103 | if (this._editor.isFocused()) 104 | return; 105 | 106 | this._kbKeyShortcuts.some((shortcut, idx) => { 107 | if (e.key == shortcut) { 108 | const note = this._kbOctaveShift * 12 + idx; 109 | const state = this._noteStates[note]; 110 | if (state.keyboardState) 111 | return; 112 | 113 | if (!state.mouseState && !state.midiState) 114 | this._noteOn(note, 0.8); 115 | 116 | this._noteStates[note].keyboardState = true; 117 | this._updateKeyboardState(note); 118 | 119 | return true; 120 | } 121 | }); 122 | }); 123 | 124 | document.addEventListener('keyup', e => { 125 | if (this._editor.isFocused()) 126 | return; 127 | 128 | this._kbKeyShortcuts.some((shortcut, idx) => { 129 | if (e.key == shortcut) { 130 | const note = this._kbOctaveShift * 12 + idx; 131 | const state = this._noteStates[note]; 132 | 133 | if (!state.mouseState && !state.midiState) 134 | this._noteOff(note, 0.8); 135 | 136 | this._noteStates[note].keyboardState = false; 137 | this._updateKeyboardState(note); 138 | 139 | return true; 140 | } 141 | }); 142 | }); 143 | } 144 | 145 | _setupEditor() { 146 | this._editor = ace.edit("editor"); 147 | ace.config.set('basePath', 'https://pagecdn.io/lib/ace/1.4.12/') 148 | this._editor.setKeyboardHandler("ace/keyboard/sublime"); 149 | this._editor.commands.addCommand({ 150 | name: 'reload', 151 | bindKey: { win: 'Ctrl-S', mac: 'Command-S' }, 152 | exec: (editor) => this._reloadSfz(), 153 | readOnly: true, // false if this command should not apply in readOnly mode 154 | }); 155 | } 156 | 157 | _setupWebMidi() { 158 | if (navigator.requestMIDIAccess) { 159 | navigator.requestMIDIAccess({ name: "midi", sysex: false }) 160 | .then((midiAccess) => { 161 | var devicesNames = []; 162 | midiAccess.inputs.forEach((input, idx) => { 163 | input.onmidimessage = this._dispatchMIDIMessage.bind(this); 164 | devicesNames.push(input.name); 165 | }) 166 | 167 | if (devicesNames.length > 0) { 168 | document.getElementById("connected-devices").textContent = 169 | 'Connected devices: ' + devicesNames.join(', '); 170 | } 171 | }, () => console.log("MIDI connection failure")); 172 | } else { 173 | console.log('WebMIDI is not supported in this browser.'); 174 | } 175 | } 176 | 177 | _noteNumberToLabel(note) { 178 | const octave = Math.floor(note / 12 - 1); 179 | const noteNames = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"]; 180 | const noteIdx = (note % 12); 181 | return noteNames[noteIdx] + octave; 182 | } 183 | 184 | _labelToNoteNumber(label) { 185 | const noteNames = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"]; 186 | const octave = parseInt(label.length > 3 ? label.slice(-2) : label.slice(-1)); 187 | const noteName = label.slice(0, -1); 188 | const noteIdx = noteNames.findIndex((x) => noteName == x); 189 | if (noteIdx < 0 || octave < -1) 190 | return -1; 191 | 192 | 193 | return (octave + 1) * 12 + noteIdx; 194 | } 195 | 196 | _noteOn(note, velocity) { 197 | this._post({ type: 'note_on', number: note, value: velocity }); 198 | const id = this._noteNumberToLabel(note); 199 | var key = document.getElementById(id); 200 | if (key) 201 | key.style.backgroundColor = "steelblue"; 202 | }; 203 | 204 | _noteOff(note) { 205 | this._post({ type: 'note_off', number: note, value: 0.0 }); 206 | const id = this._noteNumberToLabel(note); 207 | var key = document.getElementById(id); 208 | if (key) 209 | key.style.backgroundColor = key.classList.contains('white') ? "white" : "black"; 210 | }; 211 | 212 | _dispatchMIDIMessage(message) { 213 | // console.log('Midi message:', message); 214 | var command = message.data[0]; 215 | var number = message.data[1]; 216 | var value = (message.data.length > 2) ? message.data[2] / 127.0 : 0; // a velocity value might not be included with a noteOff command 217 | 218 | switch (command) { 219 | case 144: { // noteOn 220 | const state = this._noteStates[number]; 221 | if (value > 0) { 222 | if (!state.mouseState && !state.keyboardState) 223 | this._noteOn(number, value); 224 | 225 | this._noteStates[number].midiState = true; 226 | } else { 227 | if (!state.mouseState && !state.keyboardState) 228 | this._noteOff(number, value); 229 | 230 | this._noteStates[number].midiState = false; 231 | } 232 | this._updateKeyboardState(number); 233 | } 234 | break; 235 | case 128: { // noteOff 236 | const state = this._noteStates[number]; 237 | if (!state.mouseState && !state.keyboardState) 238 | this._noteOff(number, value); 239 | 240 | this._noteStates[number].midiState = false; 241 | this._updateKeyboardState(number); 242 | } 243 | break; 244 | case 176: // cc 245 | this._post({ type: 'cc', number: number, value: value }); 246 | var element = this._sliders[number].element; 247 | if (element != undefined) 248 | element.slider('set value', value, false); 249 | 250 | var input = this._sliders[number].input; 251 | if (input != undefined) 252 | input.val(parseFloat(value.toFixed(4))); 253 | 254 | break; 255 | case 208: // cc 256 | this._post({ type: 'aftertouch', value: value }); 257 | break; 258 | case 224: // pitch wheel 259 | var pitch = (((message.data[2] << 7) + message.data[1]) - 8192); 260 | pitch = Math.max(-8191, Math.min(8191, pitch)) / 8191.0; 261 | this._post({ type: 'pitch_wheel', value: pitch }); 262 | break; 263 | } 264 | } 265 | 266 | _onMessage(message) { 267 | if (message.data.numRegions != null) 268 | this._numRegions.textContent = message.data.numRegions; 269 | 270 | if (message.data.activeVoices != null) 271 | this._activeVoices.textContent = message.data.activeVoices; 272 | 273 | if (message.data.cc != null) { 274 | var slider = this._sliders[message.data.cc]; 275 | slider.used = true; 276 | if (message.data.label != '') 277 | slider.label = message.data.label; 278 | else 279 | slider.label = 'CC ' + message.data.cc; 280 | 281 | slider.default = message.data.default; 282 | 283 | 284 | const sliderId = 'slider-' + message.data.cc; 285 | const inputId = 'slider-input-' + message.data.cc; 286 | var newSlider = document.createElement('div'); 287 | newSlider.innerHTML = `${slider.label}
` 288 | this._leftControlColumn.appendChild(newSlider); 289 | 290 | var jqSlider = $('#' + sliderId).attr('class', 'ui slider'); 291 | var jqInput = $('#' + inputId); 292 | jqSlider.slider({ 293 | min: 0, max: 1, step: 0, 294 | onMove: (element, value) => { 295 | this._post({ type: 'cc', number: message.data.cc, value: value }); 296 | jqInput.val(parseFloat(value.toFixed(4))); 297 | } 298 | }) 299 | .slider('set value', message.data.value) 300 | .on('dblclick', () => jqSlider.slider('set value', message.data.default )); 301 | jqInput.on('change', () => { 302 | const floatVal = parseFloat(jqInput.val()); 303 | if (floatVal != NaN && floatVal >= 0 && floatVal <= 1) { 304 | jqSlider.slider('set value', floatVal) 305 | } else { 306 | jqInput.val(jqSlider.slider('get value')); 307 | } 308 | }); 309 | 310 | slider.element = jqSlider; 311 | slider.input = jqInput; 312 | } 313 | } 314 | 315 | async _initializeAudio() { 316 | this._context = new AudioContext(); 317 | this._context.audioWorklet.addModule('./sfizz-processor.js').then(() => { 318 | this._synthNode = new SfizzNode(this._context); 319 | this._volumeNode = new GainNode(this._context, { gain: 0.25 }); 320 | this._synthNode.connect(this._volumeNode) 321 | .connect(this._context.destination); 322 | this._synthNode.port.onmessage = this._onMessage.bind(this); 323 | 324 | this._overlay.style.display = "none"; 325 | this._reloadButton.classList.remove('disabled'); 326 | this._context.resume(); 327 | this._reloadSfz(); 328 | }); 329 | } 330 | 331 | _post(message) { 332 | this._synthNode.port.postMessage(message); 333 | } 334 | 335 | _updateKeyboardState(note) { 336 | const state = this._noteStates[note]; 337 | if (!state.element) 338 | return; 339 | 340 | if (!state.midiState && !state.mouseState && !state.keyboardState) { 341 | if (state.whiteKey == true) 342 | this._noteStates[note].element.style.backgroundColor = 'white'; 343 | else if (state.whiteKey == false) 344 | this._noteStates[note].element.style.backgroundColor = 'black'; 345 | } else { 346 | this._noteStates[note].element.style.backgroundColor = 'steelblue'; 347 | } 348 | } 349 | 350 | _updateKeyboardLabels() { 351 | const start = this._kbOctaveShift * 12; 352 | const stop = this._kbOctaveShift * 12 + this._kbKeyShortcuts.length; 353 | this._noteStates.forEach((e, idx) => { 354 | if (!e.element) 355 | return; 356 | 357 | if (idx < start || idx >= stop) { 358 | e.element.textContent = ""; 359 | } else { 360 | e.element.textContent = this._kbKeyShortcuts[idx - start]; 361 | } 362 | 363 | if (e.keyboardState && !e.midiState && !e.mouseState) 364 | this._noteOff(idx); 365 | 366 | e.keyboardState = false; 367 | this._updateKeyboardState(idx); 368 | }); 369 | } 370 | 371 | _buildKeyboard() { 372 | document.getElementById('keyboard-prototype').hidden = false; 373 | var whitePrototype = document.getElementById('white-prototype'); 374 | var blackPrototype = document.getElementById('black-prototype'); 375 | const whiteKeyWidth = whitePrototype.offsetWidth; 376 | const blackKeyWidth = blackPrototype.offsetWidth; 377 | const whiteKeyHeight = whitePrototype.offsetHeight; 378 | const blackKeyHeight = blackPrototype.offsetHeight; 379 | document.getElementById('keyboard-prototype').hidden = true; 380 | const keyNames = ['c', 'd', 'e', 'f', 'g', 'a', 'b']; 381 | const octaveWidth = keyNames.length * whiteKeyWidth; 382 | const blackOffset = whiteKeyWidth - blackKeyWidth / 2; 383 | const blackPositions = [0, 1, 3, 4, 5]; 384 | var whiteCount = 0; 385 | 386 | var makeKey = (pos, id, cls) => { 387 | var key = document.createElement('button'); 388 | key.className = cls; 389 | key.style = 'left: ' + pos + 'px;'; 390 | key.id = id; 391 | const note = this._labelToNoteNumber(id); 392 | this._noteStates[note].element = key; 393 | 394 | if (cls == 'white') { 395 | whiteCount += 1; 396 | this._noteStates[note].whiteKey = true; 397 | } else { 398 | this._noteStates[note].whiteKey = false; 399 | } 400 | 401 | if (note >= 0) { 402 | const keyHeight = (cls == 'white') ? whiteKeyHeight : blackKeyHeight; 403 | key.addEventListener('mousedown', (e) => { 404 | const state = this._noteStates[note]; 405 | if (!state.midiState 406 | && !state.keyboardState) 407 | this._noteOn(note, e.offsetY / keyHeight); 408 | 409 | this._noteStates[note].mouseState = true; 410 | this._updateKeyboardState(note); 411 | }); 412 | key.addEventListener('mouseup', () => { 413 | const state = this._noteStates[note]; 414 | if (!state.midiState && !state.keyboardState) 415 | this._noteOff(note); 416 | 417 | this._noteStates[note].mouseState = false; 418 | this._updateKeyboardState(note); 419 | }); 420 | key.addEventListener('mouseleave', () => { 421 | const state = this._noteStates[note]; 422 | if (state.mouseState && !state.midiState && !state.keyboardState) 423 | this._noteOff(note); 424 | 425 | this._noteStates[note].mouseState = false; 426 | this._updateKeyboardState(note); 427 | }); 428 | key.addEventListener('mouseenter', (e) => { 429 | const state = this._noteStates[note]; 430 | if ((e.buttons & 1)) { 431 | if (!state.midiState && !state.keyboardState) 432 | this._noteOn(note, e.offsetY / keyHeight); 433 | 434 | this._noteStates[note].mouseState = true; 435 | } 436 | this._updateKeyboardState(note); 437 | }); 438 | } 439 | this._keyboard.appendChild(key); 440 | }; 441 | 442 | var makeOctave = (shift, octaveNumber) => { 443 | keyNames.forEach((keyName, idx) => 444 | makeKey(shift + idx * whiteKeyWidth, keyName + octaveNumber, 'white')); 445 | blackPositions.forEach((whiteIdx) => 446 | makeKey(shift + whiteIdx * whiteKeyWidth + blackOffset, keyNames[whiteIdx] + 's' + octaveNumber, 'black')); 447 | }; 448 | 449 | makeKey(0, 'a0', 'white'); 450 | makeKey(whiteKeyWidth, 'b0', 'white'); 451 | makeKey(blackOffset, 'as0', 'black'); 452 | for (let i = 1; i < 8; i++) 453 | makeOctave((i - 1) * octaveWidth + 2 * whiteKeyWidth, i); 454 | makeKey(7 * octaveWidth + 2 * whiteKeyWidth, 'c8', 'white'); 455 | this._keyboard.lastChild.style.borderRight = 'solid 1px'; 456 | const keyBoardWidth = whiteCount * whiteKeyWidth; 457 | this._keyboard.style.width = keyBoardWidth.toString() + 'px'; // resize the keyboard to center it 458 | this._keyboardButtons.style.width = keyBoardWidth.toString() + 'px'; 459 | this._updateKeyboardLabels(); 460 | } 461 | 462 | onWindowLoad() { 463 | this._initializeView(); 464 | this._buildKeyboard(); 465 | this._setupEditor(); 466 | document.body.addEventListener('click', () => { 467 | this._initializeAudio(); 468 | this._setupKeyCaptures(); 469 | this._setupWebMidi(); 470 | }, { once: true }); 471 | } 472 | } 473 | 474 | const sfizzApp = new SfizzApp(); 475 | window.addEventListener('load', () => sfizzApp.onWindowLoad()); 476 | -------------------------------------------------------------------------------- /ace/mode-sfz.js: -------------------------------------------------------------------------------- 1 | define("ace/mode/sfz_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module){/* ***** BEGIN LICENSE BLOCK ***** 2 | * Distributed under the BSD license: 3 | * 4 | * Copyright (c) 2012, Ajax.org B.V. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Ajax.org B.V. nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * ***** END LICENSE BLOCK ***** */ 30 | "use strict"; 31 | var oop = require("../lib/oop"); 32 | var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; 33 | var SFZHighlightRules = function () { 34 | this.$rules = { 35 | start: [{ 36 | include: "#comment" 37 | }, { 38 | include: "#headers" 39 | }, { 40 | include: "#sfz1_sound-source" 41 | }, { 42 | include: "#sfz1_instrument-settings" 43 | }, { 44 | include: "#sfz1_region-logic" 45 | }, { 46 | include: "#sfz1_performance-parameters" 47 | }, { 48 | include: "#sfz1_modulation" 49 | }, { 50 | include: "#sfz1_effects" 51 | }, { 52 | include: "#sfz2_directives" 53 | }, { 54 | include: "#sfz2_sound-source" 55 | }, { 56 | include: "#sfz2_instrument-settings" 57 | }, { 58 | include: "#sfz2_region-logic" 59 | }, { 60 | include: "#sfz2_performance-parameters" 61 | }, { 62 | include: "#sfz2_modulation" 63 | }, { 64 | include: "#sfz2_curves" 65 | }, { 66 | include: "#aria_instrument-settings" 67 | }, { 68 | include: "#aria_region-logic" 69 | }, { 70 | include: "#aria_performance-parameters" 71 | }, { 72 | include: "#aria_modulation" 73 | }, { 74 | include: "#aria_curves" 75 | }, { 76 | include: "#aria_effects" 77 | }], 78 | "#comment": [{ 79 | token: "punctuation.definition.comment.sfz", 80 | regex: /\/\*/, 81 | push: [{ 82 | token: "punctuation.definition.comment.sfz", 83 | regex: /\*\//, 84 | next: "pop" 85 | }, { 86 | defaultToken: "comment.block.sfz" 87 | }] 88 | }, { 89 | token: [ 90 | "punctuation.whitespace.comment.leading.sfz", 91 | "punctuation.definition.comment.sfz" 92 | ], 93 | regex: /((?:[\s]+)?)(\/\/)(?:\s*(?=\s|$))?/, 94 | push: [{ 95 | token: "comment.line.double-slash.sfz", 96 | regex: /(?=$)/, 97 | next: "pop" 98 | }, { 99 | defaultToken: "comment.line.double-slash.sfz" 100 | }] 101 | }], 102 | "#headers": [{ 103 | token: [ 104 | "punctuation.definition.tag.begin.sfz", 105 | "keyword.control.$2.sfz", 106 | "punctuation.definition.tag.begin.sfz" 107 | ], 108 | regex: /(<)(control|global|master|group|region|curve|effect|midi)(>)/, 109 | comment: "Headers" 110 | }, { 111 | token: "invalid.sfz", 112 | regex: /<.*(?!(?:control|global|master|group|region|curve|effect|midi))>/, 113 | comment: "Non-compliant headers" 114 | }], 115 | "#sfz1_sound-source": [{ 116 | token: [ 117 | "variable.language.sound-source.$1.sfz", 118 | "keyword.operator.assignment.sfz" 119 | ], 120 | regex: /\b(sample)(=?)/, 121 | push: [{ 122 | token: "meta.opcode.sfz", 123 | regex: /(?=(?:\s\/\/|$))/, 124 | next: "pop" 125 | }, { 126 | defaultToken: "meta.opcode.sfz" 127 | }], 128 | comment: "opcodes: (sample): (any string)" 129 | }, { 130 | token: "variable.language.sound-source.$1.sfz", 131 | regex: /\bdelay(?:_random|_oncc\d{1,3})?\b/, 132 | push: [{ 133 | token: "meta.opcode.sfz", 134 | regex: /\s|$/, 135 | next: "pop" 136 | }, { 137 | include: "#float_0-100" 138 | }, { 139 | defaultToken: "meta.opcode.sfz" 140 | }], 141 | comment: "opcodes: (delay|delay_random|delay_onccN): (0 to 100 percent)" 142 | }, { 143 | token: "variable.language.sound-source.$1.sfz", 144 | regex: /\boffset(?:_random|_oncc\d{1,3})?\b/, 145 | push: [{ 146 | token: "meta.opcode.sfz", 147 | regex: /\s|$/, 148 | next: "pop" 149 | }, { 150 | include: "#int_positive" 151 | }, { 152 | defaultToken: "meta.opcode.sfz" 153 | }], 154 | comment: "opcodes: (offset|offset_random|offset_onccN): (0 to 4294967296 sample units)" 155 | }, { 156 | token: "variable.language.sound-source.$1.sfz", 157 | regex: /\bend\b/, 158 | push: [{ 159 | token: "meta.opcode.sfz", 160 | regex: /\s|$/, 161 | next: "pop" 162 | }, { 163 | include: "#int_positive_or_neg1" 164 | }, { 165 | defaultToken: "meta.opcode.sfz" 166 | }], 167 | comment: "opcodes: (end): (-1 to 4294967296 sample units)" 168 | }, { 169 | token: "variable.language.sound-source.$1.sfz", 170 | regex: /\bcount\b/, 171 | push: [{ 172 | token: "meta.opcode.sfz", 173 | regex: /\s|$/, 174 | next: "pop" 175 | }, { 176 | include: "#int_positive" 177 | }, { 178 | defaultToken: "meta.opcode.sfz" 179 | }], 180 | comment: "opcodes: (count): (0 to 4294967296 loops)" 181 | }, { 182 | token: "variable.language.sound-source.$1.sfz", 183 | regex: /\bloop_mode\b/, 184 | push: [{ 185 | token: "meta.opcode.sfz", 186 | regex: /\s|$/, 187 | next: "pop" 188 | }, { 189 | include: "#string_loop_mode" 190 | }, { 191 | defaultToken: "meta.opcode.sfz" 192 | }], 193 | comment: "opcodes: (loop_mode): (no_loop|one_shot|loop_continuous|loop_sustain)" 194 | }, { 195 | token: "variable.language.sound-source.$1.sfz", 196 | regex: /\b(?:loop_start|loop_end)\b/, 197 | push: [{ 198 | token: "meta.opcode.sfz", 199 | regex: /\s|$/, 200 | next: "pop" 201 | }, { 202 | include: "#int_positive" 203 | }, { 204 | defaultToken: "meta.opcode.sfz" 205 | }], 206 | comment: "opcodes: (loop_start|loop_end): (0 to 4294967296 sample units)" 207 | }, { 208 | token: "variable.language.sound-source.$1.sfz", 209 | regex: /\b(?:sync_beats|sync_offset)\b/, 210 | push: [{ 211 | token: "meta.opcode.sfz", 212 | regex: /\s|$/, 213 | next: "pop" 214 | }, { 215 | include: "#float_0-32" 216 | }, { 217 | defaultToken: "meta.opcode.sfz" 218 | }], 219 | comment: "opcodes: (sync_beats|sync_offset): (0 to 32 beats)" 220 | }], 221 | "#sfz1_instrument-settings": [{ 222 | token: "variable.language.instrument-settings.$1.sfz", 223 | regex: /\b(?:group|polyphony_group|off_by)\b/, 224 | push: [{ 225 | token: "meta.opcode.sfz", 226 | regex: /\s|$/, 227 | next: "pop" 228 | }, { 229 | include: "#int_positive" 230 | }, { 231 | defaultToken: "meta.opcode.sfz" 232 | }], 233 | comment: "opcodes: (group|polyphony_group|off_by): (0 to 4294967296 sample units)" 234 | }, { 235 | token: "variable.language.instrument-settings.$1.sfz", 236 | regex: /\boff_mode\b/, 237 | push: [{ 238 | token: "meta.opcode.sfz", 239 | regex: /\s|$/, 240 | next: "pop" 241 | }, { 242 | include: "#string_fast-normal-time" 243 | }, { 244 | defaultToken: "meta.opcode.sfz" 245 | }], 246 | comment: "opcodes: (off_mode): (fast|normal)" 247 | }, { 248 | token: "variable.language.instrument-settings.$1.sfz", 249 | regex: /\boutput\b/, 250 | push: [{ 251 | token: "meta.opcode.sfz", 252 | regex: /\s|$/, 253 | next: "pop" 254 | }, { 255 | include: "#int_0-1024" 256 | }, { 257 | defaultToken: "meta.opcode.sfz" 258 | }], 259 | comment: "opcodes: (output): (0 to 1024 MIDI Nodes)" 260 | }], 261 | "#sfz1_region-logic": [{ 262 | token: "variable.language.region-logic.key-mapping.$1.sfz", 263 | regex: /\b(?:key|lokey|hikey)\b/, 264 | push: [{ 265 | token: "meta.opcode.sfz", 266 | regex: /\s|$/, 267 | next: "pop" 268 | }, { 269 | include: "#int_0-127_or_string_note" 270 | }, { 271 | defaultToken: "meta.opcode.sfz" 272 | }], 273 | comment: "opcodes: (key|lokey|hikey): (0 to 127 MIDI Note or C-1 to G#9 Note)" 274 | }, { 275 | token: "variable.language.region-logic.key-mapping.$1.sfz", 276 | regex: /\b(?:lovel|hivel)\b/, 277 | push: [{ 278 | token: "meta.opcode.sfz", 279 | regex: /\s|$/, 280 | next: "pop" 281 | }, { 282 | include: "#int_0-127" 283 | }, { 284 | defaultToken: "meta.opcode.sfz" 285 | }], 286 | comment: "opcodes: (love|hivel): (0 to 127 MIDI Velocity)" 287 | }, { 288 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 289 | regex: /\b(?:lochan|hichan)\b/, 290 | push: [{ 291 | token: "meta.opcode.sfz", 292 | regex: /\s|$/, 293 | next: "pop" 294 | }, { 295 | include: "#int_1-16" 296 | }, { 297 | defaultToken: "meta.opcode.sfz" 298 | }], 299 | comment: "opcodes: (lochan|hichan): (1 to 16 MIDI Channel)" 300 | }, { 301 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 302 | regex: /\b(?:lo|hi)cc(?:\d{1,3})?\b/, 303 | push: [{ 304 | token: "meta.opcode.sfz", 305 | regex: /\s|$/, 306 | next: "pop" 307 | }, { 308 | include: "#int_0-127" 309 | }, { 310 | defaultToken: "meta.opcode.sfz" 311 | }], 312 | comment: "opcodes: (loccN|hiccN): (0 to 127 MIDI Controller)" 313 | }, { 314 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 315 | regex: /\b(?:lobend|hibend)\b/, 316 | push: [{ 317 | token: "meta.opcode.sfz", 318 | regex: /\s|$/, 319 | next: "pop" 320 | }, { 321 | include: "#int_neg8192-8192" 322 | }, { 323 | defaultToken: "meta.opcode.sfz" 324 | }], 325 | comment: "opcodes: (lobend|hibend): (-8192 to 8192 cents)" 326 | }, { 327 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 328 | regex: /\bsw_(?:lokey|hikey|last|down|up|previous)\b/, 329 | push: [{ 330 | token: "meta.opcode.sfz", 331 | regex: /\s|$/, 332 | next: "pop" 333 | }, { 334 | include: "#int_0-127_or_string_note" 335 | }, { 336 | defaultToken: "meta.opcode.sfz" 337 | }], 338 | comment: "opcodes: (sw_lokey|sw_hikey|sw_last|sw_down|sw_up|sw_previous): (0 to 127 MIDI Note or C-1 to G#9 Note)" 339 | }, { 340 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 341 | regex: /\bsw_vel\b/, 342 | push: [{ 343 | token: "meta.opcode.sfz", 344 | regex: /\s|$/, 345 | next: "pop" 346 | }, { 347 | include: "#string_current-previous" 348 | }, { 349 | defaultToken: "meta.opcode.sfz" 350 | }], 351 | comment: "opcodes: (sw_vel): (current|previous)" 352 | }, { 353 | token: "variable.language.region-logic.internal-conditions.$1.sfz", 354 | regex: /\b(?:lobpm|hibpm)\b/, 355 | push: [{ 356 | token: "meta.opcode.sfz", 357 | regex: /\s|$/, 358 | next: "pop" 359 | }, { 360 | include: "#float_0-500" 361 | }, { 362 | defaultToken: "meta.opcode.sfz" 363 | }], 364 | comment: "opcodes: (lobpm|hibpm): (0 to 500 BPM)" 365 | }, { 366 | token: "variable.language.region-logic.internal-conditions.$1.sfz", 367 | regex: /\b(?:lochanaft|hichanaft|lopolyaft|hipolyaft)\b/, 368 | push: [{ 369 | token: "meta.opcode.sfz", 370 | regex: /\s|$/, 371 | next: "pop" 372 | }, { 373 | include: "#int_0-127" 374 | }, { 375 | defaultToken: "meta.opcode.sfz" 376 | }], 377 | comment: "opcodes: (lochanaft|hichanaft|lopolyaft|hipolyaft): (0 to 127 MIDI Controller)" 378 | }, { 379 | token: "variable.language.region-logic.internal-conditions.$1.sfz", 380 | regex: /\b(?:lorand|hirand)\b/, 381 | push: [{ 382 | token: "meta.opcode.sfz", 383 | regex: /\s|$/, 384 | next: "pop" 385 | }, { 386 | include: "#float_0-1" 387 | }, { 388 | defaultToken: "meta.opcode.sfz" 389 | }], 390 | comment: "opcodes: (lorand|hirand): (0 to 1 float)" 391 | }, { 392 | token: "variable.language.region-logic.internal-conditions.$1.sfz", 393 | regex: /\b(?:seq_length|seq_position)\b/, 394 | push: [{ 395 | token: "meta.opcode.sfz", 396 | regex: /\s|$/, 397 | next: "pop" 398 | }, { 399 | include: "#int_1-100" 400 | }, { 401 | defaultToken: "meta.opcode.sfz" 402 | }], 403 | comment: "opcodes: (seq_length|seq_position): (1 to 100 beats)" 404 | }, { 405 | token: "variable.language.region-logic.triggers.$1.sfz", 406 | regex: /\btrigger\b/, 407 | push: [{ 408 | token: "meta.opcode.sfz", 409 | regex: /\s|$/, 410 | next: "pop" 411 | }, { 412 | include: "#string_attack-release-first-legato" 413 | }, { 414 | defaultToken: "meta.opcode.sfz" 415 | }], 416 | comment: "opcodes: (trigger): (attack|release|first|legato)" 417 | }, { 418 | token: "variable.language.region-logic.triggers.$1.sfz", 419 | regex: /\bon_(?:lo|hi)cc(?:\d{1,3})?\b/, 420 | push: [{ 421 | token: "meta.opcode.sfz", 422 | regex: /\s|$/, 423 | next: "pop" 424 | }, { 425 | include: "#int_neg1-127" 426 | }, { 427 | defaultToken: "meta.opcode.sfz" 428 | }], 429 | comment: "opcodes: (on_loccN|on_hiccN): (-1 to 127 MIDI Controller)" 430 | }], 431 | "#sfz1_performance-parameters": [{ 432 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 433 | regex: /\b(?:pan|position|width|amp_veltrack)\b/, 434 | push: [{ 435 | token: "meta.opcode.sfz", 436 | regex: /\s|$/, 437 | next: "pop" 438 | }, { 439 | include: "#float_neg100-100" 440 | }, { 441 | defaultToken: "meta.opcode.sfz" 442 | }], 443 | comment: "opcodes: (pan|position|width|amp_veltrack): (-100 to 100 percent)" 444 | }, { 445 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 446 | regex: /\bvolume\b/, 447 | push: [{ 448 | token: "meta.opcode.sfz", 449 | regex: /\s|$/, 450 | next: "pop" 451 | }, { 452 | include: "#float_neg144-6" 453 | }, { 454 | defaultToken: "meta.opcode.sfz" 455 | }], 456 | comment: "opcodes: (volume): (-144 to 6 dB)" 457 | }, { 458 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 459 | regex: /\bamp_keycenter\b/, 460 | push: [{ 461 | token: "meta.opcode.sfz", 462 | regex: /\s|$/, 463 | next: "pop" 464 | }, { 465 | include: "#int_0-127_or_string_note" 466 | }, { 467 | defaultToken: "meta.opcode.sfz" 468 | }], 469 | comment: "opcodes: (amp_keycenter): (0 to 127 MIDI Note or C-1 to G#9 Note)" 470 | }, { 471 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 472 | regex: /\bamp_keytrack\b/, 473 | push: [{ 474 | token: "meta.opcode.sfz", 475 | regex: /\s|$/, 476 | next: "pop" 477 | }, { 478 | include: "#float_neg96-12" 479 | }, { 480 | defaultToken: "meta.opcode.sfz" 481 | }], 482 | comment: "opcodes: (amp_keytrack): (-96 to 12 dB)" 483 | }, { 484 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 485 | regex: /\bamp_velcurve_(?:\d{1,3})?\b/, 486 | push: [{ 487 | token: "meta.opcode.sfz", 488 | regex: /\s|$/, 489 | next: "pop" 490 | }, { 491 | include: "#float_0-1" 492 | }, { 493 | defaultToken: "meta.opcode.sfz" 494 | }], 495 | comment: "opcodes: (amp_velcurve_N): (0 to 1 curve)" 496 | }, { 497 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 498 | regex: /\bamp_random\b/, 499 | push: [{ 500 | token: "meta.opcode.sfz", 501 | regex: /\s|$/, 502 | next: "pop" 503 | }, { 504 | include: "#float_0-24" 505 | }, { 506 | defaultToken: "meta.opcode.sfz" 507 | }], 508 | comment: "opcodes: (amp_random): (0 to 24 dB)" 509 | }, { 510 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 511 | regex: /\bgain_oncc(?:\d{1,3})?\b/, 512 | push: [{ 513 | token: "meta.opcode.sfz", 514 | regex: /\s|$/, 515 | next: "pop" 516 | }, { 517 | include: "#float_neg144-48" 518 | }, { 519 | defaultToken: "meta.opcode.sfz" 520 | }], 521 | comment: "opcodes: (gain_onccN): (-144 to 48 dB)" 522 | }, { 523 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 524 | regex: /\brt_decay\b/, 525 | push: [{ 526 | token: "meta.opcode.sfz", 527 | regex: /\s|$/, 528 | next: "pop" 529 | }, { 530 | include: "#float_0-200" 531 | }, { 532 | defaultToken: "meta.opcode.sfz" 533 | }], 534 | comment: "opcodes: (rt_decay): (0 to 200 dB)" 535 | }, { 536 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 537 | regex: /\b(?:xf_cccurve|xf_keycurve|xf_velcurve)\b/, 538 | push: [{ 539 | token: "meta.opcode.sfz", 540 | regex: /\s|$/, 541 | next: "pop" 542 | }, { 543 | include: "#string_gain-power" 544 | }, { 545 | defaultToken: "meta.opcode.sfz" 546 | }], 547 | comment: "opcodes: (xf_cccurve|xf_keycurve|xf_velcurve): (gain|power)" 548 | }, { 549 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 550 | regex: /\b(?:xfin_locc(?:\d{1,3})?|xfin_hicc(?:\d{1,3})?|xfout_locc(?:\d{1,3})?|xfout_hicc(?:\d{1,3})?|xfin_lokey|xfin_hikey|xfout_lokey|xfout_hikey|xfin_lovel|xfin_hivel|xfout_lovel|xfout_hivel)\b/, 551 | push: [{ 552 | token: "meta.opcode.sfz", 553 | regex: /\s|$/, 554 | next: "pop" 555 | }, { 556 | include: "#int_0-127" 557 | }, { 558 | defaultToken: "meta.opcode.sfz" 559 | }], 560 | comment: "opcodes: (xfin_loccN|xfin_hiccN|xfout_loccN|xfout_hiccN|xfin_lokey|xfin_hikey|xfout_lokey|xfout_hikey|xfin_lovel|xfin_hivel|xfout_lovel|xfout_hivel): (0 to 127 MIDI Velocity)" 561 | }, { 562 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 563 | regex: /\b(?:xfin_lokey|xfin_hikey|xfout_lokey|xfout_hikey)\b/, 564 | push: [{ 565 | token: "meta.opcode.sfz", 566 | regex: /\s|$/, 567 | next: "pop" 568 | }, { 569 | include: "#int_0-127_or_string_note" 570 | }, { 571 | defaultToken: "meta.opcode.sfz" 572 | }], 573 | comment: "opcodes: (xfin_lokey|xfin_hikey|xfout_lokey|xfout_hikey): (0 to 127 MIDI Note or C-1 to G#9 Note)" 574 | }, { 575 | token: "variable.language.performance-parameters.pitch.$1.sfz", 576 | regex: /\b(?:bend_up|bend_down|pitch_veltrack)\b/, 577 | push: [{ 578 | token: "meta.opcode.sfz", 579 | regex: /\s|$/, 580 | next: "pop" 581 | }, { 582 | include: "#int_neg9600-9600" 583 | }, { 584 | defaultToken: "meta.opcode.sfz" 585 | }], 586 | comment: "opcodes: (bend_up|bend_down|pitch_veltrack): (-9600 to 9600 cents)" 587 | }, { 588 | token: "variable.language.performance-parameters.pitch.$1.sfz", 589 | regex: /\bbend_step\b/, 590 | push: [{ 591 | token: "meta.opcode.sfz", 592 | regex: /\s|$/, 593 | next: "pop" 594 | }, { 595 | include: "#int_1-1200" 596 | }, { 597 | defaultToken: "meta.opcode.sfz" 598 | }], 599 | comment: "opcodes: (bend_step): (1 to 1200 cents)" 600 | }, { 601 | token: "variable.language.performance-parameters.pitch.$1.sfz", 602 | regex: /\bpitch_keycenter\b/, 603 | push: [{ 604 | token: "meta.opcode.sfz", 605 | regex: /\s|$/, 606 | next: "pop" 607 | }, { 608 | include: "#int_0-127_or_string_note" 609 | }, { 610 | defaultToken: "meta.opcode.sfz" 611 | }], 612 | comment: "opcodes: (pitch_keycenter): (0 to 127 MIDI Note)" 613 | }, { 614 | token: "variable.language.performance-parameters.pitch.$1.sfz", 615 | regex: /\bpitch_keytrack\b/, 616 | push: [{ 617 | token: "meta.opcode.sfz", 618 | regex: /\s|$/, 619 | next: "pop" 620 | }, { 621 | include: "#int_neg1200-1200" 622 | }, { 623 | defaultToken: "meta.opcode.sfz" 624 | }], 625 | comment: "opcodes: (pitch_keytrack): (-1200 to 1200 cents)" 626 | }, { 627 | token: "variable.language.performance-parameters.pitch.$1.sfz", 628 | regex: /\bpitch_random\b/, 629 | push: [{ 630 | token: "meta.opcode.sfz", 631 | regex: /\s|$/, 632 | next: "pop" 633 | }, { 634 | include: "#int_0-9600" 635 | }, { 636 | defaultToken: "meta.opcode.sfz" 637 | }], 638 | comment: "opcodes: (pitch_random): (0 to 9600 cents)" 639 | }, { 640 | token: "variable.language.performance-parameters.pitch.$1.sfz", 641 | regex: /\btranspose\b/, 642 | push: [{ 643 | token: "meta.opcode.sfz", 644 | regex: /\s|$/, 645 | next: "pop" 646 | }, { 647 | include: "#int_neg127-127" 648 | }, { 649 | defaultToken: "meta.opcode.sfz" 650 | }], 651 | comment: "opcodes: (transpose): (-127 to 127 MIDI Note)" 652 | }, { 653 | token: "variable.language.performance-parameters.pitch.$1.sfz", 654 | regex: /\btune\b/, 655 | push: [{ 656 | token: "meta.opcode.sfz", 657 | regex: /\s|$/, 658 | next: "pop" 659 | }, { 660 | include: "#int_neg9600-9600" 661 | }, { 662 | defaultToken: "meta.opcode.sfz" 663 | }], 664 | comment: "opcodes: (tune): (-2400 to 2400 cents)" 665 | }, { 666 | token: "variable.language.performance-parameters.filters.$1.sfz", 667 | regex: /\bcutoff\b/, 668 | push: [{ 669 | token: "meta.opcode.sfz", 670 | regex: /\s|$/, 671 | next: "pop" 672 | }, { 673 | include: "#float_positive" 674 | }, { 675 | defaultToken: "meta.opcode.sfz" 676 | }], 677 | comment: "opcodes: (cutoff): (0 to arbitrary Hz)" 678 | }, { 679 | token: "variable.language.performance-parameters.filters.$1.sfz", 680 | regex: /\b(?:cutoff_oncc(?:\d{1,3})?|cutoff_chanaft|cutoff_polyaft)\b/, 681 | push: [{ 682 | token: "meta.opcode.sfz", 683 | regex: /\s|$/, 684 | next: "pop" 685 | }, { 686 | include: "#int_neg9600-9600" 687 | }, { 688 | defaultToken: "meta.opcode.sfz" 689 | }], 690 | comment: "opcodes: (cutoff_onccN|cutoff_chanaft|cutoff_polyaft): (-9600 to 9600 cents)" 691 | }, { 692 | token: "variable.language.performance-parameters.filters.$1.sfz", 693 | regex: /\bfil_keytrack\b/, 694 | push: [{ 695 | token: "meta.opcode.sfz", 696 | regex: /\s|$/, 697 | next: "pop" 698 | }, { 699 | include: "#int_0-1200" 700 | }, { 701 | defaultToken: "meta.opcode.sfz" 702 | }], 703 | comment: "opcodes: (fil_keytrack): (0 to 1200 cents)" 704 | }, { 705 | token: "variable.language.performance-parameters.filters.$1.sfz", 706 | regex: /\bfil_keycenter\b/, 707 | push: [{ 708 | token: "meta.opcode.sfz", 709 | regex: /\s|$/, 710 | next: "pop" 711 | }, { 712 | include: "#int_0-127_or_string_note" 713 | }, { 714 | defaultToken: "meta.opcode.sfz" 715 | }], 716 | comment: "opcodes: (fil_keycenter): (0 to 127 MIDI Note)" 717 | }, { 718 | token: "variable.language.performance-parameters.filters.$1.sfz", 719 | regex: /\bfil_random\b/, 720 | push: [{ 721 | token: "meta.opcode.sfz", 722 | regex: /\s|$/, 723 | next: "pop" 724 | }, { 725 | include: "#int_0-9600" 726 | }, { 727 | defaultToken: "meta.opcode.sfz" 728 | }], 729 | comment: "opcodes: (fil_random): (0 to 9600 cents)" 730 | }, { 731 | token: "variable.language.performance-parameters.filters.$1.sfz", 732 | regex: /\bfil_type\b/, 733 | push: [{ 734 | token: "meta.opcode.sfz", 735 | regex: /\s|$/, 736 | next: "pop" 737 | }, { 738 | include: "#string_lpf-hpf-bpf-brf" 739 | }, { 740 | defaultToken: "meta.opcode.sfz" 741 | }], 742 | comment: "opcodes: (fil_type): (lpf_1p|hpf_1p|lpf_2p|hpf_2p|bpf_2p|brf_2p)" 743 | }, { 744 | token: "variable.language.performance-parameters.filters.$1.sfz", 745 | regex: /\bfil_veltrack\b/, 746 | push: [{ 747 | token: "meta.opcode.sfz", 748 | regex: /\s|$/, 749 | next: "pop" 750 | }, { 751 | include: "#int_neg9600-9600" 752 | }, { 753 | defaultToken: "meta.opcode.sfz" 754 | }], 755 | comment: "opcodes: (fil_veltrack): (-9600 to 9600 cents)" 756 | }, { 757 | token: "variable.language.performance-parameters.filters.$1.sfz", 758 | regex: /\bresonance\b/, 759 | push: [{ 760 | token: "meta.opcode.sfz", 761 | regex: /\s|$/, 762 | next: "pop" 763 | }, { 764 | include: "#float_0-40" 765 | }, { 766 | defaultToken: "meta.opcode.sfz" 767 | }], 768 | comment: "opcodes: (resonance): (0 to 40 dB)" 769 | }, { 770 | token: "variable.language.performance-parameters.eq.$1.sfz", 771 | regex: /\b(?:eq1_freq|eq2_freq|eq3_freq)\b/, 772 | push: [{ 773 | token: "meta.opcode.sfz", 774 | regex: /\s|$/, 775 | next: "pop" 776 | }, { 777 | include: "#float_0-30000" 778 | }, { 779 | defaultToken: "meta.opcode.sfz" 780 | }], 781 | comment: "opcodes: (eq1_freq|eq2_freq|eq3_freq): (0 to 30000 Hz)" 782 | }, { 783 | token: "variable.language.performance-parameters.eq.$1.sfz", 784 | regex: /\b(?:eq[1-3]_freq_oncc(?:\d{1,3})?|eq1_vel2freq|eq2_vel2freq|eq3_vel2freq)\b/, 785 | push: [{ 786 | token: "meta.opcode.sfz", 787 | regex: /\s|$/, 788 | next: "pop" 789 | }, { 790 | include: "#float_neg30000-30000" 791 | }, { 792 | defaultToken: "meta.opcode.sfz" 793 | }], 794 | comment: "opcodes: (eq1_freq_onccN|eq2_freq_onccN|eq3_freq_onccN|eq1_vel2freq|eq2_vel2freq|eq3_vel2freq): (-30000 to 30000 Hz)" 795 | }, { 796 | token: "variable.language.performance-parameters.eq.$1.sfz", 797 | regex: /\b(?:eq1_bw|eq2_bw|eq3_bw)\b/, 798 | push: [{ 799 | token: "meta.opcode.sfz", 800 | regex: /\s|$/, 801 | next: "pop" 802 | }, { 803 | include: "#float_0-4" 804 | }, { 805 | defaultToken: "meta.opcode.sfz" 806 | }], 807 | comment: "opcodes: (eq1_bw|eq2_bw|eq3_bw): (0.0001 to 4 octaves)" 808 | }, { 809 | token: "variable.language.performance-parameters.eq.$1.sfz", 810 | regex: /\b(?:eq[1-3]_bw_oncc(?:\d{1,3})?|eq1_vel2bw|eq2_vel2bw|eq3_vel2bw)\b/, 811 | push: [{ 812 | token: "meta.opcode.sfz", 813 | regex: /\s|$/, 814 | next: "pop" 815 | }, { 816 | include: "#float_neg4-4" 817 | }, { 818 | defaultToken: "meta.opcode.sfz" 819 | }], 820 | comment: "opcodes: (eq1_bw_onccN|eq2_bw_onccN|eq3_bw_onccN|eq1_vel2bw|eq2_vel2bw|eq3_vel2bw): (-30000 to 30000 Hz)" 821 | }, { 822 | token: "variable.language.performance-parameters.eq.$1.sfz", 823 | regex: /\b(?:eq[1-3]_(?:vel2)?gain|eq[1-3]_gain_oncc(?:\d{1,3})?)\b/, 824 | push: [{ 825 | token: "meta.opcode.sfz", 826 | regex: /\s|$/, 827 | next: "pop" 828 | }, { 829 | include: "#float_neg96-24" 830 | }, { 831 | defaultToken: "meta.opcode.sfz" 832 | }], 833 | comment: "opcodes: (eq1_gain|eq2_gain|eq3_gain|eq1_gain_onccN|eq2_gain_onccN|eq3_gain_onccN|eq1_vel2gain|eq2_vel2gain|eq3_vel2gain): (-96 to 24 dB)" 834 | }], 835 | "#sfz1_modulation": [{ 836 | token: "variable.language.modulation.envelope-generators.$1.sfz", 837 | regex: /\b(?:ampeg|fileg|pitcheg)_(?:(?:attack|decay|delay|hold|release|start|sustain)(?:_oncc(?:\d{1,3})?)?|vel2(?:attack|decay|delay|hold|release|start|sustain))\b/, 838 | push: [{ 839 | token: "meta.opcode.sfz", 840 | regex: /\s|$/, 841 | next: "pop" 842 | }, { 843 | include: "#float_0-100" 844 | }, { 845 | defaultToken: "meta.opcode.sfz" 846 | }], 847 | comment: "opcodes: (ampeg_delay_onccN|ampeg_attack_onccN|ampeg_hold_onccN|ampeg_decay_onccN|ampeg_release_onccN|ampeg_vel2delay|ampeg_vel2attack|ampeg_vel2hold|ampeg_vel2decay|ampeg_vel2release|pitcheg_vel2delay|pitcheg_vel2attack|pitcheg_vel2hold|pitcheg_vel2decay|pitcheg_vel2release|fileg_vel2delay|fileg_vel2attack|fileg_vel2hold|fileg_vel2decay|fileg_vel2release): (0 to 100 seconds)" 848 | }, { 849 | token: "variable.language.modulation.envelope-generators.$1.sfz", 850 | regex: /\b(?:pitcheg_depth|fileg_depth|pitcheg_vel2depth|fileg_vel2depth)\b/, 851 | push: [{ 852 | token: "meta.opcode.sfz", 853 | regex: /\s|$/, 854 | next: "pop" 855 | }, { 856 | include: "#int_neg12000-12000" 857 | }, { 858 | defaultToken: "meta.opcode.sfz" 859 | }], 860 | comment: "opcodes: (pitcheg_depth|fileg_depth|pitcheg_vel2depth|fileg_vel2depth): (-12000 to 12000 cents)" 861 | }, { 862 | token: "variable.language.modulation.lfo.$1.sfz", 863 | regex: /\bamplfo_(?:depth(?:cc(?:\d{1,3})?)?|depth(?:chan|poly)aft)\b/, 864 | push: [{ 865 | token: "meta.opcode.sfz", 866 | regex: /\s|$/, 867 | next: "pop" 868 | }, { 869 | include: "#float_neg20-20" 870 | }, { 871 | defaultToken: "meta.opcode.sfz" 872 | }], 873 | comment: "opcodes: (amplfo_depth|amplfo_depthccN|amplfo_depthchanaft|amplfo_depthpolyaft): (-20 to 20 dB)" 874 | }, { 875 | token: "variable.language.modulation.lfo.$1.sfz", 876 | regex: /\b(?:fillfo|pitchlfo)_(?:depth(?:(?:_on)?cc(?:\d{1,3})?)?|depth(?:chan|poly)aft)\b/, 877 | push: [{ 878 | token: "meta.opcode.sfz", 879 | regex: /\s|$/, 880 | next: "pop" 881 | }, { 882 | include: "#int_neg1200-1200" 883 | }, { 884 | defaultToken: "meta.opcode.sfz" 885 | }], 886 | comment: "opcodes: (pitchlfo_depth|pitchlfo_depthccN|pitchlfo_depthchanaft|pitchlfo_depthpolyaft|fillfo_depth|fillfo_depthccN|fillfo_depthchanaft|fillfo_depthpolyaft): (-1200 to 1200 cents)" 887 | }, { 888 | token: "variable.language.modulation.lfo.$1.sfz", 889 | regex: /\b(?:(?:amplfo|fillfo|pitchlfo)_(?:freq|(?:cc(?:\d{1,3})?)?)|freq(?:chan|poly)aft)\b/, 890 | push: [{ 891 | token: "meta.opcode.sfz", 892 | regex: /\s|$/, 893 | next: "pop" 894 | }, { 895 | include: "#float_neg200-200" 896 | }, { 897 | defaultToken: "meta.opcode.sfz" 898 | }], 899 | comment: "opcodes: (amplfo_freqccN|amplfo_freqchanaft|amplfo_freqpolyaft|pitchlfo_freqccN|pitchlfo_freqchanaft|pitchlfo_freqpolyaft|fillfo_freqccN|fillfo_freqchanaft|fillfo_freqpolyaft): (-200 to 200 Hz)" 900 | }, { 901 | token: "variable.language.modulation.lfo.$1.sfz", 902 | regex: /\b(?:amplfo|fillfo|pitchlfo)_(?:delay|fade)\b/, 903 | push: [{ 904 | token: "meta.opcode.sfz", 905 | regex: /\s|$/, 906 | next: "pop" 907 | }, { 908 | include: "#float_0-100" 909 | }, { 910 | defaultToken: "meta.opcode.sfz" 911 | }], 912 | comment: "opcodes: (amplfo_delay|amplfo_fade|pitchlfo_delay|pitchlfo_fade|fillfo_delay|fillfo_fade): (0 to 100 seconds)" 913 | }, { 914 | token: "variable.language.modulation.lfo.$1.sfz", 915 | regex: /\b(?:amplfo_freq|pitchlfo_freq|fillfo_freq)\b/, 916 | push: [{ 917 | token: "meta.opcode.sfz", 918 | regex: /\s|$/, 919 | next: "pop" 920 | }, { 921 | include: "#float_0-20" 922 | }, { 923 | defaultToken: "meta.opcode.sfz" 924 | }], 925 | comment: "opcodes: (amplfo_freq|pitchlfo_freq|fillfo_freq): (0 to 20 Hz)" 926 | }], 927 | "#sfz1_effects": [{ 928 | token: "variable.language.effects.$1.sfz", 929 | regex: /\b(?:effect1|effect2)\b/, 930 | push: [{ 931 | token: "meta.opcode.sfz", 932 | regex: /\s|$/, 933 | next: "pop" 934 | }, { 935 | include: "#float_0-100" 936 | }, { 937 | defaultToken: "meta.opcode.sfz" 938 | }], 939 | comment: "opcodes: (effect1|effect2): (0 to 100 percent)" 940 | }], 941 | "#sfz2_directives": [{ 942 | token: [ 943 | "meta.preprocessor.define.sfz", 944 | "meta.generic.define.sfz", 945 | "punctuation.definition.variable.sfz", 946 | "meta.preprocessor.string.sfz", 947 | "meta.generic.define.sfz", 948 | "meta.preprocessor.string.sfz" 949 | ], 950 | regex: /(\#define)(\s+)(\$)([^\s]+)(\s+)(.+)\b/, 951 | comment: "#define statement" 952 | }, { 953 | token: [ 954 | "meta.preprocessor.import.sfz", 955 | "meta.generic.include.sfz", 956 | "punctuation.definition.string.begin.sfz", 957 | "meta.preprocessor.string.sfz", 958 | "meta.preprocessor.string.sfz", 959 | "punctuation.definition.string.end.sfz" 960 | ], 961 | regex: /(\#include)(\s+)(")(.+)(?=\.sfz)(\.sfzh?)(")/, 962 | comment: "#include statement" 963 | }, { 964 | token: "variable.other.constant.sfz", 965 | regex: /\$[^\s\=]+/, 966 | comment: "defined variable" 967 | }], 968 | "#sfz2_sound-source": [{ 969 | token: [ 970 | "variable.language.sound-source.$1.sfz", 971 | "keyword.operator.assignment.sfz" 972 | ], 973 | regex: /\b(default_path)(=?)/, 974 | push: [{ 975 | token: "meta.opcode.sfz", 976 | regex: /(?=(?:\s\/\/|$))/, 977 | next: "pop" 978 | }, { 979 | defaultToken: "meta.opcode.sfz" 980 | }], 981 | comment: "opcodes: (default_path): any string" 982 | }, { 983 | token: "variable.language.sound-source.sample-playback.$1.sfz", 984 | regex: /\bdirection\b/, 985 | push: [{ 986 | token: "meta.opcode.sfz", 987 | regex: /\s|$/, 988 | next: "pop" 989 | }, { 990 | include: "#string_forward-reverse" 991 | }, { 992 | defaultToken: "meta.opcode.sfz" 993 | }], 994 | comment: "opcodes: (direction): (forward|reverse)" 995 | }, { 996 | token: "variable.language.sound-source.sample-playback.$1.sfz", 997 | regex: /\bloop_count\b/, 998 | push: [{ 999 | token: "meta.opcode.sfz", 1000 | regex: /\s|$/, 1001 | next: "pop" 1002 | }, { 1003 | include: "#int_positive" 1004 | }, { 1005 | defaultToken: "meta.opcode.sfz" 1006 | }], 1007 | comment: "opcodes: (loop_count): (0 to 4294967296 loops)" 1008 | }, { 1009 | token: "variable.language.sound-source.sample-playback.$1.sfz", 1010 | regex: /\bloop_type\b/, 1011 | push: [{ 1012 | token: "meta.opcode.sfz", 1013 | regex: /\s|$/, 1014 | next: "pop" 1015 | }, { 1016 | include: "#string_forward-backward-alternate" 1017 | }, { 1018 | defaultToken: "meta.opcode.sfz" 1019 | }], 1020 | comment: "opcodes: (loop_type): (forward|backward|alternate)" 1021 | }, { 1022 | token: "variable.language.sound-source.sample-playback.$1.sfz", 1023 | regex: /\bmd5\b/, 1024 | push: [{ 1025 | token: "meta.opcode.sfz", 1026 | regex: /\s|$/, 1027 | next: "pop" 1028 | }, { 1029 | include: "#string_md5" 1030 | }, { 1031 | defaultToken: "meta.opcode.sfz" 1032 | }], 1033 | comment: "opcodes: (md5): (128-bit hex md5 hash)" 1034 | }], 1035 | "#sfz2_instrument-settings": [{ 1036 | token: "variable.language.instrument-settings.$1.sfz", 1037 | regex: /\boctave_offset\b/, 1038 | push: [{ 1039 | token: "meta.opcode.sfz", 1040 | regex: /\s|$/, 1041 | next: "pop" 1042 | }, { 1043 | include: "#int_neg10-10" 1044 | }, { 1045 | defaultToken: "meta.opcode.sfz" 1046 | }], 1047 | comment: "opcodes: (octave_offset): (-10 to 10 octaves)" 1048 | }, { 1049 | token: [ 1050 | "variable.language.instrument-settings.$1.sfz", 1051 | "keyword.operator.assignment.sfz" 1052 | ], 1053 | regex: /\b(region_label|label_cc(?:\d{1,3})?)(=?)/, 1054 | push: [{ 1055 | token: "meta.opcode.sfz", 1056 | regex: /(?=(?:\s\/\/|$))/, 1057 | next: "pop" 1058 | }, { 1059 | defaultToken: "meta.opcode.sfz" 1060 | }], 1061 | comment: "opcodes: (region_label|label_ccN): (any string)" 1062 | }, { 1063 | token: "variable.language.instrument-settings.$1.sfz", 1064 | regex: /\bset_cc(?:\d{1,3})?\b/, 1065 | push: [{ 1066 | token: "meta.opcode.sfz", 1067 | regex: /\s|$/, 1068 | next: "pop" 1069 | }, { 1070 | include: "#int_0-127" 1071 | }, { 1072 | defaultToken: "meta.opcode.sfz" 1073 | }], 1074 | comment: "opcodes: (set_ccN): (0 to 127 MIDI Controller)" 1075 | }, { 1076 | token: "variable.language.instrument-settings.voice-lifecycle.$1.sfz", 1077 | regex: /\b(?:polyphony|note_polyphony)\b/, 1078 | push: [{ 1079 | token: "meta.opcode.sfz", 1080 | regex: /\s|$/, 1081 | next: "pop" 1082 | }, { 1083 | include: "#int_0-127" 1084 | }, { 1085 | defaultToken: "meta.opcode.sfz" 1086 | }], 1087 | comment: "opcodes: (polyphony|note_polyphony): (0 to 127 voices)" 1088 | }, { 1089 | token: "variable.language.instrument-settings.voice-lifecycle.$1.sfz", 1090 | regex: /\b(?:note_selfmask|rt_dead)\b/, 1091 | push: [{ 1092 | token: "meta.opcode.sfz", 1093 | regex: /\s|$/, 1094 | next: "pop" 1095 | }, { 1096 | include: "#string_on-off" 1097 | }, { 1098 | defaultToken: "meta.opcode.sfz" 1099 | }], 1100 | comment: "opcodes: (note_selfmask|rt_dead): (on|off)" 1101 | }], 1102 | "#sfz2_region-logic": [{ 1103 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1104 | regex: /\b(?:sustain_sw|sostenuto_sw)\b/, 1105 | push: [{ 1106 | token: "meta.opcode.sfz", 1107 | regex: /\s|$/, 1108 | next: "pop" 1109 | }, { 1110 | include: "#string_on-off" 1111 | }, { 1112 | defaultToken: "meta.opcode.sfz" 1113 | }], 1114 | comment: "opcodes: (sustain_sw|sostenuto_sw): (on|off)" 1115 | }, { 1116 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1117 | regex: /\b(?:loprog|hiprog)\b/, 1118 | push: [{ 1119 | token: "meta.opcode.sfz", 1120 | regex: /\s|$/, 1121 | next: "pop" 1122 | }, { 1123 | include: "#int_0-127" 1124 | }, { 1125 | defaultToken: "meta.opcode.sfz" 1126 | }], 1127 | comment: "opcodes: (loprog|hiprog): (0 to 127 MIDI program)" 1128 | }], 1129 | "#sfz2_performance-parameters": [{ 1130 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1131 | regex: /\bvolume_oncc(?:\d{1,3})?\b/, 1132 | push: [{ 1133 | token: "meta.opcode.sfz", 1134 | regex: /\s|$/, 1135 | next: "pop" 1136 | }, { 1137 | include: "#float_neg144-6" 1138 | }, { 1139 | defaultToken: "meta.opcode.sfz" 1140 | }], 1141 | comment: "opcodes: (volume_onccN): (-144 to 6 dB)" 1142 | }, { 1143 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1144 | regex: /\bphase\b/, 1145 | push: [{ 1146 | token: "meta.opcode.sfz", 1147 | regex: /\s|$/, 1148 | next: "pop" 1149 | }, { 1150 | include: "#string_normal-invert" 1151 | }, { 1152 | defaultToken: "meta.opcode.sfz" 1153 | }], 1154 | comment: "opcodes: (phase): (normal|invert)" 1155 | }, { 1156 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1157 | regex: /\bwidth_oncc(?:\d{1,3})?\b/, 1158 | push: [{ 1159 | token: "meta.opcode.sfz", 1160 | regex: /\s|$/, 1161 | next: "pop" 1162 | }, { 1163 | include: "#float_neg100-100" 1164 | }, { 1165 | defaultToken: "meta.opcode.sfz" 1166 | }], 1167 | comment: "opcodes: (width_onccN): (-100 to 100 percent)" 1168 | }, { 1169 | token: "variable.language.performance-parameters.pitch.$1.sfz", 1170 | regex: /\bbend_smooth\b/, 1171 | push: [{ 1172 | token: "meta.opcode.sfz", 1173 | regex: /\s|$/, 1174 | next: "pop" 1175 | }, { 1176 | include: "#int_0-9600" 1177 | }, { 1178 | defaultToken: "meta.opcode.sfz" 1179 | }], 1180 | comment: "opcodes: (bend_smooth): (0 to 9600 cents)" 1181 | }, { 1182 | token: "variable.language.performance-parameters.pitch.$1.sfz", 1183 | regex: /\b(?:bend_stepup|bend_stepdown)\b/, 1184 | push: [{ 1185 | token: "meta.opcode.sfz", 1186 | regex: /\s|$/, 1187 | next: "pop" 1188 | }, { 1189 | include: "#int_1-1200" 1190 | }, { 1191 | defaultToken: "meta.opcode.sfz" 1192 | }], 1193 | comment: "opcodes: (bend_stepup|bend_stepdown): (1 to 1200 cents)" 1194 | }, { 1195 | token: "variable.language.performance-parameters.filters.$1.sfz", 1196 | regex: /\b(?:cutoff2|cutoff2_oncc(?:\d{1,3})?)\b/, 1197 | push: [{ 1198 | token: "meta.opcode.sfz", 1199 | regex: /\s|$/, 1200 | next: "pop" 1201 | }, { 1202 | include: "#float_positive" 1203 | }, { 1204 | defaultToken: "meta.opcode.sfz" 1205 | }], 1206 | comment: "opcodes: (cutoff2|cutoff2_onccN): (0 to arbitrary Hz)" 1207 | }, { 1208 | token: "variable.language.performance-parameters.filters.$1.sfz", 1209 | regex: /\b(?:resonance_oncc(?:\d{1,3})?|resonance2|resonance2_oncc(?:\d{1,3})?)\b/, 1210 | push: [{ 1211 | token: "meta.opcode.sfz", 1212 | regex: /\s|$/, 1213 | next: "pop" 1214 | }, { 1215 | include: "#float_0-40" 1216 | }, { 1217 | defaultToken: "meta.opcode.sfz" 1218 | }], 1219 | comment: "opcodes: (resonance_onccN|resonance2|resonance2_onccN): (0 to 40 dB)" 1220 | }, { 1221 | token: "variable.language.performance-parameters.filters.$1.sfz", 1222 | regex: /\bfil2_type\b/, 1223 | push: [{ 1224 | token: "meta.opcode.sfz", 1225 | regex: /\s|$/, 1226 | next: "pop" 1227 | }, { 1228 | include: "#string_lpf-hpf-bpf-brf" 1229 | }, { 1230 | defaultToken: "meta.opcode.sfz" 1231 | }], 1232 | comment: "opcodes: (fil2_type): (lpf_1p|hpf_1p|lpf_2p|hpf_2p|bpf_2p|brf_2p)" 1233 | }], 1234 | "#sfz2_modulation": [{ 1235 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1236 | regex: /\beg\d{2}_(?:curve|loop|points|sustain)\b/, 1237 | push: [{ 1238 | token: "meta.opcode.sfz", 1239 | regex: /\s|$/, 1240 | next: "pop" 1241 | }, { 1242 | include: "#int_positive" 1243 | }, { 1244 | defaultToken: "meta.opcode.sfz" 1245 | }], 1246 | comment: "opcodes: (egN_(curve|loop|points|sustain)): (positive int)" 1247 | }, { 1248 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1249 | regex: /\beg\d{2}_level\d*(?:_oncc(?:\d{1,3})?)?\b/, 1250 | push: [{ 1251 | token: "meta.opcode.sfz", 1252 | regex: /\s|$/, 1253 | next: "pop" 1254 | }, { 1255 | include: "#float_neg1-1" 1256 | }, { 1257 | defaultToken: "meta.opcode.sfz" 1258 | }], 1259 | comment: "opcodes: (egN_level|egN_level_onccX): (-1 to 1 float)" 1260 | }, { 1261 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1262 | regex: /\beg\d{2}_shape\d+\b/, 1263 | push: [{ 1264 | token: "meta.opcode.sfz", 1265 | regex: /\s|$/, 1266 | next: "pop" 1267 | }, { 1268 | include: "#float_neg10-10" 1269 | }, { 1270 | defaultToken: "meta.opcode.sfz" 1271 | }], 1272 | comment: "opcodes: (egN_shapeX): (-10 to 10 number)" 1273 | }, { 1274 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1275 | regex: /\beg\d{2}_time\d*(?:_oncc(?:\d{1,3})?)?\b/, 1276 | push: [{ 1277 | token: "meta.opcode.sfz", 1278 | regex: /\s|$/, 1279 | next: "pop" 1280 | }, { 1281 | include: "#float_0-100" 1282 | }, { 1283 | defaultToken: "meta.opcode.sfz" 1284 | }], 1285 | comment: "opcodes: (egN_time|egN_time_onccX): (0 to 100 seconds)" 1286 | }, { 1287 | token: "variable.language.modulation.lfo.$1.sfz", 1288 | regex: /\blfo\d{2}_(?:wave|count|freq_(?:smooth|step)cc(?:\d{1,3})?)\b/, 1289 | push: [{ 1290 | token: "meta.opcode.sfz", 1291 | regex: /\s|$/, 1292 | next: "pop" 1293 | }, { 1294 | include: "#int_positive" 1295 | }, { 1296 | defaultToken: "meta.opcode.sfz" 1297 | }], 1298 | comment: "opcodes: (lfoN_wave|lfoN_count|lfoN_freq|lfoN_freq_onccX|lfoN_freq_smoothccX): (positive int)" 1299 | }, { 1300 | token: "variable.language.modulation.lfo.$1.sfz", 1301 | regex: /\blfo\d{2}_freq(?:_oncc(?:\d{1,3})?)?\b/, 1302 | push: [{ 1303 | token: "meta.opcode.sfz", 1304 | regex: /\s|$/, 1305 | next: "pop" 1306 | }, { 1307 | include: "#float_neg20-20" 1308 | }, { 1309 | defaultToken: "meta.opcode.sfz" 1310 | }], 1311 | comment: "opcodes: (lfoN_freq|lfoN_freq_onccN): (-20 to 20 Hz)" 1312 | }, { 1313 | token: "variable.language.modulation.lfo.$1.sfz", 1314 | regex: /\b(?:lfo\d{2}_(?:delay|fade)(?:_oncc(?:\d{1,3})?)?|count)\b/, 1315 | push: [{ 1316 | token: "meta.opcode.sfz", 1317 | regex: /\s|$/, 1318 | next: "pop" 1319 | }, { 1320 | include: "#float_0-100" 1321 | }, { 1322 | defaultToken: "meta.opcode.sfz" 1323 | }], 1324 | comment: "opcodes: (lfoN_delay|lfoN_delay_onccX|lfoN_fade|lfoN_fade_onccX): (0 to 100 seconds)" 1325 | }, { 1326 | token: "variable.language.modulation.lfo.$1.sfz", 1327 | regex: /\b(?:lfo\d{2}_phase(?:_oncc(?:\d{1,3})?)?|count)\b/, 1328 | push: [{ 1329 | token: "meta.opcode.sfz", 1330 | regex: /\s|$/, 1331 | next: "pop" 1332 | }, { 1333 | include: "#float_0-1" 1334 | }, { 1335 | defaultToken: "meta.opcode.sfz" 1336 | }], 1337 | comment: "opcodes: (lfoN_phase|lfoN_phase_onccX): (0 to 1 number)" 1338 | }, { 1339 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1340 | regex: /\beg\d{2}_(?:(?:depth_lfo|depthadd_lfo|freq_lfo)|(?:amplitude|depth|depth_lfo|depthadd_lfo|freq_lfo|pitch|cutoff2?|eq[1-3]freq|eq[1-3]bw|eq[1-3]gain|pan|resonance2?|volume|width)(?:_oncc(?:\d{1,3})?)?)\b/, 1341 | push: [{ 1342 | token: "meta.opcode.sfz", 1343 | regex: /\s|$/, 1344 | next: "pop" 1345 | }, { 1346 | include: "#float_any" 1347 | }, { 1348 | defaultToken: "meta.opcode.sfz" 1349 | }], 1350 | comment: "opcodes: (other eg destinations): (any float)" 1351 | }, { 1352 | token: "variable.language.modulation.lfo.$1.sfz", 1353 | regex: /\blfo\d{2}_(?:(?:depth_lfo|depthadd_lfo|freq_lfo)|(?:amplitude|decim|bitred|depth_lfo|depthadd_lfo|freq_lfo|pitch|cutoff2?|eq[1-3]freq|eq[1-3]bw|eq[1-3]gain|pan|resonance2?|volume|width)(?:_oncc(?:\d{1,3})?)?)\b/, 1354 | push: [{ 1355 | token: "meta.opcode.sfz", 1356 | regex: /\s|$/, 1357 | next: "pop" 1358 | }, { 1359 | include: "#float_any" 1360 | }, { 1361 | defaultToken: "meta.opcode.sfz" 1362 | }], 1363 | comment: "opcodes: (other lfo destinations): (any float)" 1364 | }], 1365 | "#sfz2_curves": [{ 1366 | token: "variable.language.curves.$1.sfz", 1367 | regex: /\bv[0-9]{3}\b/, 1368 | push: [{ 1369 | token: "meta.opcode.sfz", 1370 | regex: /\s|$/, 1371 | next: "pop" 1372 | }, { 1373 | include: "#float_0-1" 1374 | }, { 1375 | defaultToken: "meta.opcode.sfz" 1376 | }], 1377 | comment: "opcodes: (vN): (0 to 1 number)" 1378 | }], 1379 | "#aria_instrument-settings": [{ 1380 | token: "variable.language.instrument-settings.$1.sfz", 1381 | regex: /\bhint_[A-z_]*\b/, 1382 | push: [{ 1383 | token: "meta.opcode.sfz", 1384 | regex: /\s|$/, 1385 | next: "pop" 1386 | }, { 1387 | include: "#float_any" 1388 | }, { 1389 | defaultToken: "meta.opcode.sfz" 1390 | }], 1391 | comment: "opcodes: (hint_): (any number)" 1392 | }, { 1393 | token: "variable.language.instrument-settings.$1.sfz", 1394 | regex: /\b(?:set_|lo|hi)hdcc(?:\d{1,3})?\b/, 1395 | push: [{ 1396 | token: "meta.opcode.sfz", 1397 | regex: /\s|$/, 1398 | next: "pop" 1399 | }, { 1400 | include: "#float_any" 1401 | }, { 1402 | defaultToken: "meta.opcode.sfz" 1403 | }], 1404 | comment: "opcodes: (set_hdccN|lohdccN|hihdccN): (any number)" 1405 | }, { 1406 | token: "variable.language.instrument-settings.$1.sfz", 1407 | regex: /\b(?:sustain_cc|sostenuto_cc|sustain_lo|sostenuto_lo)\b/, 1408 | push: [{ 1409 | token: "meta.opcode.sfz", 1410 | regex: /\s|$/, 1411 | next: "pop" 1412 | }, { 1413 | include: "#int_0-127" 1414 | }, { 1415 | defaultToken: "meta.opcode.sfz" 1416 | }], 1417 | comment: "opcodes: (sustain_cc|sostenuto_cc|sustain_lo|sostenuto_lo): (0 to 127 MIDI byte)" 1418 | }, { 1419 | token: "variable.language.instrument-settings.$1.sfz", 1420 | regex: /\bsw_octave_offset\b/, 1421 | push: [{ 1422 | token: "meta.opcode.sfz", 1423 | regex: /\s|$/, 1424 | next: "pop" 1425 | }, { 1426 | include: "#int_neg10-10" 1427 | }, { 1428 | defaultToken: "meta.opcode.sfz" 1429 | }], 1430 | comment: "opcodes: (sw_octave_offset): (-10 to 10 octaves)" 1431 | }, { 1432 | token: "variable.language.instrument-settings.voice-lifecycle.$1.sfz", 1433 | regex: /\boff_curve\b/, 1434 | push: [{ 1435 | token: "meta.opcode.sfz", 1436 | regex: /\s|$/, 1437 | next: "pop" 1438 | }, { 1439 | include: "#int_positive" 1440 | }, { 1441 | defaultToken: "meta.opcode.sfz" 1442 | }], 1443 | comment: "opcodes: (off_curve): (0 to any curve)" 1444 | }, { 1445 | token: "variable.language.instrument-settings.voice-lifecycle.$1.sfz", 1446 | regex: /\b(?:off_shape|off_time)\b/, 1447 | push: [{ 1448 | token: "meta.opcode.sfz", 1449 | regex: /\s|$/, 1450 | next: "pop" 1451 | }, { 1452 | include: "#float_neg10-10" 1453 | }, { 1454 | defaultToken: "meta.opcode.sfz" 1455 | }], 1456 | comment: "opcodes: (off_shape|off_time): (-10 to 10 number)" 1457 | }], 1458 | "#aria_region-logic": [{ 1459 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1460 | regex: /\b(?:sw_default|sw_lolast|sw_hilast)\b/, 1461 | push: [{ 1462 | token: "meta.opcode.sfz", 1463 | regex: /\s|$/, 1464 | next: "pop" 1465 | }, { 1466 | include: "#int_0-127" 1467 | }, { 1468 | defaultToken: "meta.opcode.sfz" 1469 | }], 1470 | comment: "opcodes: (sw_default|sw_lolast|sw_hilast): (0 to 127 MIDI Note)" 1471 | }, { 1472 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1473 | regex: /\bsw_label\b/, 1474 | push: [{ 1475 | token: "meta.opcode.sfz", 1476 | regex: /\s|$/, 1477 | next: "pop" 1478 | }, { 1479 | include: "#string_any_continuous" 1480 | }, { 1481 | defaultToken: "meta.opcode.sfz" 1482 | }], 1483 | comment: "opcodes: (sw_label): (any string)" 1484 | }, { 1485 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1486 | regex: /\bvar\d{2}_curvecc(?:\d{1,3})?\b/, 1487 | push: [{ 1488 | token: "meta.opcode.sfz", 1489 | regex: /\s|$/, 1490 | next: "pop" 1491 | }, { 1492 | include: "#int_positive" 1493 | }, { 1494 | defaultToken: "meta.opcode.sfz" 1495 | }], 1496 | comment: "opcodes: (varNN_curveccX): (0 to any curve)" 1497 | }, { 1498 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1499 | regex: /\bvar\d{2}_mod\b/, 1500 | push: [{ 1501 | token: "meta.opcode.sfz", 1502 | regex: /\s|$/, 1503 | next: "pop" 1504 | }, { 1505 | include: "#string_add-mult" 1506 | }, { 1507 | defaultToken: "meta.opcode.sfz" 1508 | }], 1509 | comment: "opcodes: (varNN_mod): (add|mult)" 1510 | }, { 1511 | token: "variable.language.region-logic.midi-conditions.$1.sfz", 1512 | regex: /\b(?:var\d{2}_oncc(?:\d{1,3})?|var\d{2}_(?:pitch|cutoff|resonance|cutoff2|resonance2|eq[1-3]freq|eq[1-3]bw|eq[1-3]gain|volume|amplitude|pan|width))\b/, 1513 | push: [{ 1514 | token: "meta.opcode.sfz", 1515 | regex: /\s|$/, 1516 | next: "pop" 1517 | }, { 1518 | include: "#float_any" 1519 | }, { 1520 | defaultToken: "meta.opcode.sfz" 1521 | }], 1522 | comment: "opcodes: (varNN_onccX|varNN_target): (any float)" 1523 | }], 1524 | "#aria_performance-parameters": [{ 1525 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1526 | regex: /\b(?:amplitude|amplitude_oncc(?:\d{1,3})?|global_amplitude|master_amplitude|group_amplitude)\b/, 1527 | push: [{ 1528 | token: "meta.opcode.sfz", 1529 | regex: /\s|$/, 1530 | next: "pop" 1531 | }, { 1532 | include: "#float_0-100" 1533 | }, { 1534 | defaultToken: "meta.opcode.sfz" 1535 | }], 1536 | comment: "opcodes: (amplitude|amplitude_onccN|global_amplitude|master_amplitude|group_amplitude): (0 to 100 percent)" 1537 | }, { 1538 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1539 | regex: /\bamplitude_curvecc(?:\d{1,3})?\b/, 1540 | push: [{ 1541 | token: "meta.opcode.sfz", 1542 | regex: /\s|$/, 1543 | next: "pop" 1544 | }, { 1545 | include: "#int_positive" 1546 | }, { 1547 | defaultToken: "meta.opcode.sfz" 1548 | }], 1549 | comment: "opcodes: (amplitude_curveccN): (any positive curve)" 1550 | }, { 1551 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1552 | regex: /\bamplitude_smoothcc(?:\d{1,3})?\b/, 1553 | push: [{ 1554 | token: "meta.opcode.sfz", 1555 | regex: /\s|$/, 1556 | next: "pop" 1557 | }, { 1558 | include: "#int_0-9600" 1559 | }, { 1560 | defaultToken: "meta.opcode.sfz" 1561 | }], 1562 | comment: "opcodes: (amplitude_smoothccN): (0 to 9600 number)" 1563 | }, { 1564 | token: "variable.language.performance-parameters.amplifier.$1.sfz", 1565 | regex: /\bpan_law\b/, 1566 | push: [{ 1567 | token: "meta.opcode.sfz", 1568 | regex: /\s|$/, 1569 | next: "pop" 1570 | }, { 1571 | include: "#string_balance-mma" 1572 | }, { 1573 | defaultToken: "meta.opcode.sfz" 1574 | }], 1575 | comment: "opcodes: (pan_law): (balance|mma)" 1576 | }, { 1577 | token: "variable.language.performance-parameters.amplifiers.$1.sfz", 1578 | regex: /\b(?:global_volume|master_volume|group_volume|volume_oncc(?:\d{1,3})?)\b/, 1579 | push: [{ 1580 | token: "meta.opcode.sfz", 1581 | regex: /\s|$/, 1582 | next: "pop" 1583 | }, { 1584 | include: "#float_neg144-6" 1585 | }, { 1586 | defaultToken: "meta.opcode.sfz" 1587 | }], 1588 | comment: "opcodes: (global_volume|master_volume|group_volume|volume_onccN): (-144 to 6 dB)" 1589 | }], 1590 | "#aria_modulation": [{ 1591 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1592 | regex: /\b(?:ampeg_attack_shape|ampeg_decay_shape|ampeg_release_shape|eg\d{2}_shape\d+)\b/, 1593 | push: [{ 1594 | token: "meta.opcode.sfz", 1595 | regex: /\s|$/, 1596 | next: "pop" 1597 | }, { 1598 | include: "#float_neg10-10" 1599 | }, { 1600 | defaultToken: "meta.opcode.sfz" 1601 | }], 1602 | comment: "opcodes: (ampeg_attack_shape|ampeg_decay_shape|ampeg_release_shape|egN_shapeX): (-10 to 10 float)" 1603 | }, { 1604 | token: "variable.language.modulation.envelope-generators.$1.sfz", 1605 | regex: /\b(?:ampeg_release_zero|ampeg_decay_zero)\b/, 1606 | push: [{ 1607 | token: "meta.opcode.sfz", 1608 | regex: /\s|$/, 1609 | next: "pop" 1610 | }, { 1611 | include: "#string_on-off" 1612 | }, { 1613 | defaultToken: "meta.opcode.sfz" 1614 | }], 1615 | comment: "opcodes: (ampeg_release_zero|ampeg_decay_zero): (true|false)" 1616 | }, { 1617 | token: "variable.language.modulation.lfo.$1.sfz", 1618 | regex: /\blfo\d{2}_(?:offset|ratio|scale)2?\b/, 1619 | push: [{ 1620 | token: "meta.opcode.sfz", 1621 | regex: /\s|$/, 1622 | next: "pop" 1623 | }, { 1624 | include: "#float_any" 1625 | }, { 1626 | defaultToken: "meta.opcode.sfz" 1627 | }], 1628 | comment: "opcodes: (lfoN_offset|lfoN_offset2|lfoN_ratio|lfoN_ratio2|lfoN_scale|lfoN_scale2): (any float)" 1629 | }, { 1630 | token: "variable.language.modulation.lfo.$1.sfz", 1631 | regex: /\blfo\d{2}_wave2?\b/, 1632 | push: [{ 1633 | token: "meta.opcode.sfz", 1634 | regex: /\s|$/, 1635 | next: "pop" 1636 | }, { 1637 | include: "#int_0-127" 1638 | }, { 1639 | defaultToken: "meta.opcode.sfz" 1640 | }], 1641 | comment: "opcodes: (lfoN_wave|lfoN_wav2): (0 to 127 MIDI Number)" 1642 | }], 1643 | "#aria_curves": [{ 1644 | token: "variable.language.curves.$1.sfz", 1645 | regex: /\bcurve_index\b/, 1646 | push: [{ 1647 | token: "meta.opcode.sfz", 1648 | regex: /\s|$/, 1649 | next: "pop" 1650 | }, { 1651 | include: "#int_positive" 1652 | }, { 1653 | defaultToken: "meta.opcode.sfz" 1654 | }], 1655 | comment: "opcodes: (curve_index): (any positive integer)" 1656 | }], 1657 | "#aria_effects": [{ 1658 | token: "variable.language.effects.$1.sfz", 1659 | regex: /\bparam_offset\b/, 1660 | push: [{ 1661 | token: "meta.opcode.sfz", 1662 | regex: /\s|$/, 1663 | next: "pop" 1664 | }, { 1665 | include: "#int_any" 1666 | }, { 1667 | defaultToken: "meta.opcode.sfz" 1668 | }], 1669 | comment: "opcodes: (param_offset): (any integer)" 1670 | }, { 1671 | token: "variable.language.effects.$1.sfz", 1672 | regex: /\bvendor_specific\b/, 1673 | push: [{ 1674 | token: "meta.opcode.sfz", 1675 | regex: /\s|$/, 1676 | next: "pop" 1677 | }, { 1678 | include: "#string_any_continuous" 1679 | }, { 1680 | defaultToken: "meta.opcode.sfz" 1681 | }], 1682 | comment: "opcodes: (vendor_specific): (any to continuous string)" 1683 | }], 1684 | "#float_neg30000-30000": [{ 1685 | token: [ 1686 | "keyword.operator.assignment.sfz", 1687 | "constant.numeric.float.sfz" 1688 | ], 1689 | regex: /(=)(-?(? indent) 2327 | break; 2328 | var subRange = this.getFoldWidgetRange(session, "all", row); 2329 | if (subRange) { 2330 | if (subRange.start.row <= startRow) { 2331 | break; 2332 | } 2333 | else if (subRange.isMultiLine()) { 2334 | row = subRange.end.row; 2335 | } 2336 | else if (startIndent == indent) { 2337 | break; 2338 | } 2339 | } 2340 | endRow = row; 2341 | } 2342 | return new Range(startRow, startColumn, endRow, session.getLine(endRow).length); 2343 | }; 2344 | this.getCommentRegionBlock = function (session, line, row) { 2345 | var startColumn = line.search(/\s*$/); 2346 | var maxRow = session.getLength(); 2347 | var startRow = row; 2348 | var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/; 2349 | var depth = 1; 2350 | while (++row < maxRow) { 2351 | line = session.getLine(row); 2352 | var m = re.exec(line); 2353 | if (!m) 2354 | continue; 2355 | if (m[1]) 2356 | depth--; 2357 | else 2358 | depth++; 2359 | if (!depth) 2360 | break; 2361 | } 2362 | var endRow = row; 2363 | if (endRow > startRow) { 2364 | return new Range(startRow, startColumn, endRow, line.length); 2365 | } 2366 | }; 2367 | }).call(FoldMode.prototype); 2368 | 2369 | }); 2370 | 2371 | define("ace/mode/sfz",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sfz_highlight_rules","ace/mode/folding/cstyle"], function(require, exports, module){/* ***** BEGIN LICENSE BLOCK ***** 2372 | * Distributed under the BSD license: 2373 | * 2374 | * Copyright (c) 2012, Ajax.org B.V. 2375 | * All rights reserved. 2376 | * 2377 | * Redistribution and use in source and binary forms, with or without 2378 | * modification, are permitted provided that the following conditions are met: 2379 | * * Redistributions of source code must retain the above copyright 2380 | * notice, this list of conditions and the following disclaimer. 2381 | * * Redistributions in binary form must reproduce the above copyright 2382 | * notice, this list of conditions and the following disclaimer in the 2383 | * documentation and/or other materials provided with the distribution. 2384 | * * Neither the name of Ajax.org B.V. nor the 2385 | * names of its contributors may be used to endorse or promote products 2386 | * derived from this software without specific prior written permission. 2387 | * 2388 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 2389 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 2390 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 2391 | * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY 2392 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 2393 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 2394 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 2395 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2396 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2397 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2398 | * 2399 | * ***** END LICENSE BLOCK ***** */ 2400 | "use strict"; 2401 | var oop = require("../lib/oop"); 2402 | var TextMode = require("./text").Mode; 2403 | var SFZHighlightRules = require("./sfz_highlight_rules").SFZHighlightRules; 2404 | var FoldMode = require("./folding/cstyle").FoldMode; 2405 | var Mode = function () { 2406 | this.HighlightRules = SFZHighlightRules; 2407 | this.foldingRules = new FoldMode(); 2408 | }; 2409 | oop.inherits(Mode, TextMode); 2410 | (function () { 2411 | this.$id = "ace/mode/sfz"; 2412 | }).call(Mode.prototype); 2413 | exports.Mode = Mode; 2414 | 2415 | }); (function() { 2416 | window.require(["ace/mode/sfz"], function(m) { 2417 | if (typeof module == "object" && typeof exports == "object" && module) { 2418 | module.exports = m; 2419 | } 2420 | }); 2421 | })(); 2422 | --------------------------------------------------------------------------------