├── omnicolliderpkg ├── JIT │ └── todo.txt └── Static │ ├── Omni_PROTO.sc.nim │ ├── CMakeLists.txt.nim │ └── Omni_PROTO.cpp.nim ├── omnicollider_lang ├── tests │ ├── test3.nim │ ├── nim.cfg │ ├── test1.nim │ └── test2.nim ├── omnicollider_lang.nimble ├── omnicollider_lang.nim └── omnicollider_lang │ ├── lang │ ├── buffers │ │ └── omnicollider_buffers.nim │ └── params │ │ └── omnicollider_params.nim │ └── lib │ └── buffer │ └── omnicollider_buffer.nim ├── .gitignore ├── .gitmodules ├── README.md ├── LICENSE ├── omnicollider.nimble └── omnicollider.nim /omnicolliderpkg/JIT/todo.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /omnicollider_lang/tests/test3.nim: -------------------------------------------------------------------------------- 1 | params: 2 | freq {440, 0.01, 20000} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | 3 | *.so 4 | 5 | omnicollider 6 | 7 | *.exe 8 | -------------------------------------------------------------------------------- /omnicollider_lang/tests/nim.cfg: -------------------------------------------------------------------------------- 1 | define:"omni_perform32" 2 | define:"omni_perform64" 3 | import:"omni_lang" 4 | import:"../omnicollider_lang" -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "omnicolliderpkg/deps/supercollider"] 2 | path = omnicolliderpkg/deps/supercollider 3 | url = https://github.com/supercollider/supercollider 4 | -------------------------------------------------------------------------------- /omnicollider_lang/tests/test1.nim: -------------------------------------------------------------------------------- 1 | omni_debug_macros: 2 | ins 4 3 | 4 | params: 5 | freq 6 | bubu 7 | 8 | sample: 9 | out1 = in1 -------------------------------------------------------------------------------- /omnicollider_lang/tests/test2.nim: -------------------------------------------------------------------------------- 1 | 2 | #omni_debug_macros: 3 | params: 4 | one; two 5 | 6 | buffers: 7 | buf1 "something" 8 | buf2 9 | buf3 "somethingElse" 10 | 11 | init: 12 | discard 13 | 14 | sample: 15 | buf1.samplerate = 2 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **omnicollider** 2 | 3 | Compile [omni](https://github.com/vitreo12/omni) code into [SuperCollider](https://github.com/supercollider/supercollider) `UGens`. 4 | 5 | ## **Requirements** 6 | 7 | 1) [nim](https://nim-lang.org/) 8 | 2) [git](https://git-scm.com/) 9 | 3) [cmake](https://cmake.org/) 10 | 4) [gcc](https://gcc.gnu.org/) (`Linux` and `Windows`) / [clang](https://clang.llvm.org/) (`MacOS`) 11 | 12 | Note that omni only supports nim version 1.6.0. It is recommended to install it via [choosenim](https://github.com/dom96/choosenim). 13 | 14 | ## **Installation** 15 | 16 | To install `omnicollider`, simply use the `nimble` package manager (it comes bundled with the `nim` installation).The command will also take care of installing `omni`: 17 | 18 | nimble install omnicollider -y 19 | 20 | ## **Usage** 21 | 22 | omnicollider ~/.nimble/pkgs/omni-0.4.2/examples/OmniSaw.omni 23 | 24 | ## **Website / Docs** 25 | 26 | Check omni's [website](https://vitreo12.github.io/omni). 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 Francesco Cameli 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 | -------------------------------------------------------------------------------- /omnicollider_lang/omnicollider_lang.nimble: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | version = "0.4.2" 24 | author = "Francesco Cameli" 25 | description = "omnicollider_lang" 26 | license = "MIT" 27 | 28 | requires "nim >= 1.0.0" 29 | requires "omni == 0.4.2" 30 | 31 | installDirs = @["omnicollider_lang"] 32 | -------------------------------------------------------------------------------- /omnicolliderpkg/Static/Omni_PROTO.sc.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | var OMNI_PROTO_SC = """ 24 | Omni_PROTO : UGen { 25 | *kr { 26 | //args 27 | ((this.class).asString.replace("Meta_", "") ++ ": Omni generated UGens do not provide a kr method. Returning 0.").error; 28 | ^(0); 29 | } 30 | *ar { 31 | //args 32 | //rates 33 | //multiNew 34 | } 35 | //multiOut 36 | } 37 | """ 38 | -------------------------------------------------------------------------------- /omnicollider_lang/omnicollider_lang.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | #Import / export omnicollider's params extensions 24 | import omnicollider_lang/lang/params/omnicollider_params 25 | export omnicollider_params 26 | 27 | #Import / export omnicollider's buffers extensions 28 | import omnicollider_lang/lang/buffers/omnicollider_buffers 29 | export omnicollider_buffers 30 | 31 | #Import / export omnicollider's Buffer interface 32 | import omnicollider_lang/lib/buffer/omnicollider_buffer 33 | export omnicollider_buffer -------------------------------------------------------------------------------- /omnicollider.nimble: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | version = "0.4.2" 24 | author = "Francesco Cameli" 25 | description = "SuperCollider wrapper for omni." 26 | license = "MIT" 27 | 28 | requires "nim >= 1.4.0" 29 | requires "cligen >= 1.5.0" 30 | requires "omni == 0.4.2" 31 | 32 | #Ignore omnicollider_lang 33 | skipDirs = @["omnicollider_lang"] 34 | 35 | #Install build/deps 36 | installDirs = @["omnicolliderpkg"] 37 | 38 | #Compiler executable 39 | bin = @["omnicollider"] 40 | 41 | #Make sure omnicollider-lang is getting installed first 42 | before install: 43 | let package_dir = getPkgDir() 44 | 45 | #Update SuperCollider's source files 46 | withDir(package_dir): 47 | echo "Updating the SuperCollider repository..." 48 | exec "git submodule update --init --recursive" 49 | 50 | #Install omnicollider_lang 51 | withDir(package_dir & "/omnicollider_lang"): 52 | exec "nimble install -Y" 53 | 54 | #before / after are BOTH needed for any of the two to work 55 | after install: 56 | discard 57 | -------------------------------------------------------------------------------- /omnicollider_lang/omnicollider_lang/lang/buffers/omnicollider_buffers.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | import macros, strutils 24 | 25 | macro omnicollider_buffers*(ins_number : typed, buffers_number : typed, omni_buffers_names : typed) : untyped = 26 | let buffers_names_val = omni_buffers_names.getImpl() 27 | if buffers_names_val.kind != nnkStrLit: 28 | error "buffers: omnicollider can't retrieve buffers' names." 29 | 30 | let 31 | ins_number_lit = ins_number.intVal() 32 | buffers_number_lit = buffers_number.intVal() 33 | buffers_names_seq = buffers_names_val.strVal().split(',') 34 | 35 | result = nnkStmtList.newTree() 36 | 37 | if buffers_number_lit > 0: 38 | var 39 | perform_block = nnkStmtList.newTree() 40 | omni_unpack_buffers_perform = nnkTemplateDef.newTree( 41 | newIdentNode("omni_unpack_buffers_perform"), 42 | newEmptyNode(), 43 | newEmptyNode(), 44 | nnkFormalParams.newTree( 45 | newIdentNode("untyped") 46 | ), 47 | nnkPragma.newTree( 48 | newIdentNode("dirty") 49 | ), 50 | newEmptyNode(), 51 | perform_block 52 | ) 53 | 54 | result.add( 55 | omni_unpack_buffers_perform 56 | ) 57 | 58 | for index, buffer_name in buffers_names_seq: 59 | let 60 | buffer_name_ident = newIdentNode(buffer_name) 61 | buffer_name_omni_buffer_ident = newIdentNode(buffer_name & "_omni_buffer") 62 | omni_ins_ptr = newIdentNode("omni_ins_ptr") 63 | buffer_index = int(ins_number_lit + index) #shift by ins 64 | 65 | perform_block.add( 66 | nnkLetSection.newTree( 67 | nnkIdentDefs.newTree( 68 | buffer_name_ident, 69 | newEmptyNode(), 70 | nnkDotExpr.newTree( 71 | newIdentNode("omni_ugen"), 72 | buffer_name_omni_buffer_ident 73 | ) 74 | ) 75 | ), 76 | nnkCall.newTree( 77 | newIdentNode("omnicollider_set_input_bufnum_buffer"), 78 | buffer_name_ident, 79 | nnkBracketExpr.newTree( 80 | nnkBracketExpr.newTree( 81 | omni_ins_ptr, 82 | newLit(buffer_index) 83 | ), 84 | newLit(0) 85 | ) 86 | ) 87 | ) 88 | 89 | #error repr result 90 | 91 | template omni_buffers_pre_perform_hook*() : untyped = 92 | omnicollider_buffers(omni_inputs, omni_buffers, omni_buffers_names_const) 93 | -------------------------------------------------------------------------------- /omnicollider_lang/omnicollider_lang/lang/params/omnicollider_params.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | 24 | import macros, strutils 25 | 26 | #overwrite the omni_unpack_params_perform template! 27 | #instead of calling SetParam at each cycle from SC's CPP interface, just replace the template and use the inputs directly instead 28 | macro omnicollider_params*(ins_number : typed, buffers_number : typed, params_number : typed, params_names : typed) : untyped = 29 | let params_names_val = params_names.getImpl() 30 | if params_names_val.kind != nnkStrLit: 31 | error "params: omnicollider can't retrieve params' names." 32 | 33 | let 34 | ins_number_lit = ins_number.intVal() 35 | params_number_lit = params_number.intVal() 36 | buffers_number_lit = buffers_number.intVal() 37 | params_names_seq = params_names_val.strVal().split(',') 38 | 39 | result = nnkStmtList.newTree() 40 | 41 | if params_number_lit > 0: 42 | var 43 | new_omni_unpack_params_body = nnkStmtList.newTree( 44 | nnkLetSection.newTree() 45 | ) 46 | 47 | new_omni_unpack_params_perform = nnkTemplateDef.newTree( 48 | newIdentNode("omni_unpack_params_perform"), 49 | newEmptyNode(), 50 | newEmptyNode(), 51 | nnkFormalParams.newTree( 52 | newIdentNode("untyped") 53 | ), 54 | nnkPragma.newTree( 55 | newIdentNode("dirty") 56 | ), 57 | newEmptyNode(), 58 | new_omni_unpack_params_body 59 | ) 60 | 61 | result.add( 62 | new_omni_unpack_params_perform 63 | ) 64 | 65 | for index, param_name in params_names_seq: 66 | let 67 | param_name_ident = newIdentNode(param_name) 68 | omni_ins_ptr = newIdentNode("omni_ins_ptr") 69 | param_index = int(ins_number_lit + buffers_number_lit + index) #shift by ins + buffers 70 | 71 | let let_stmt_ident_defs = nnkIdentDefs.newTree( 72 | param_name_ident, 73 | newEmptyNode(), 74 | nnkCall.newTree( 75 | newIdentNode("omni_param_" & param_name & "_min_max"), 76 | nnkBracketExpr.newTree( 77 | nnkBracketExpr.newTree( 78 | omni_ins_ptr, 79 | newLit(param_index) 80 | ), 81 | newLit(0) 82 | ) 83 | ) 84 | ) 85 | 86 | new_omni_unpack_params_body[0].add( 87 | let_stmt_ident_defs 88 | ) 89 | 90 | #register the omni_params_post_hook call 91 | template omni_params_pre_perform_hook*() : untyped = 92 | omnicollider_params(omni_inputs, omni_buffers, omni_params, omni_params_names_const) 93 | -------------------------------------------------------------------------------- /omnicollider_lang/omnicollider_lang/lib/buffer/omnicollider_buffer.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | #Explicit import of omni_wrapper for all things needed to declare a new wrapper 24 | import omni_lang/core/wrapper/omni_wrapper 25 | 26 | #All these functions are defined in the SC object cpp file 27 | proc get_buffer_SC(sc_world : pointer, fbufnum : cfloat, print_invalid : cint) : pointer {.importc, cdecl.} 28 | proc get_buffer_data_SC(snd_buf : pointer) : ptr float32 {.importc, cdecl.} 29 | proc get_frames_buffer_SC(snd_buf : pointer) : cint {.importc, cdecl.} 30 | proc get_channels_buffer_SC(snd_buf : pointer) : cint {.importc, cdecl.} 31 | proc get_samplerate_buffer_SC(snd_buf : pointer) : cdouble {.importc, cdecl.} 32 | when defined(supernova): 33 | proc lock_buffer_SC (snd_buf : pointer) : void {.importc, cdecl.} 34 | proc unlock_buffer_SC(snd_buf : pointer) : void {.importc, cdecl.} 35 | 36 | #Template to not duplicate code for either snd_buf or snd_buf_data invalidity 37 | template invalid_buffer() : untyped {.dirty.} = 38 | buffer.bufnum = float32(-1e9) 39 | buffer.print_invalid = 0 40 | return false 41 | 42 | #Create a new Buffer interface for the omnicollider wrapper 43 | omniBufferInterface: 44 | struct: 45 | sc_world : pointer 46 | snd_buf : pointer 47 | snd_buf_data : ptr UncheckedArray[float32] 48 | bufnum : float 49 | input_bufnum : float 50 | print_invalid : int 51 | 52 | #(buffer_interface : pointer, buffer_name : cstring) -> void 53 | init: 54 | buffer.sc_world = buffer_interface #SC's World* is passed in as the buffer_interface argument 55 | buffer.bufnum = float32(-1e9) 56 | buffer.print_invalid = 1 57 | 58 | #(buffer : Buffer, value : cstring) -> void 59 | update: 60 | discard 61 | 62 | #(buffer : Buffer) -> bool 63 | lock: 64 | #Check for new Buffer 65 | let bufnum = buffer.input_bufnum 66 | if buffer.bufnum != bufnum: 67 | let snd_buf = get_buffer_SC(buffer.sc_world, cfloat(bufnum), cint(buffer.print_invalid)) 68 | buffer.snd_buf = snd_buf 69 | 70 | if not isNil(snd_buf): 71 | buffer.bufnum = bufnum 72 | buffer.print_invalid = 1 #next time there's an invalid buffer, print it out 73 | buffer.length = get_frames_buffer_SC(snd_buf) 74 | buffer.samplerate = get_samplerate_buffer_SC(snd_buf) 75 | buffer.channels = get_channels_buffer_SC(snd_buf) 76 | 77 | #This might still be valid if Buffer.free has been run, that's why I also check against data 78 | let snd_buf = buffer.snd_buf 79 | if isNil(snd_buf): 80 | invalid_buffer() 81 | 82 | #Get float* data and check if it's still valid (Buffer might have been freed) 83 | let buffer_data = cast[pointer](get_buffer_data_SC(snd_buf)) 84 | if isNil(buffer_data): 85 | invalid_buffer() 86 | 87 | when defined(supernova): 88 | lock_buffer_SC(snd_buf) 89 | 90 | #After lock, set snd_buf_data 91 | buffer.snd_buf_data = cast[ptr UncheckedArray[float32]](buffer_data) 92 | 93 | return true 94 | 95 | #(buffer : Buffer) -> void 96 | unlock: 97 | when defined(supernova): 98 | unlock_buffer_SC(buffer.snd_buf) 99 | 100 | #(buffer : Buffer, index : SomeInteger, channel : SomeInteger) -> float 101 | getter: 102 | let chans = buffer.channels 103 | 104 | var actual_index : int 105 | 106 | if chans == 1: 107 | actual_index = index 108 | else: 109 | #channel is 0 indexed 110 | if channel >= chans: 111 | return 0.0 112 | actual_index = (index * chans) + channel 113 | 114 | if actual_index >= 0 and actual_index < buffer.size: 115 | return buffer.snd_buf_data[actual_index] 116 | 117 | return 0.0 118 | 119 | #(buffer : Buffer, x : SomeFloat, index : SomeInteger, channel : SomeInteger) -> void 120 | setter: 121 | let chans = buffer.channels 122 | 123 | var actual_index : int 124 | 125 | if chans == 1: 126 | actual_index = index 127 | else: 128 | #channel is 0 indexed 129 | if channel >= chans: 130 | return 131 | actual_index = (index * chans) + channel 132 | 133 | if actual_index >= 0 and actual_index < buffer.size: 134 | buffer.snd_buf_data[actual_index] = float32(x) 135 | 136 | #Use the extra block to add setter for input_num, used in omnicollider_buffers call 137 | extra: 138 | proc omnicollider_set_input_bufnum_buffer*(buffer : Buffer, input_bufnum : float) : void {.inline.} = 139 | buffer.input_bufnum = input_bufnum 140 | -------------------------------------------------------------------------------- /omnicolliderpkg/Static/CMakeLists.txt.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | var OMNI_PROTO_CMAKE = """ 24 | set(FILENAME "Omni_PROTO.cpp") 25 | cmake_minimum_required (VERSION 3.0) 26 | get_filename_component(PROJECT ${FILENAME} NAME_WE) 27 | 28 | #Needed for generic OSX builds. MacOS 10.10 is the minimum. 29 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.10" CACHE STRING "Minimum OS X deployment version") 30 | 31 | #Define project 32 | project (${PROJECT}) 33 | 34 | message(STATUS "Build dir: ${OMNI_BUILD_DIR}") 35 | 36 | #Include build dir for omni.h. OMNI_BUILD_DIR also contains the compiled static lib (linked later in target_link_libraries) 37 | include_directories(${OMNI_BUILD_DIR}) 38 | 39 | #SC includes 40 | include_directories(${SC_PATH}/include/plugin_interface) 41 | include_directories(${SC_PATH}/include/common) 42 | include_directories(${SC_PATH}/common) 43 | 44 | #Supernova includes 45 | option(SUPERNOVA "Build plugins for supernova" OFF) 46 | if (SUPERNOVA) 47 | include_directories(${SC_PATH}/external_libraries/nova-tt) 48 | # actually just boost.atomic 49 | include_directories(${SC_PATH}/external_libraries/boost) 50 | include_directories(${SC_PATH}/external_libraries/boost_lockfree) 51 | include_directories(${SC_PATH}/external_libraries/boost-lockfree) 52 | endif() 53 | 54 | #Compiled UGen suffix 55 | set(CMAKE_SHARED_MODULE_PREFIX "") 56 | if(APPLE OR WIN32) 57 | set(CMAKE_SHARED_MODULE_SUFFIX ".scx") 58 | endif() 59 | 60 | #CPP11 61 | option(CPP11 "Build with c++11." ON) 62 | set (CMAKE_CXX_STANDARD 11) 63 | 64 | #Set release build type as default 65 | if(NOT CMAKE_BUILD_TYPE) 66 | set(CMAKE_BUILD_TYPE "Release") 67 | endif() 68 | 69 | #Set Clang on OSX 70 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 71 | set(CMAKE_COMPILER_IS_CLANG 1) 72 | endif() 73 | 74 | #Should all these C flags be disabled for generic builds??? 75 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) 76 | add_definitions(-fvisibility=hidden) 77 | 78 | include (CheckCCompilerFlag) 79 | include (CheckCXXCompilerFlag) 80 | 81 | CHECK_C_COMPILER_FLAG(-msse HAS_SSE) 82 | CHECK_CXX_COMPILER_FLAG(-msse HAS_CXX_SSE) 83 | 84 | if (HAS_SSE) 85 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse") 86 | endif() 87 | if (HAS_CXX_SSE) 88 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse") 89 | endif() 90 | 91 | CHECK_C_COMPILER_FLAG(-msse2 HAS_SSE2) 92 | CHECK_CXX_COMPILER_FLAG(-msse2 HAS_CXX_SSE2) 93 | 94 | if (HAS_SSE2) 95 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2") 96 | endif() 97 | if (HAS_CXX_SSE2) 98 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2") 99 | endif() 100 | 101 | CHECK_C_COMPILER_FLAG(-msse3 HAS_SSE3) 102 | CHECK_CXX_COMPILER_FLAG(-msse3 HAS_CXX_SSE3) 103 | 104 | if (HAS_SSE3) 105 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse3") 106 | endif() 107 | if (HAS_CXX_SSE3) 108 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse3") 109 | endif() 110 | 111 | CHECK_C_COMPILER_FLAG(-mfpmath=sse HAS_FPMATH_SSE) 112 | CHECK_CXX_COMPILER_FLAG(-mfpmath=sse HAS_CXX_FPMATH_SSE) 113 | 114 | if (HAS_FPMATH_SSE) 115 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpmath=sse") 116 | endif() 117 | if (HAS_CXX_FPMATH_SSE) 118 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse") 119 | endif() 120 | 121 | if(CPP11) 122 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 123 | if(CMAKE_COMPILER_IS_CLANG) 124 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 125 | endif() 126 | endif() 127 | endif() 128 | 129 | #Windows build. 130 | if(MINGW) 131 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mstackrealign") 132 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mstackrealign") 133 | endif() 134 | 135 | #Set optimizer flags 136 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG") 137 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG") 138 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -DNDEBUG") 139 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG") 140 | 141 | #Build architecture... if not none, pass the flags through 142 | message(STATUS "BUILD ARCHITECTURE : ${BUILD_MARCH}") 143 | if (NOT BUILD_MARCH STREQUAL "none") 144 | add_definitions(-march=${BUILD_MARCH}) 145 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=${BUILD_MARCH}") 146 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${BUILD_MARCH}") 147 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -march=${BUILD_MARCH}") 148 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=${BUILD_MARCH}") 149 | endif() 150 | 151 | #If native, also add mtune=native 152 | if (BUILD_MARCH STREQUAL "native") 153 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mtune=native") 154 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=native") 155 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mtune=native") 156 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mtune=native") 157 | add_definitions(-mtune=native) 158 | endif() 159 | 160 | #Declare the new UGen shared lib to build 161 | add_library(${PROJECT} MODULE ${FILENAME}) 162 | 163 | #Linker flags for scsynth UGen 164 | target_link_libraries(${PROJECT} "-fPIC -L'${OMNI_BUILD_DIR}' -l${PROJECT}") 165 | 166 | if(SUPERNOVA) 167 | #Declare the new supernova UGen shared lib to build 168 | add_library(${PROJECT}_supernova MODULE ${FILENAME}) 169 | 170 | #Add all the supernova definitions 171 | set_property(TARGET ${PROJECT}_supernova 172 | PROPERTY COMPILE_DEFINITIONS SUPERNOVA) 173 | 174 | #Linker flags for supernova UGen 175 | target_link_libraries(${PROJECT}_supernova "-fPIC -L'${OMNI_BUILD_DIR}' -l${PROJECT}_supernova") 176 | endif() 177 | """ 178 | -------------------------------------------------------------------------------- /omnicolliderpkg/Static/Omni_PROTO.cpp.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | var OMNI_PROTO_INCLUDES = """ 24 | #include 25 | #include 26 | #include 27 | #include "SC_PlugIn.h" 28 | #include "omni.h" 29 | """ 30 | 31 | var OMNI_PROTO_CPP = """ 32 | #define NAME "Omni_PROTO" 33 | 34 | #if defined(__APPLE__) || defined(_WIN32) 35 | #define EXTENSION ".scx" 36 | #elif __linux__ 37 | #define EXTENSION ".so" 38 | #endif 39 | 40 | //Interface table 41 | static InterfaceTable *ft; 42 | 43 | //Use an atomic flag so it works for supernova too 44 | std::atomic_flag init_global_lock = ATOMIC_FLAG_INIT; 45 | bool init_global = false; 46 | 47 | //World pointer. This global pointer is used for RT allocation functions 48 | World* SCWorld; 49 | 50 | //Wrappers around RTAlloc, RTFree 51 | void* RTAlloc_func(size_t in_size) 52 | { 53 | return ft->fRTAlloc(SCWorld, in_size); 54 | } 55 | 56 | void RTFree_func(void* in_ptr) 57 | { 58 | ft->fRTFree(SCWorld, in_ptr); 59 | } 60 | 61 | void RTPrint_func(const char* format_string, ...) 62 | { 63 | ft->fPrint(format_string); 64 | } 65 | 66 | /*********************************/ 67 | /* omnicollider buffer interface */ 68 | /*********************************/ 69 | extern "C" 70 | { 71 | void* get_buffer_SC(void* buffer_SCWorld, float fbufnum, int print_invalid) 72 | { 73 | if(!buffer_SCWorld) 74 | return nullptr; 75 | 76 | World* SCWorld = (World*)buffer_SCWorld; 77 | 78 | uint32 bufnum = (int)fbufnum; 79 | 80 | //If bufnum is not more that maximum number of buffers in World* it means bufnum doesn't point to a LocalBuf 81 | if(!(bufnum >= SCWorld->mNumSndBufs)) 82 | { 83 | SndBuf* snd_buf = SCWorld->mSndBufs + bufnum; 84 | 85 | if(!(snd_buf->data)) 86 | { 87 | if(print_invalid) 88 | printf("WARNING: Omni: Invalid buffer at index %d\n", bufnum); 89 | return nullptr; 90 | } 91 | 92 | return (void*)snd_buf; 93 | } 94 | else 95 | { 96 | printf("ERROR: Omni: local buffers are not yet supported\n"); 97 | return nullptr; 98 | //It would require to provide "unit" here (to retrieve parent). 99 | //Perhaps it can be passed in void* buffer_interface? 100 | } 101 | } 102 | 103 | void lock_buffer_SC(void* snd_buf) 104 | { 105 | #ifdef SUPERNOVA 106 | ACQUIRE_SNDBUF_SHARED(((SndBuf*)snd_buf)); 107 | #endif 108 | } 109 | 110 | void unlock_buffer_SC(void* snd_buf) 111 | { 112 | #ifdef SUPERNOVA 113 | RELEASE_SNDBUF_SHARED(((SndBuf*)snd_buf)); 114 | #endif 115 | } 116 | 117 | float* get_buffer_data_SC(void* snd_buf) 118 | { 119 | float* data = ((SndBuf*)snd_buf)->data; 120 | return data; 121 | } 122 | 123 | int get_frames_buffer_SC(void* snd_buf) 124 | { 125 | return ((SndBuf*)snd_buf)->frames; 126 | } 127 | 128 | double get_samplerate_buffer_SC(void* snd_buf) 129 | { 130 | return ((SndBuf*)snd_buf)->samplerate; 131 | } 132 | 133 | int get_channels_buffer_SC(void* snd_buf) 134 | { 135 | return ((SndBuf*)snd_buf)->channels; 136 | } 137 | } 138 | 139 | //SC struct 140 | struct Omni_PROTO : public Unit 141 | { 142 | void* omni_ugen; 143 | }; 144 | 145 | //SC functions 146 | static void Omni_PROTO_Ctor(Omni_PROTO* unit); 147 | static void Omni_PROTO_Dtor(Omni_PROTO* unit); 148 | static void Omni_PROTO_next(Omni_PROTO* unit, int inNumSamples); 149 | static void Omni_PROTO_silence_next(Omni_PROTO* unit, int inNumSamples); 150 | 151 | void Omni_PROTO_Ctor(Omni_PROTO* unit) 152 | { 153 | //Initialization routines. These are executed only the first time an OMNI_PROTO UGen is created. 154 | if(!init_global) 155 | { 156 | //Acquire lock 157 | while(init_global_lock.test_and_set(std::memory_order_acquire)) 158 | ; //spin 159 | 160 | //First thread that reaches this will set it for the entire shared object just once 161 | if(!init_global) 162 | { 163 | if(!(&Omni_InitGlobal)) 164 | Print("ERROR: No %s%s loaded\n", NAME, EXTENSION); 165 | else 166 | { 167 | //Set SCWorld pointer used in the RT functions 168 | SCWorld = unit->mWorld; 169 | 170 | //Init omni with all the correct function pointers 171 | Omni_InitGlobal( 172 | (omni_alloc_func_t*)RTAlloc_func, 173 | (omni_free_func_t*)RTFree_func, 174 | (omni_print_func_t*)RTPrint_func 175 | ); 176 | } 177 | 178 | //Completed initialization 179 | init_global = true; 180 | } 181 | 182 | //Release lock 183 | init_global_lock.clear(std::memory_order_release); 184 | } 185 | 186 | //Alloc 187 | unit->omni_ugen = Omni_UGenAlloc(); 188 | 189 | //Set starting input values for params 190 | for(int i = 0; i < NUM_PARAMS; i++) 191 | { 192 | int param_index = params_indices[i]; 193 | float in_val = unit->mInBuf[param_index][0]; 194 | const char* param_name = params_names[i].c_str(); 195 | Omni_UGenSetParam(unit->omni_ugen, param_name, in_val); 196 | } 197 | 198 | //Initialize 199 | bool omni_initialized = Omni_UGenInit( 200 | unit->omni_ugen, 201 | unit->mWorld->mBufLength, 202 | unit->mWorld->mSampleRate, 203 | (void*)unit->mWorld 204 | ); 205 | 206 | if(omni_initialized) 207 | { 208 | SETCALC(Omni_PROTO_next); 209 | Omni_PROTO_next(unit, 1); 210 | } 211 | else 212 | { 213 | unit->omni_ugen = nullptr; 214 | SETCALC(Omni_PROTO_silence_next); 215 | Omni_PROTO_silence_next(unit, 1); 216 | } 217 | } 218 | 219 | void Omni_PROTO_Dtor(Omni_PROTO* unit) 220 | { 221 | if(unit->omni_ugen) 222 | Omni_UGenFree(unit->omni_ugen); 223 | } 224 | 225 | void Omni_PROTO_next(Omni_PROTO* unit, int inNumSamples) 226 | { 227 | Omni_UGenPerform32( 228 | unit->omni_ugen, 229 | unit->mInBuf, 230 | unit->mOutBuf, 231 | inNumSamples 232 | ); 233 | } 234 | 235 | void Omni_PROTO_silence_next(Omni_PROTO* unit, int inNumSamples) 236 | { 237 | for(int i = 0; i < unit->mNumOutputs; i++) 238 | { 239 | for(int y = 0; y < inNumSamples; y++) 240 | unit->mOutBuf[i][y] = 0.0f; 241 | } 242 | } 243 | 244 | PluginLoad(Omni_PROTOUGens) 245 | { 246 | ft = inTable; 247 | DefineDtorCantAliasUnit(Omni_PROTO); 248 | } 249 | """ 250 | -------------------------------------------------------------------------------- /omnicollider.nim: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020-2021 Francesco Cameli 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 | 23 | import cligen, terminal, os, strutils 24 | 25 | when not defined(Windows): 26 | import osproc 27 | 28 | #Package version is passed as argument when building. It will be constant and set correctly 29 | const 30 | NimblePkgVersion {.strdefine.} = "" 31 | omnicollider_ver = NimblePkgVersion 32 | 33 | #-v / --version 34 | let version_flag = "OmniCollider - version " & $omnicollider_ver & "\n(c) 2020-2021 Francesco Cameli" 35 | 36 | #Default to the omni nimble folder, which should have it installed if omni has been installed correctly 37 | const default_sc_path = "~/.nimble/pkgs/omnicollider-" & omnicollider_ver & "/omnicolliderpkg/deps/supercollider" 38 | 39 | when defined(Linux): 40 | const 41 | default_extensions_path = "~/.local/share/SuperCollider/Extensions" 42 | ugen_extension = ".so" 43 | lib_prepend = "lib" 44 | static_lib_extension = ".a" 45 | 46 | when defined(MacOSX) or defined(MacOS): 47 | const 48 | default_extensions_path = "~/Library/Application Support/SuperCollider/Extensions" 49 | ugen_extension = ".scx" 50 | lib_prepend = "lib" 51 | static_lib_extension = ".a" 52 | 53 | when defined(Windows): 54 | const 55 | default_extensions_path = "~\\AppData\\Local\\SuperCollider\\Extensions" 56 | ugen_extension = ".scx" 57 | lib_prepend = "" 58 | static_lib_extension = ".lib" 59 | 60 | template printError(msg : string) : untyped = 61 | setForegroundColor(fgRed) 62 | writeStyled("ERROR: ", {styleBright}) 63 | setForegroundColor(fgWhite, true) 64 | writeStyled(msg & "\n") 65 | 66 | template printDone(msg : string) : void = 67 | setForegroundColor(fgGreen) 68 | writeStyled("\nSUCCESS: ", {styleBright}) 69 | setForegroundColor(fgWhite, true) 70 | writeStyled(msg & "\n") 71 | 72 | proc omnicollider_single_file(is_multi : bool = false, fileFullPath : string, outDir : string = "", scPath : string = "", architecture : string = "native", supernova : bool = true, removeBuildFiles : bool = true) : int = 73 | 74 | #Check if file exists 75 | if not fileFullPath.fileExists(): 76 | printError($fileFullPath & " does not exist.") 77 | return 1 78 | 79 | var 80 | omniFile = splitFile(fileFullPath) 81 | omniFileDir = omniFile.dir 82 | omniFileName = omniFile.name 83 | omniFileExt = omniFile.ext 84 | 85 | #Check file first charcter, must be a capital letter 86 | if not omniFileName[0].isUpperAscii: 87 | omniFileName[0] = omniFileName[0].toUpperAscii() 88 | 89 | #Check file extension 90 | if not(omniFileExt == ".omni") and not(omniFileExt == ".oi"): 91 | printError($fileFullPath & " is not an Omni file.") 92 | return 1 93 | 94 | var expanded_sc_path : string 95 | 96 | if scPath == "": 97 | expanded_sc_path = default_sc_path 98 | else: 99 | expanded_sc_path = scPath 100 | 101 | expanded_sc_path = expanded_sc_path.normalizedPath().expandTilde().absolutePath() 102 | 103 | #Check scPath 104 | if not expanded_sc_path.dirExists(): 105 | printError("scPath: " & $expanded_sc_path & " does not exist.") 106 | return 1 107 | 108 | var expanded_out_dir : string 109 | 110 | if outDir == "": 111 | expanded_out_dir = default_extensions_path 112 | else: 113 | expanded_out_dir = outDir 114 | 115 | expanded_out_dir = expanded_out_dir.normalizedPath().expandTilde().absolutePath() 116 | 117 | #Check outDir 118 | if not expanded_out_dir.dirExists(): 119 | printError("outDir: " & $expanded_out_dir & " does not exist.") 120 | return 1 121 | 122 | #x86_64 and amd64 are aliases for x86-64 123 | var real_architecture = architecture 124 | if real_architecture == "x86_64" or real_architecture == "amd64": 125 | real_architecture = "x86-64" 126 | 127 | #Full paths to the new file in omniFileName directory 128 | let 129 | #New folder named with the name of the Omni file 130 | fullPathToNewFolder = $omniFileDir & "/" & $omniFileName 131 | 132 | #This is to use in shell cmds instead of fullPathToNewFolder, expands spaces to "\ " 133 | #fullPathToNewFolderShell = fullPathToNewFolder.replace(" ", "\\ ") 134 | 135 | #This is the Omni file copied to the new folder 136 | fullPathToOmniFile = $fullPathToNewFolder & "/" & $omniFileName & $omniFileExt 137 | 138 | #These are the .cpp, .sc and cmake files in new folder 139 | fullPathToCppFile = $fullPathToNewFolder & "/" & $omniFileName & ".cpp" 140 | fullPathToSCFile = $fullPathToNewFolder & "/" & $omniFileName & ".sc" 141 | fullPathToCMakeFile = $fullPathToNewFolder & "/" & "CMakeLists.txt" 142 | 143 | #These are the paths to the generated static libraries 144 | fullPathToStaticLib = $fullPathToNewFolder & "/" & $lib_prepend & $omniFileName & $static_lib_extension 145 | fullPathToStaticLib_supernova = $fullPathToNewFolder & "/" & $lib_prepend & $omniFileName & "_supernova" & $static_lib_extension 146 | 147 | #Create directory in same folder as .omni file 148 | removeDir(fullPathToNewFolder) 149 | createDir(fullPathToNewFolder) 150 | 151 | #Copy omniFile to folder 152 | copyFile(fileFullPath, fullPathToOmniFile) 153 | 154 | # ================= # 155 | # COMPILE OMNI FILE # 156 | # ================= # 157 | 158 | #Compile omni file 159 | let omni_command = "omni \"" & $fileFullPath & "\" --silent:true --architecture:" & $real_architecture & " --lib:static --wrapper:omnicollider_lang --performBits:32 --define:omni_locks_disable --define:omni_buffers_disable_multithreading --exportIO:true --outDir:\"" & $fullPathToNewFolder & "\"" 160 | 161 | #Windows requires powershell to figure out the .nimble path... 162 | when defined(Windows): 163 | let failedOmniCompilation = execShellCmd(omni_command) 164 | else: 165 | let failedOmniCompilation = execCmd(omni_command) 166 | 167 | #error code from execCmd is usually some 8bit number saying what error arises. I don't care which one for now. 168 | if failedOmniCompilation > 0: 169 | removeDir(fullPathToNewFolder) 170 | if is_multi: 171 | printError("Failed compilation of '" & omniFileName & omniFileExt & "'.") 172 | return 1 173 | 174 | #Also for supernova 175 | if supernova: 176 | #supernova gets passed both supercollider (which turns on the rt_alloc) and supernova (for buffer handling) flags 177 | var omni_command_supernova = "omni \"" & $fileFullPath & "\" --silent:true --architecture:" & $real_architecture & " --lib:static --outName:" & $omniFileName & "_supernova --wrapper:omnicollider_lang --performBits:32 --define:omni_locks_disable --define:supernova --exportIO:true --outDir:\"" & $fullPathToNewFolder & "\"" 178 | 179 | #Windows requires powershell to figure out the .nimble path... go figure! 180 | when defined(Windows): 181 | let failedOmniCompilation_supernova = execShellCmd(omni_command_supernova) 182 | else: 183 | let failedOmniCompilation_supernova = execCmd(omni_command_supernova) 184 | 185 | #error code from execCmd is usually some 8bit number saying what error arises. I don't care which one for now. 186 | if failedOmniCompilation_supernova > 0: 187 | removeDir(fullPathToNewFolder) 188 | if is_multi: 189 | printError("Failed compilation of '" & omniFileName & omniFileExt & "'.") 190 | return 1 191 | 192 | # ================ # 193 | # RETRIEVE I / O # 194 | # ================ # 195 | 196 | let 197 | fullPathToIOFile = fullPathToNewFolder & "/" & omniFileName & "_io.txt" 198 | io_file = readFile(fullPathToIOFile) 199 | io_file_seq = io_file.split('\n') 200 | 201 | if io_file_seq.len != 11: 202 | printError("Invalid io file: " & fullPathToIOFile & ".") 203 | removeDir(fullPathToNewFolder) 204 | return 1 205 | 206 | var 207 | num_inputs = parseInt(io_file_seq[0]) 208 | inputs_names_string = io_file_seq[1] 209 | inputs_names = inputs_names_string.split(',') 210 | inputs_defaults_string = io_file_seq[2] 211 | inputs_defaults = inputs_defaults_string.split(',') 212 | num_params = parseInt(io_file_seq[3]) 213 | params_names_string = io_file_seq[4] 214 | params_names = params_names_string.split(',') 215 | params_defaults_string = io_file_seq[5] 216 | params_defaults = params_defaults_string.split(',') 217 | num_buffers = parseInt(io_file_seq[6]) 218 | buffers_names_string = io_file_seq[7] 219 | buffers_names = buffers_names_string.split(',') 220 | num_outputs = parseInt(io_file_seq[9]) 221 | 222 | var num_inputs_buffers_params = num_inputs 223 | 224 | #Check for 0 inputs, cleanup the entries ("NIL" and 0) 225 | if num_inputs == 0: 226 | inputs_names.del(0) 227 | inputs_defaults.del(0) 228 | 229 | #Do this check cause no buffers == "NIL", don't wanna add that 230 | if num_buffers > 0: 231 | num_inputs_buffers_params += num_buffers 232 | inputs_names.add(buffers_names) 233 | 234 | #Do this check cause no params == "NIL", don't wanna add that 235 | if num_params > 0: 236 | num_inputs_buffers_params += num_params 237 | inputs_names.add(params_names) 238 | inputs_defaults.add(params_defaults) 239 | 240 | # ======== # 241 | # SC I / O # 242 | # ======== # 243 | 244 | var 245 | #SC 246 | arg_string = "" 247 | arg_rates = "" 248 | multiNew_string = "^this.multiNew('audio'" 249 | multiOut_string = "" 250 | 251 | #CPP 252 | NUM_PARAMS_CPP = "#define NUM_PARAMS " & $num_params 253 | PARAMS_INDICES_CPP = "const std::array params_indices = { " 254 | PARAMS_NAMES_CPP = "const std::array params_names = { " 255 | 256 | if num_inputs_buffers_params == 0: 257 | multiNew_string.add(");") 258 | else: 259 | arg_string.add("arg ") 260 | multiNew_string.add(",") 261 | for index, input_name in inputs_names: 262 | var 263 | default_val : string 264 | is_param = false 265 | is_buffer = false 266 | 267 | #buffer 268 | if index >= num_inputs and index < num_inputs + num_buffers: 269 | is_buffer = true 270 | default_val = "0" 271 | 272 | #param 273 | elif index >= num_inputs + num_buffers: 274 | PARAMS_INDICES_CPP.add($index & ",") 275 | PARAMS_NAMES_CPP.add("\"" & $input_name & "\",") 276 | is_param = true 277 | default_val = inputs_defaults[index - num_buffers] 278 | 279 | #ins 280 | else: 281 | default_val = inputs_defaults[index] 282 | 283 | if is_param: 284 | arg_rates.add("if(" & $input_name & ".rate == 'audio', { ((this.class).asString.replace(\"Meta_\", \"\") ++ \": expected argument \'" & $input_name & "\' to be at control rate. Wrapping it in a A2K.kr UGen.\").warn; " & $input_name & " = A2K.kr(" & $input_name & "); });\n\t\t") 285 | elif is_buffer: 286 | arg_rates.add("if(" & $input_name & ".class != Buffer, { if(" & $input_name & ".rate == 'audio', { ((this.class).asString.replace(\"Meta_\", \"\") ++ \": expected argument \'" & $input_name & "\' to be at control rate. Wrapping it in a A2K.kr UGen.\").warn; " & $input_name & " = A2K.kr(" & $input_name & "); }) });\n\t\t") 287 | else: 288 | arg_rates.add("if(" & $input_name & ".rate != 'audio', { ((this.class).asString.replace(\"Meta_\", \"\") ++ \": expected argument \'" & $input_name & "\' to be at audio rate. Wrapping it in a K2A.ar UGen.\").warn; " & $input_name & " = K2A.ar(" & $input_name & "); });\n\t\t") 289 | 290 | if index == num_inputs_buffers_params - 1: 291 | arg_string.add($input_name & "=(" & $default_val & ");") 292 | multiNew_string.add($input_name & ");") 293 | break 294 | 295 | arg_string.add($input_name & "=(" & $default_val & "), ") 296 | multiNew_string.add($input_name & ", ") 297 | 298 | PARAMS_INDICES_CPP.removeSuffix(',') 299 | PARAMS_NAMES_CPP.removeSuffix(',') 300 | PARAMS_INDICES_CPP.add(" };") 301 | PARAMS_NAMES_CPP.add(" };") 302 | 303 | #These are the files to overwrite! Need them at every iteration (when compiling multiple files or a folder) 304 | include "omnicolliderpkg/Static/Omni_PROTO.cpp.nim" 305 | include "omnicolliderpkg/Static/CMakeLists.txt.nim" 306 | include "omnicolliderpkg/Static/Omni_PROTO.sc.nim" 307 | 308 | OMNI_PROTO_SC = OMNI_PROTO_SC.replace("//args", arg_string) 309 | OMNI_PROTO_SC = OMNI_PROTO_SC.replace("//rates", arg_rates) 310 | OMNI_PROTO_SC = OMNI_PROTO_SC.replace("//multiNew", multiNew_string) 311 | 312 | #Multiple outputs UGen 313 | if num_outputs > 1: 314 | multiOut_string = "init { arg ... theInputs;\n\t\tinputs = theInputs;\n\t\t^this.initOutputs(" & $num_outputs & ", rate);\n\t}" 315 | OMNI_PROTO_SC = OMNI_PROTO_SC.replace("//multiOut", multiOut_string) 316 | OMNI_PROTO_SC = OMNI_PROTO_SC.replace(" : UGen", " : MultiOutUGen") 317 | 318 | #Replace Omni_PROTO with the name of the Omni file 319 | OMNI_PROTO_CPP = OMNI_PROTO_CPP.replace("Omni_PROTO", omniFileName) 320 | OMNI_PROTO_SC = OMNI_PROTO_SC.replace("Omni_PROTO", omniFileName) 321 | OMNI_PROTO_CMAKE = OMNI_PROTO_CMAKE.replace("Omni_PROTO", omniFileName) 322 | 323 | #Chain the file together with all correct infos too 324 | OMNI_PROTO_CPP = $OMNI_PROTO_INCLUDES & "\n" & $NUM_PARAMS_CPP & "\n" & $PARAMS_INDICES_CPP & "\n" & PARAMS_NAMES_CPP & "\n" & "\n" & $OMNI_PROTO_CPP 325 | 326 | # =========== # 327 | # WRITE FILES # 328 | # =========== # 329 | 330 | #Create .ccp/.sc/cmake files in the new folder 331 | let 332 | cppFile = open(fullPathToCppFile, fmWrite) 333 | scFile = open(fullPathToSCFile, fmWrite) 334 | cmakeFile = open(fullPathToCMakeFile, fmWrite) 335 | 336 | cppFile.write(OMNI_PROTO_CPP) 337 | scFile.write(OMNI_PROTO_SC) 338 | cmakeFile.write(OMNI_PROTO_CMAKE) 339 | 340 | cppFile.close 341 | scFile.close 342 | cmakeFile.close 343 | 344 | # ========== # 345 | # BUILD UGEN # 346 | # ========== # 347 | 348 | #Create build folder 349 | removeDir($fullPathToNewFolder & "/build") 350 | createDir($fullPathToNewFolder & "/build") 351 | 352 | var 353 | supernova_on_off = "OFF" 354 | sc_cmake_cmd : string 355 | 356 | if supernova: 357 | supernova_on_off = "ON" 358 | 359 | when defined(Windows): 360 | #Cmake wants a path in unix style, not windows! Replace "/" with "\" 361 | let fullPathToNewFolder_Unix = fullPathToNewFolder.replace("\\", "/") 362 | sc_cmake_cmd = "cmake -G \"MinGW Makefiles\" -DCMAKE_MAKE_PROGRAM:PATH=\"make\" -DOMNI_BUILD_DIR=\"" & $fullPathToNewFolder_Unix & "\" -DSC_PATH=\"" & $expanded_sc_path & "\" -DSUPERNOVA=" & $supernova_on_off & " -DCMAKE_BUILD_TYPE=Release -DBUILD_MARCH=" & $real_architecture & " .." 363 | else: 364 | sc_cmake_cmd = "cmake -DOMNI_BUILD_DIR=\"" & $fullPathToNewFolder & "\" -DSC_PATH=\"" & $expanded_sc_path & "\" -DSUPERNOVA=" & $supernova_on_off & " -DCMAKE_BUILD_TYPE=Release -DBUILD_MARCH=" & $real_architecture & " .." 365 | 366 | #cd into the build directory 367 | setCurrentDir(fullPathToNewFolder & "/build") 368 | 369 | #Execute CMake 370 | when defined(Windows): 371 | let failedSCCmake = execShellCmd(sc_cmake_cmd) 372 | else: 373 | let failedSCCmake = execCmd(sc_cmake_cmd) 374 | 375 | #error code from execCmd is usually some 8bit number saying what error arises. I don't care which one for now. 376 | if failedSCCmake > 0: 377 | printError("Unsuccessful cmake generation of the UGen file \"" & $omniFileName & ".cpp\".") 378 | removeDir(fullPathToNewFolder) 379 | return 1 380 | 381 | #make command 382 | let compilation_cmd = "cmake --build . --config Release" 383 | when defined(Windows): 384 | let failedSCCompilation = execShellCmd(compilation_cmd) #execCmd doesn't work on Windows (since it wouldn't go through the powershell) 385 | else: 386 | let failedSCCompilation = execCmd(compilation_cmd) 387 | 388 | #error code from execCmd is usually some 8bit number saying what error arises. I don't care which one for now. 389 | if failedSCCompilation > 0: 390 | printError("Unsuccessful compilation of the UGen file \"" & $omniFileName & ".cpp\".") 391 | removeDir(fullPathToNewFolder) 392 | return 1 393 | 394 | #cd back to the original folder where omni file is 395 | setCurrentDir(omniFileDir) 396 | 397 | # ========================= # 398 | # COPY TO EXTENSIONS FOLDER # 399 | # ========================= # 400 | copyFile($fullPathToNewFolder & "/build/" & $omniFileName & $ugen_extension, $fullPathToNewFolder & "/" & $omniFileName & $ugen_extension) 401 | if supernova: 402 | copyFile($fullPathToNewFolder & "/build/" & $omniFileName & "_supernova" & $ugen_extension, $fullPathToNewFolder & "/" & $omniFileName & "_supernova" & $ugen_extension) 403 | 404 | #Remove build dir 405 | removeDir(fullPathToNewFolder & "/build") 406 | 407 | #If removeBuildFiles, remove all sources and static libraries compiled 408 | if removeBuildFiles: 409 | let fullPathToOmniHeaderFile = fullPathToNewFolder & "/omni.h" 410 | removeFile(fullPathToOmniHeaderFile) 411 | removeFile(fullPathToCppFile) 412 | removeFile(fullPathToOmniFile) 413 | removeFile(fullPathToCMakeFile) 414 | removeFile(fullPathToIOFile) 415 | removeFile(fullPathToStaticLib) 416 | if supernova: 417 | removeFile(fullPathToStaticLib_supernova) 418 | 419 | #Copy to extensions folder 420 | let fullPathToNewFolderInOutDir = $expanded_out_dir & "/" & omniFileName 421 | 422 | #Remove temp folder used for compilation only if it differs from outDir (otherwise, it's gonna delete the actual folder) 423 | if fullPathToNewFolderInOutDir != fullPathToNewFolder: 424 | #Remove previous folder in outDir if there was, then copy the new one over, then delete the temporary one 425 | removeDir(fullPathToNewFolderInOutDir) 426 | copyDir(fullPathToNewFolder, fullPathToNewFolderInOutDir) 427 | removeDir(fullPathToNewFolder) 428 | 429 | printDone("The '" & $omniFileName & "' UGen has been correctly built and installed in \"" & $expanded_out_dir & "\".") 430 | 431 | return 0 432 | 433 | proc omnicollider(files : seq[string], outDir : string = "", scPath : string = "", architecture : string = "native", supernova : bool = true, removeBuildFiles : bool = true) : int = 434 | #no files provided, print --version 435 | if files.len == 0: 436 | echo version_flag 437 | return 0 438 | 439 | for omniFile in files: 440 | #Get full extended path 441 | let omniFileFullPath = omniFile.normalizedPath().expandTilde().absolutePath() 442 | 443 | #if just one file in CLI, also pass the outName flag 444 | if omniFileFullPath.fileExists(): 445 | if files.len == 1: 446 | return omnicollider_single_file(false, omniFileFullPath, outDir, scPath, architecture, supernova, removeBuildFiles): 447 | else: 448 | if omnicollider_single_file(true, omniFileFullPath, outDir, scPath, architecture, supernova, removeBuildFiles) > 0: 449 | return 1 450 | 451 | #If it's a dir, compile all .omni/.oi files in it 452 | elif omniFileFullPath.dirExists(): 453 | for kind, dirFile in walkDir(omniFileFullPath): 454 | if kind == pcFile: 455 | let 456 | dirFileFullPath = dirFile.normalizedPath().expandTilde().absolutePath() 457 | dirFileExt = dirFileFullPath.splitFile().ext 458 | 459 | if dirFileExt == ".omni" or dirFileExt == ".oi": 460 | if omnicollider_single_file(true, dirFileFullPath, outDir, scPath, architecture, supernova, removeBuildFiles) > 0: 461 | return 1 462 | 463 | else: 464 | printError($omniFileFullPath & " does not exist.") 465 | return 1 466 | 467 | return 0 468 | 469 | #Workaround to pass custom version 470 | clCfg.version = version_flag 471 | 472 | #Remove --help-syntax 473 | clCfg.helpSyntax = "" 474 | 475 | #Arguments string 476 | let arguments = "Arguments:\n Omni file(s) or folder." 477 | 478 | #Ignore clValType 479 | clCfg.hTabCols = @[ clOptKeys, #[clValType,]# clDflVal, clDescrip ] 480 | 481 | #Dispatch the omnicollider function as the CLI one 482 | dispatch( 483 | omnicollider, 484 | 485 | #Remove "Usage: ..." 486 | noHdr = true, 487 | 488 | #Custom options printing 489 | usage = version_flag & "\n\n" & arguments & "\n\nOptions:\n$options", 490 | 491 | short = { 492 | "version" : 'v', 493 | "scPath" : 'p', 494 | "supernova" : 's' 495 | }, 496 | 497 | help = { 498 | "help" : "CLIGEN-NOHELP", 499 | "version" : "CLIGEN-NOHELP", 500 | "outDir" : "Output directory. Defaults to SuperCollider's 'Platform.userExtensionDir': \"" & $default_extensions_path & "\".", 501 | "scPath" : "Path to the SuperCollider source code folder. Defaults to the one in OmniCollider's dependencies: \"" & $default_sc_path & "\".", 502 | "architecture" : "Build architecture.", 503 | "supernova" : "Build with supernova support.", 504 | "removeBuildFiles" : "Remove all source files used for compilation from outDir." 505 | } 506 | ) 507 | --------------------------------------------------------------------------------