├── .github └── workflows │ └── all.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── ChucKDesigner.toe ├── LICENSE ├── Plugins └── .gitignore ├── README.md ├── assets ├── .gitignore ├── 60988__folktelemetry__crash-fast-14.wav ├── README.md ├── advanced_128_channels_out.ck ├── advanced_fauck_flute.ck ├── advanced_fauck_reverb.ck ├── advanced_global_float_example.ck ├── advanced_global_float_random_out.ck ├── basic_adsr.ck ├── basic_alarm.ck ├── basic_array_melody.ck ├── basic_blit.ck ├── basic_blit2.ck ├── basic_delay.ck ├── basic_fm.ck ├── basic_wind.ck ├── chuckdesigner_file_playback.ck ├── chuckdesigner_passthrough.ck ├── chuckdesigner_ping_pong_delay.ck ├── chuckdesigner_print_directory.ck ├── machine_add.ck ├── sequencer.ck ├── sequencer_simple.ck ├── stk_mandolin.ck ├── stk_modalbar.ck ├── stk_rhodey.ck ├── stk_shake_cycle.ck └── stk_wurley.ck ├── build_macos.sh ├── build_windows.bat ├── docs └── float_example.png ├── mac └── miniAudicle.entitlements ├── src ├── ChucKDesignerCHOP.cpp ├── ChucKDesignerCHOP.h ├── ChucKListenerCHOP.cpp ├── ChucKListenerCHOP.h ├── Info.plist ├── Plugin_ChucK.cpp └── Plugin_ChucK.h └── thirdparty └── TouchDesigner ├── CHOP_CPlusPlusBase.h ├── CPlusPlus_Common.h └── GL_Extensions.h /.github/workflows/all.yml: -------------------------------------------------------------------------------- 1 | env: 2 | CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM: ${{ secrets.CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM }} 3 | MACOS_CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }} 4 | MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} 5 | P12_PASSWORD: ${{ secrets.P12_PASSWORD }} 6 | KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} 7 | NOTARIZATION_TEAM_ID: ${{ secrets.NOTARIZATION_TEAM_ID }} 8 | NOTARIZATION_USERNAME: ${{ secrets.NOTARIZATION_USERNAME }} 9 | NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }} 10 | 11 | name: Compile 12 | on: 13 | pull_request: {} 14 | push: 15 | tags: 16 | - '*' 17 | jobs: 18 | 19 | build-windows: 20 | runs-on: windows-latest 21 | strategy: 22 | matrix: 23 | include: 24 | - { name: "win64", os: "windows-2022", python-version: "3.9", python-major: "39"} 25 | - { name: "win64", os: "windows-2022", python-version: "3.11", python-major: "311"} 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | submodules: true 30 | 31 | - name: Setup Python 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | 36 | - name: Add msbuild to PATH 37 | uses: microsoft/setup-msbuild@v2 38 | 39 | - name: Get CMake 40 | uses: lukka/get-cmake@latest 41 | 42 | - name: Build Windows (Release) 43 | run: | 44 | mkdir build 45 | cmake . -DCMAKE_BUILD_TYPE=Release -Bbuild -DPython_ROOT_DIR=$pythonLocation 46 | cd build 47 | msbuild ChucKDesignerCHOP.sln /property:Configuration=Release 48 | 49 | - name: Make distribution 50 | run: | 51 | mkdir ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }} 52 | move ${{ github.workspace }}/Plugins/ChucK*.dll ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }} 53 | 7z a ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }}.zip ./ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }}/* -r 54 | 55 | - name: Upload artifact 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }} 59 | path: ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }}.zip 60 | if-no-files-found: error 61 | 62 | build-macos: 63 | strategy: 64 | fail-fast: false 65 | matrix: 66 | include: 67 | - name: macos-x86_64 68 | arch: x86_64 69 | os: macos-12 70 | python-version: "3.11" 71 | python-major: "311" 72 | - name: macos-arm64 73 | arch: arm64 74 | os: macos-12 75 | python-version: "3.11" 76 | python-major: "311" 77 | 78 | runs-on: macos-12 79 | env: 80 | DEST_DIR: ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }} 81 | steps: 82 | - uses: actions/checkout@v4 83 | with: 84 | submodules: true 85 | 86 | - name: Install Certificate 87 | # https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development 88 | run: | 89 | # create variables 90 | CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 91 | KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db 92 | 93 | # import certificate and provisioning profile from secrets 94 | echo -n "$MACOS_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH 95 | 96 | # create temporary keychain 97 | security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" $KEYCHAIN_PATH 98 | security set-keychain-settings -lut 21600 $KEYCHAIN_PATH 99 | security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" $KEYCHAIN_PATH 100 | 101 | # import certificate to keychain 102 | security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH 103 | security set-key-partition-list -S apple-tool:,apple: -k "$MACOS_CERTIFICATE_PASSWORD" $KEYCHAIN_PATH 104 | echo "list-keychain:\n" 105 | security list-keychain -d user -s $KEYCHAIN_PATH 106 | echo "find-identity:\n" 107 | security find-identity -v 108 | echo "find-identity codesigning:\n" 109 | security find-identity -p codesigning -v 110 | 111 | - name: Setup Python 112 | uses: actions/setup-python@v5 113 | with: 114 | python-version: ${{ matrix.python-version }} 115 | 116 | - name: Brew install requirements (arm64) 117 | if: ${{ endsWith( matrix.name, 'macos-arm64') }} 118 | run: | 119 | brew update 120 | PACKAGES=(flac libogg libtool libvorbis opus mpg123 lame) 121 | DEPS=($(brew deps --union --topological $(echo $PACKAGES) | tr '\n' ' ')) 122 | PACKAGES=("${DEPS[@]}" "${PACKAGES[@]}") 123 | export HOMEBREW_NO_INSTALL_CLEANUP=1 124 | export HOMEBREW_NO_INSTALL_UPGRADE=1 125 | export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 126 | for PACKAGE in "${PACKAGES[@]}" 127 | do 128 | echo "Fetching bottle: $PACKAGE" 129 | response=$(brew fetch --bottle-tag=arm64_monterey $PACKAGE 2>&1) 130 | package_path=$(echo $response | sed -n 's/.*\:\ \(.*\.tar\.gz\).*/\1/p') 131 | package_path=$(echo "$package_path" | xargs) 132 | echo "Package Path: $package_path" 133 | brew reinstall --verbose --force-bottle "$package_path" || true 134 | done 135 | 136 | brew uninstall --ignore-dependencies curl git || true 137 | 138 | - name: Install dependencies macOS 139 | if: ${{ endsWith( matrix.name, 'macos-x86_64') }} 140 | run: | 141 | brew install autoconf autogen automake flac libogg libtool libvorbis opus mpg123 pkg-config 142 | 143 | - name: Some Setup 144 | run: | 145 | cd thirdparty/chuck/src/core 146 | bison -dv -b chuck chuck.y 147 | flex -ochuck.yy.c chuck.lex 148 | 149 | - name: Build MacOS (Release) 150 | run: | 151 | cmake -Bbuild -G "Xcode" -DCMAKE_OSX_ARCHITECTURES=${{matrix.arch}} -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 -DPYTHONVER="${{matrix.python-version}}" -DPython_ROOT_DIR=$pythonLocation 152 | cmake --build build --config Release 153 | codesign --entitlements "mac/miniAudicle.entitlements" --force --deep --timestamp --verify --verbose=2 --options=runtime --sign "Developer ID Application: David Braun (${{secrets.CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM}})" build/Release/ChucKDesignerCHOP.plugin 154 | codesign --entitlements "mac/miniAudicle.entitlements" --force --deep --timestamp --verify --verbose=2 --options=runtime --sign "Developer ID Application: David Braun (${{secrets.CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM}})" build/Release/ChucKListenerCHOP.plugin 155 | codesign --entitlements "mac/miniAudicle.entitlements" --force --deep --timestamp --verify --verbose=2 --options=runtime --sign "Developer ID Application: David Braun (${{secrets.CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM}})" build/Release/libChucKDesignerShared.dylib 156 | codesign --verify --deep --strict --verbose=2 build/Release/ChucKDesignerCHOP.plugin 157 | codesign --verify --deep --strict --verbose=2 build/Release/ChucKListenerCHOP.plugin 158 | codesign --verify --deep --strict --verbose=2 build/Release/libChucKDesignerShared.dylib 159 | 160 | - name: Make distribution 161 | run: | 162 | mkdir $DEST_DIR 163 | cp ${{ github.workspace }}/build/Release/libChucKDesignerShared.dylib $DEST_DIR 164 | mv ${{ github.workspace }}/build/Release/ChucKDesignerCHOP.plugin $DEST_DIR 165 | mv ${{ github.workspace }}/build/Release/ChucKListenerCHOP.plugin $DEST_DIR 166 | zip -r $DEST_DIR.zip $DEST_DIR 167 | 168 | - name: Notarize 169 | run: | 170 | xcrun notarytool submit "$DEST_DIR.zip" \ 171 | --team-id "$NOTARIZATION_TEAM_ID" \ 172 | --apple-id "$NOTARIZATION_USERNAME" \ 173 | --password "$NOTARIZATION_PASSWORD" \ 174 | --wait 175 | 176 | - name: Staple 177 | # While you can notarize a ZIP archive, you can’t staple to it directly. 178 | # Instead, run stapler against each item that you added to the archive. 179 | # Then create a new ZIP file containing the stapled items for distribution. 180 | # Although tickets are created for standalone binaries, it’s not currently possible to staple tickets to them. 181 | run: | 182 | xcrun stapler staple $DEST_DIR/ChucKDesignerCHOP.plugin 183 | xcrun stapler staple $DEST_DIR/ChucKListenerCHOP.plugin 184 | 185 | - name: Make stapled distribution 186 | run: | 187 | rm $DEST_DIR.zip 188 | zip -r $DEST_DIR.zip $DEST_DIR 189 | 190 | - name: Upload artifact 191 | uses: actions/upload-artifact@v4 192 | with: 193 | name: ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }} 194 | path: ChucKDesigner-${{ matrix.name }}-Python${{ matrix.python-major }}.zip 195 | if-no-files-found: error 196 | 197 | create-release: 198 | if: startsWith(github.ref, 'refs/tags/v') 199 | needs: [build-windows, build-macos] 200 | runs-on: ubuntu-latest 201 | name: "Create Release on GitHub" 202 | steps: 203 | - uses: actions/download-artifact@v4 204 | with: 205 | path: "dist" 206 | 207 | - uses: ncipollo/release-action@v1 208 | with: 209 | artifacts: "dist/*/*" 210 | token: ${{ secrets.GITHUB_TOKEN }} 211 | draft: true 212 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | Backup 3 | CrashAutoSave* 4 | *.dmp 5 | ChucKDesigner.[0-9]*.toe 6 | .DS_STORE -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/chuck"] 2 | path = thirdparty/chuck 3 | url = https://github.com/ccrma/chuck 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.0 FATAL_ERROR) 2 | 3 | SET(VERSION 0.3.5) 4 | project(ChucKDesigner VERSION ${VERSION}) 5 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ChucKDesignerCHOP) 6 | 7 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 8 | 9 | ################################################################################ 10 | # Sub-projects 11 | ################################################################################ 12 | 13 | file(GLOB CK_SOURCES 14 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/*.h 15 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/*.cpp 16 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/*.c 17 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/regcomp.c 18 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/regerror.c 19 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/regexec.c 20 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-ast.c 21 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-compile.c 22 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-filter.c 23 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-match-approx.c 24 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-match-backtrack.c 25 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-match-parallel.c 26 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-mem.c 27 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-parse.c 28 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-stack.c 29 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/regex/tre-xmalloc.c 30 | ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/lo/*.c 31 | ) 32 | 33 | list(FILTER CK_SOURCES EXCLUDE REGEX ".*lex.yy.c") 34 | 35 | if(WIN32) 36 | list(FILTER CK_SOURCES EXCLUDE REGEX ".*server_thread.c") 37 | list(FILTER CK_SOURCES EXCLUDE REGEX ".*lo/config.h") 38 | else() 39 | list(FILTER CK_SOURCES EXCLUDE REGEX ".*chuck_yacc.h") 40 | list(FILTER CK_SOURCES EXCLUDE REGEX ".*chuck_yacc.c") 41 | list(FILTER CK_SOURCES EXCLUDE REGEX ".*core/regex/*") 42 | list(APPEND CK_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/chuck/src/core/chuck.yy.c") 43 | endif() 44 | 45 | file(GLOB TOUCHDESIGNER_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/TouchDesigner/*) 46 | 47 | project(ChucKDesignerShared VERSION ${VERSION}) 48 | 49 | add_library(ChucKDesignerShared SHARED 50 | ${CK_SOURCES} 51 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Plugin_ChucK.h 52 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Plugin_ChucK.cpp 53 | ${TOUCHDESIGNER_SOURCES} 54 | ) 55 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) 56 | 57 | # Include header directories 58 | target_include_directories(ChucKDesignerShared PRIVATE 59 | $ 60 | $ 61 | $ 62 | ) 63 | if(APPLE) 64 | target_include_directories(ChucKDesignerShared PRIVATE 65 | $ 66 | ) 67 | 68 | # Link MultitouchSupport Apple framework 69 | find_library( multitouchsupportlibs MultitouchSupport /System/Library/PrivateFrameworks ) 70 | message(STATUS "Found multitouchsupportlibs libs: ${multitouchsupportlibs}" ) 71 | target_link_libraries(${PROJECT_NAME} PRIVATE ${multitouchsupportlibs}) 72 | else() 73 | target_include_directories(ChucKDesignerShared PRIVATE 74 | $ 75 | ) 76 | endif() 77 | 78 | target_compile_definitions(${PROJECT_NAME} PRIVATE 79 | "$<$:" 80 | "_DEBUG" 81 | ">" 82 | "$<$:" 83 | "NDEBUG" 84 | ">" 85 | "NDEBUG" 86 | "_USRDLL" 87 | "HAVE_CONFIG_H;" 88 | "CHUCKDESIGNERSHARED_EXPORTS" 89 | ) 90 | 91 | if(WIN32) 92 | target_compile_definitions(${PROJECT_NAME} PRIVATE 93 | "WIN32;" 94 | "__PLATFORM_WINDOWS__;" 95 | "__PLATFORM_WIN32__;" 96 | "__WINDOWS_MODERN__;" 97 | "__WINDOWS_DS__;" 98 | "_WINDOWS;" 99 | ) 100 | # win sock 32 101 | target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32) 102 | # windows multimedia for rt midi 103 | target_link_libraries(${PROJECT_NAME} PRIVATE winmm) 104 | # more 105 | target_link_libraries(${PROJECT_NAME} PRIVATE wsock32) 106 | target_link_libraries(${PROJECT_NAME} PRIVATE dsound dinput8 dxguid) 107 | target_link_libraries(${PROJECT_NAME} PRIVATE iphlpapi) 108 | else() 109 | target_compile_definitions(${PROJECT_NAME} PRIVATE 110 | "__PLATFORM_APPLE__;" 111 | "__MACOSX_CORE__;" 112 | "__APPLE__;" 113 | "__MACH__;" 114 | ) 115 | target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation" "-framework CoreMIDI" "-framework CoreAudio" "-framework IOKit" "-framework Carbon" "-framework AppKit" "-framework Foundation") 116 | endif() 117 | 118 | if (MSVC) 119 | add_custom_command(TARGET ${PROJECT_NAME} 120 | POST_BUILD 121 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 122 | "$" 123 | ${CMAKE_SOURCE_DIR}/Plugins 124 | # "%USERPROFILE%/Documents/Derivative/Plugins" 125 | ) 126 | endif() 127 | 128 | ###################### ChucKListenerCHOP 129 | 130 | project(ChucKListenerCHOP VERSION ${VERSION}) 131 | 132 | add_library(ChucKListenerCHOP MODULE 133 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ChucKListenerCHOP.h 134 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ChucKListenerCHOP.cpp 135 | ${TOUCHDESIGNER_SOURCES} 136 | ) 137 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) 138 | set_target_properties(${PROJECT_NAME} PROPERTIES 139 | BUNDLE true 140 | BUNDLE_EXTENSION "plugin" 141 | INTERPROCEDURAL_OPTIMIZATION_RELEASE "TRUE" 142 | PRODUCT_BUNDLE_IDENTIFIER design.dirt.cpp.${PROJECT_NAME} 143 | MACOSX_BUNDLE_GUI_IDENTIFIER design.dirt.cpp.${PROJECT_NAME} 144 | MACOSX_BUNDLE_INFO_STRING ${PROJECT_NAME} 145 | MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME} 146 | MACOSX_BUNDLE_BUNDLE_VERSION ${VERSION} 147 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${VERSION} 148 | MACOSX_BUNDLE_COPYRIGHT "David Braun" 149 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/src/Info.plist 150 | XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "/System/Library/PrivateFrameworks /Library/Frameworks" 151 | ) 152 | 153 | # Include header directories 154 | target_include_directories(ChucKListenerCHOP PRIVATE 155 | $ 156 | $ 157 | $ 158 | $ 159 | ) 160 | 161 | set(Python_FIND_REGISTRY "LAST") 162 | set(Python_FIND_STRATEGY "LOCATION") 163 | find_package(Python ${PYTHONVER} EXACT REQUIRED COMPONENTS Interpreter Development) 164 | target_link_libraries(${PROJECT_NAME} PRIVATE Python::Python) 165 | 166 | target_link_libraries(${PROJECT_NAME} PRIVATE ChucKDesignerShared) 167 | 168 | target_compile_definitions(${PROJECT_NAME} PRIVATE 169 | "$<$:" 170 | "_DEBUG" 171 | ">" 172 | "$<$:" 173 | "NDEBUG" 174 | ">" 175 | "NDEBUG" 176 | "HAVE_CONFIG_H;" 177 | "_USRDLL" 178 | ) 179 | 180 | if(WIN32) 181 | target_compile_definitions(${PROJECT_NAME} PRIVATE 182 | "WIN32;" 183 | "__PLATFORM_WIN32__;" 184 | "__WINDOWS_MODERN__;" 185 | "__WINDOWS_DS__;" 186 | "_WINDOWS;" 187 | ) 188 | # win sock 32 189 | target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32) 190 | # windows multimedia for rt midi 191 | target_link_libraries(${PROJECT_NAME} PRIVATE winmm) 192 | # more 193 | target_link_libraries(${PROJECT_NAME} PRIVATE wsock32) 194 | target_link_libraries(${PROJECT_NAME} PRIVATE dsound dinput8 dxguid) 195 | else() 196 | target_compile_definitions(${PROJECT_NAME} PRIVATE 197 | "__MACOSX_CORE__;" 198 | "__APPLE__;" 199 | "__MACH__;" 200 | ) 201 | target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation" "-framework CoreMIDI" "-framework CoreAudio") 202 | endif() 203 | 204 | if(MSVC) 205 | add_custom_command(TARGET ${PROJECT_NAME} 206 | POST_BUILD 207 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 208 | "$" 209 | ${CMAKE_SOURCE_DIR}/Plugins 210 | # "%USERPROFILE%/Documents/Derivative/Plugins" 211 | ) 212 | endif() 213 | 214 | ###################### ChucKDesignerCHOP 215 | 216 | project(ChucKDesignerCHOP VERSION ${VERSION}) 217 | 218 | add_library(ChucKDesignerCHOP MODULE 219 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ChucKDesignerCHOP.h 220 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ChucKDesignerCHOP.cpp 221 | ${TOUCHDESIGNER_SOURCES} 222 | ) 223 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) 224 | set_target_properties(${PROJECT_NAME} PROPERTIES 225 | BUNDLE true 226 | BUNDLE_EXTENSION "plugin" 227 | INTERPROCEDURAL_OPTIMIZATION_RELEASE "TRUE" 228 | PRODUCT_BUNDLE_IDENTIFIER design.dirt.cpp.${PROJECT_NAME} 229 | MACOSX_BUNDLE_GUI_IDENTIFIER design.dirt.cpp.${PROJECT_NAME} 230 | MACOSX_BUNDLE_INFO_STRING ${PROJECT_NAME} 231 | MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME} 232 | MACOSX_BUNDLE_BUNDLE_VERSION ${VERSION} 233 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${VERSION} 234 | MACOSX_BUNDLE_COPYRIGHT "David Braun" 235 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/src/Info.plist 236 | XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "/System/Library/PrivateFrameworks /Library/Frameworks" 237 | ) 238 | 239 | # Include header directories 240 | target_include_directories(ChucKDesignerCHOP PRIVATE 241 | $ 242 | $ 243 | $ 244 | $ 245 | ) 246 | 247 | set(Python_FIND_REGISTRY "LAST") 248 | set(Python_FIND_STRATEGY "LOCATION") 249 | find_package(Python ${PYTHONVER} EXACT REQUIRED COMPONENTS Interpreter Development) 250 | target_link_libraries(${PROJECT_NAME} PRIVATE Python::Python) 251 | 252 | target_link_libraries(${PROJECT_NAME} PRIVATE ChucKDesignerShared) 253 | 254 | target_compile_definitions(${PROJECT_NAME} PRIVATE 255 | "$<$:" 256 | "_DEBUG" 257 | ">" 258 | "$<$:" 259 | "NDEBUG" 260 | ">" 261 | "NDEBUG" 262 | "HAVE_CONFIG_H" 263 | "_USRDLL" 264 | ) 265 | 266 | if(WIN32) 267 | target_compile_definitions(${PROJECT_NAME} PRIVATE 268 | "WIN32;" 269 | "__PLATFORM_WIN32__;" 270 | "__WINDOWS_MODERN__;" 271 | "__WINDOWS_DS__;" 272 | "_WINDOWS;" 273 | ) 274 | # win sock 32 275 | target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32) 276 | # windows multimedia for rt midi 277 | target_link_libraries(${PROJECT_NAME} PRIVATE winmm) 278 | # more 279 | target_link_libraries(${PROJECT_NAME} PRIVATE wsock32) 280 | target_link_libraries(${PROJECT_NAME} PRIVATE dsound dinput8 dxguid) 281 | else() 282 | target_compile_definitions(${PROJECT_NAME} PRIVATE 283 | "__MACOSX_CORE__;" 284 | "__APPLE__;" 285 | "__MACH__;" 286 | ) 287 | target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation" "-framework CoreMIDI" "-framework CoreAudio") 288 | endif() 289 | 290 | if(MSVC) 291 | add_custom_command(TARGET ${PROJECT_NAME} 292 | POST_BUILD 293 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 294 | "$" 295 | ${CMAKE_SOURCE_DIR}/Plugins 296 | # "%USERPROFILE%/Documents/Derivative/Plugins" 297 | ) 298 | else() 299 | 300 | # These two are for renaming what GitHub actions produces 301 | add_custom_command(TARGET ChucKDesignerCHOP POST_BUILD 302 | COMMAND install_name_tool -change /Library/Frameworks/Python.framework/Versions/${PYTHONVER}/Python @executable_path/../Frameworks/Python.framework/Versions/${PYTHONVER}/Python "$" 303 | ) 304 | add_custom_command(TARGET ChucKListenerCHOP POST_BUILD 305 | COMMAND install_name_tool -change /Library/Frameworks/Python.framework/Versions/${PYTHONVER}/Python @executable_path/../Frameworks/Python.framework/Versions/${PYTHONVER}/Python "$" 306 | ) 307 | 308 | # These two are for renaming what an ordinary mac produces 309 | add_custom_command(TARGET ChucKDesignerCHOP POST_BUILD 310 | COMMAND install_name_tool -change @rpath/Python.framework/Versions/${PYTHONVER}/Python @executable_path/../Frameworks/Python.framework/Versions/${PYTHONVER}/Python "$" 311 | ) 312 | add_custom_command(TARGET ChucKListenerCHOP POST_BUILD 313 | COMMAND install_name_tool -change @rpath/Python.framework/Versions/${PYTHONVER}/Python @executable_path/../Frameworks/Python.framework/Versions/${PYTHONVER}/Python "$" 314 | ) 315 | 316 | # libChucKDesignerShared replacements 317 | add_custom_command(TARGET ChucKDesignerCHOP 318 | POST_BUILD 319 | COMMAND install_name_tool -change @rpath/libChucKDesignerShared.dylib @loader_path/../../../libChucKDesignerShared.dylib "$" 320 | ) 321 | add_custom_command(TARGET ChucKListenerCHOP 322 | POST_BUILD 323 | COMMAND install_name_tool -change @rpath/libChucKDesignerShared.dylib @loader_path/../../../libChucKDesignerShared.dylib "$" 324 | ) 325 | endif() 326 | 327 | set_target_properties(${PROJECT_NAME} PROPERTIES 328 | VS_DEBUGGER_COMMAND "C:\\Program Files\\Derivative\\TouchDesigner\\bin\\TouchDesigner.exe" 329 | VS_DEBUGGER_COMMAND_ARGUMENTS "..\\ChucKDesigner.toe") 330 | set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) 331 | -------------------------------------------------------------------------------- /ChucKDesigner.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DBraun/ChucKDesigner/f5ed4284efecedef2d177dea733f53afde22f8ac/ChucKDesigner.toe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ChucKDesigner 2 | Copyright (C) 2022 David Braun 3 | --------------------------------------------------------------------- 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | 18 | ---------------------------------------------------------------------------- 19 | 20 | GNU GENERAL PUBLIC LICENSE 21 | Version 2, June 1991 22 | 23 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 24 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 25 | Everyone is permitted to copy and distribute verbatim copies 26 | of this license document, but changing it is not allowed. 27 | 28 | Preamble 29 | 30 | The licenses for most software are designed to take away your 31 | freedom to share and change it. By contrast, the GNU General Public 32 | License is intended to guarantee your freedom to share and change free 33 | software--to make sure the software is free for all its users. This 34 | General Public License applies to most of the Free Software 35 | Foundation's software and to any other program whose authors commit to 36 | using it. (Some other Free Software Foundation software is covered by 37 | the GNU Library General Public License instead.) You can apply it to 38 | your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Our General Public Licenses are designed to make sure that you 42 | have the freedom to distribute copies of free software (and charge for 43 | this service if you wish), that you receive source code or can get it 44 | if you want it, that you can change the software or use pieces of it 45 | in new free programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must show them these terms so they know their 56 | rights. 57 | 58 | We protect your rights with two steps: (1) copyright the software, and 59 | (2) offer you this license which gives you legal permission to copy, 60 | distribute and/or modify the software. 61 | 62 | Also, for each author's protection and ours, we want to make certain 63 | that everyone understands that there is no warranty for this free 64 | software. If the software is modified by someone else and passed on, we 65 | want its recipients to know that what they have is not the original, so 66 | that any problems introduced by others will not reflect on the original 67 | authors' reputations. 68 | 69 | Finally, any free program is threatened constantly by software 70 | patents. We wish to avoid the danger that redistributors of a free 71 | program will individually obtain patent licenses, in effect making the 72 | program proprietary. To prevent this, we have made it clear that any 73 | patent must be licensed for everyone's free use or not licensed at all. 74 | 75 | The precise terms and conditions for copying, distribution and 76 | modification follow. 77 | 78 | GNU GENERAL PUBLIC LICENSE 79 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 80 | 81 | 0. This License applies to any program or other work which contains 82 | a notice placed by the copyright holder saying it may be distributed 83 | under the terms of this General Public License. The "Program", below, 84 | refers to any such program or work, and a "work based on the Program" 85 | means either the Program or any derivative work under copyright law: 86 | that is to say, a work containing the Program or a portion of it, 87 | either verbatim or with modifications and/or translated into another 88 | language. (Hereinafter, translation is included without limitation in 89 | the term "modification".) Each licensee is addressed as "you". 90 | 91 | Activities other than copying, distribution and modification are not 92 | covered by this License; they are outside its scope. The act of 93 | running the Program is not restricted, and the output from the Program 94 | is covered only if its contents constitute a work based on the 95 | Program (independent of having been made by running the Program). 96 | Whether that is true depends on what the Program does. 97 | 98 | 1. You may copy and distribute verbatim copies of the Program's 99 | source code as you receive it, in any medium, provided that you 100 | conspicuously and appropriately publish on each copy an appropriate 101 | copyright notice and disclaimer of warranty; keep intact all the 102 | notices that refer to this License and to the absence of any warranty; 103 | and give any other recipients of the Program a copy of this License 104 | along with the Program. 105 | 106 | You may charge a fee for the physical act of transferring a copy, and 107 | you may at your option offer warranty protection in exchange for a fee. 108 | 109 | 2. You may modify your copy or copies of the Program or any portion 110 | of it, thus forming a work based on the Program, and copy and 111 | distribute such modifications or work under the terms of Section 1 112 | above, provided that you also meet all of these conditions: 113 | 114 | a) You must cause the modified files to carry prominent notices 115 | stating that you changed the files and the date of any change. 116 | 117 | b) You must cause any work that you distribute or publish, that in 118 | whole or in part contains or is derived from the Program or any 119 | part thereof, to be licensed as a whole at no charge to all third 120 | parties under the terms of this License. 121 | 122 | c) If the modified program normally reads commands interactively 123 | when run, you must cause it, when started running for such 124 | interactive use in the most ordinary way, to print or display an 125 | announcement including an appropriate copyright notice and a 126 | notice that there is no warranty (or else, saying that you provide 127 | a warranty) and that users may redistribute the program under 128 | these conditions, and telling the user how to view a copy of this 129 | License. (Exception: if the Program itself is interactive but 130 | does not normally print such an announcement, your work based on 131 | the Program is not required to print an announcement.) 132 | 133 | These requirements apply to the modified work as a whole. If 134 | identifiable sections of that work are not derived from the Program, 135 | and can be reasonably considered independent and separate works in 136 | themselves, then this License, and its terms, do not apply to those 137 | sections when you distribute them as separate works. But when you 138 | distribute the same sections as part of a whole which is a work based 139 | on the Program, the distribution of the whole must be on the terms of 140 | this License, whose permissions for other licensees extend to the 141 | entire whole, and thus to each and every part regardless of who wrote it. 142 | 143 | Thus, it is not the intent of this section to claim rights or contest 144 | your rights to work written entirely by you; rather, the intent is to 145 | exercise the right to control the distribution of derivative or 146 | collective works based on the Program. 147 | 148 | In addition, mere aggregation of another work not based on the Program 149 | with the Program (or with a work based on the Program) on a volume of 150 | a storage or distribution medium does not bring the other work under 151 | the scope of this License. 152 | 153 | 3. You may copy and distribute the Program (or a work based on it, 154 | under Section 2) in object code or executable form under the terms of 155 | Sections 1 and 2 above provided that you also do one of the following: 156 | 157 | a) Accompany it with the complete corresponding machine-readable 158 | source code, which must be distributed under the terms of Sections 159 | 1 and 2 above on a medium customarily used for software interchange; or, 160 | 161 | b) Accompany it with a written offer, valid for at least three 162 | years, to give any third party, for a charge no more than your 163 | cost of physically performing source distribution, a complete 164 | machine-readable copy of the corresponding source code, to be 165 | distributed under the terms of Sections 1 and 2 above on a medium 166 | customarily used for software interchange; or, 167 | 168 | c) Accompany it with the information you received as to the offer 169 | to distribute corresponding source code. (This alternative is 170 | allowed only for noncommercial distribution and only if you 171 | received the program in object code or executable form with such 172 | an offer, in accord with Subsection b above.) 173 | 174 | The source code for a work means the preferred form of the work for 175 | making modifications to it. For an executable work, complete source 176 | code means all the source code for all modules it contains, plus any 177 | associated interface definition files, plus the scripts used to 178 | control compilation and installation of the executable. However, as a 179 | special exception, the source code distributed need not include 180 | anything that is normally distributed (in either source or binary 181 | form) with the major components (compiler, kernel, and so on) of the 182 | operating system on which the executable runs, unless that component 183 | itself accompanies the executable. 184 | 185 | If distribution of executable or object code is made by offering 186 | access to copy from a designated place, then offering equivalent 187 | access to copy the source code from the same place counts as 188 | distribution of the source code, even though third parties are not 189 | compelled to copy the source along with the object code. 190 | 191 | 4. You may not copy, modify, sublicense, or distribute the Program 192 | except as expressly provided under this License. Any attempt 193 | otherwise to copy, modify, sublicense or distribute the Program is 194 | void, and will automatically terminate your rights under this License. 195 | However, parties who have received copies, or rights, from you under 196 | this License will not have their licenses terminated so long as such 197 | parties remain in full compliance. 198 | 199 | 5. You are not required to accept this License, since you have not 200 | signed it. However, nothing else grants you permission to modify or 201 | distribute the Program or its derivative works. These actions are 202 | prohibited by law if you do not accept this License. Therefore, by 203 | modifying or distributing the Program (or any work based on the 204 | Program), you indicate your acceptance of this License to do so, and 205 | all its terms and conditions for copying, distributing or modifying 206 | the Program or works based on it. 207 | 208 | 6. Each time you redistribute the Program (or any work based on the 209 | Program), the recipient automatically receives a license from the 210 | original licensor to copy, distribute or modify the Program subject to 211 | these terms and conditions. You may not impose any further 212 | restrictions on the recipients' exercise of the rights granted herein. 213 | You are not responsible for enforcing compliance by third parties to 214 | this License. 215 | 216 | 7. If, as a consequence of a court judgment or allegation of patent 217 | infringement or for any other reason (not limited to patent issues), 218 | conditions are imposed on you (whether by court order, agreement or 219 | otherwise) that contradict the conditions of this License, they do not 220 | excuse you from the conditions of this License. If you cannot 221 | distribute so as to satisfy simultaneously your obligations under this 222 | License and any other pertinent obligations, then as a consequence you 223 | may not distribute the Program at all. For example, if a patent 224 | license would not permit royalty-free redistribution of the Program by 225 | all those who receive copies directly or indirectly through you, then 226 | the only way you could satisfy both it and this License would be to 227 | refrain entirely from distribution of the Program. 228 | 229 | If any portion of this section is held invalid or unenforceable under 230 | any particular circumstance, the balance of the section is intended to 231 | apply and the section as a whole is intended to apply in other 232 | circumstances. 233 | 234 | It is not the purpose of this section to induce you to infringe any 235 | patents or other property right claims or to contest validity of any 236 | such claims; this section has the sole purpose of protecting the 237 | integrity of the free software distribution system, which is 238 | implemented by public license practices. Many people have made 239 | generous contributions to the wide range of software distributed 240 | through that system in reliance on consistent application of that 241 | system; it is up to the author/donor to decide if he or she is willing 242 | to distribute software through any other system and a licensee cannot 243 | impose that choice. 244 | 245 | This section is intended to make thoroughly clear what is believed to 246 | be a consequence of the rest of this License. 247 | 248 | 8. If the distribution and/or use of the Program is restricted in 249 | certain countries either by patents or by copyrighted interfaces, the 250 | original copyright holder who places the Program under this License 251 | may add an explicit geographical distribution limitation excluding 252 | those countries, so that distribution is permitted only in or among 253 | countries not thus excluded. In such case, this License incorporates 254 | the limitation as if written in the body of this License. 255 | 256 | 9. The Free Software Foundation may publish revised and/or new versions 257 | of the General Public License from time to time. Such new versions will 258 | be similar in spirit to the present version, but may differ in detail to 259 | address new problems or concerns. 260 | 261 | Each version is given a distinguishing version number. If the Program 262 | specifies a version number of this License which applies to it and "any 263 | later version", you have the option of following the terms and conditions 264 | either of that version or of any later version published by the Free 265 | Software Foundation. If the Program does not specify a version number of 266 | this License, you may choose any version ever published by the Free Software 267 | Foundation. 268 | 269 | 10. If you wish to incorporate parts of the Program into other free 270 | programs whose distribution conditions are different, write to the author 271 | to ask for permission. For software which is copyrighted by the Free 272 | Software Foundation, write to the Free Software Foundation; we sometimes 273 | make exceptions for this. Our decision will be guided by the two goals 274 | of preserving the free status of all derivatives of our free software and 275 | of promoting the sharing and reuse of software generally. 276 | 277 | NO WARRANTY 278 | 279 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 280 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 281 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 282 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 283 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 284 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 285 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 286 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 287 | REPAIR OR CORRECTION. 288 | 289 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 290 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 291 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 292 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 293 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 294 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 295 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 296 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 297 | POSSIBILITY OF SUCH DAMAGES. 298 | 299 | END OF TERMS AND CONDITIONS 300 | 301 | How to Apply These Terms to Your New Programs 302 | 303 | If you develop a new program, and you want it to be of the greatest 304 | possible use to the public, the best way to achieve this is to make it 305 | free software which everyone can redistribute and change under these terms. 306 | 307 | To do so, attach the following notices to the program. It is safest 308 | to attach them to the start of each source file to most effectively 309 | convey the exclusion of warranty; and each file should have at least 310 | the "copyright" line and a pointer to where the full notice is found. 311 | 312 | 313 | Copyright (C) 314 | 315 | This program is free software; you can redistribute it and/or modify 316 | it under the terms of the GNU General Public License as published by 317 | the Free Software Foundation; either version 2 of the License, or 318 | (at your option) any later version. 319 | 320 | This program is distributed in the hope that it will be useful, 321 | but WITHOUT ANY WARRANTY; without even the implied warranty of 322 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 323 | GNU General Public License for more details. 324 | 325 | You should have received a copy of the GNU General Public License 326 | along with this program; if not, write to the Free Software 327 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 328 | 329 | 330 | Also add information on how to contact you by electronic and paper mail. 331 | 332 | If the program is interactive, make it output a short notice like this 333 | when it starts in an interactive mode: 334 | 335 | Gnomovision version 69, Copyright (C) year name of author 336 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 337 | This is free software, and you are welcome to redistribute it 338 | under certain conditions; type `show c' for details. 339 | 340 | The hypothetical commands `show w' and `show c' should show the appropriate 341 | parts of the General Public License. Of course, the commands you use may 342 | be called something other than `show w' and `show c'; they could even be 343 | mouse-clicks or menu items--whatever suits your program. 344 | 345 | You should also get your employer (if you work as a programmer) or your 346 | school, if any, to sign a "copyright disclaimer" for the program, if 347 | necessary. Here is a sample; alter the names: 348 | 349 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 350 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 351 | 352 | , 1 April 1989 353 | Ty Coon, President of Vice 354 | 355 | This General Public License does not permit incorporating your program into 356 | proprietary programs. If your program is a subroutine library, you may 357 | consider it more useful to permit linking proprietary applications with the 358 | library. If this is what you want to do, use the GNU Library General 359 | Public License instead of this License. -------------------------------------------------------------------------------- /Plugins/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.dll 3 | *.plugin 4 | *.dylib -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChucKDesigner 2 | 3 | `TouchDesigner => ChucK => TouchDesigner;` 4 | 5 | ChucKDesigner is an integration of the [ChucK](https://chuck.stanford.edu/) music/audio programming language with the [TouchDesigner](https://derivative.ca) visual programming language. With ChucKDesigner, 6 | * TouchDesigner can run ChucK code at any sample rate, with any number of input and output channels. 7 | * TouchDesigner can receive and post-process ChucK's output such as audio or melodic information. 8 | * TouchDesigner can set ChucK global variables with TouchDesigner UI, CHOP streams, and Python scripting. 9 | * TouchDesigner can use Python callbacks to respond to changes in ChucK global variables. 10 | 11 | ChucKDesigner consists of two custom operators, the ChucK Audio CHOP and the ChucK Listener CHOP. The ChucK Audio CHOP "runs" the ChucK code. TouchDesigner can also tell it how to update global variables. The Listener CHOP "listens" to a ChucK Audio CHOP. It can continuously stream out global floats and integers as ordinary CHOP data. Using a Python-based callback DAT, it can also access many more global variable types such as arrays, strings, and events. 12 | 13 | Please use the [issues](https://github.com/DBraun/ChucKDesigner/issues) and [discussions](https://github.com/DBraun/ChucKDesigner/discussions) pages on GitHub. You can also participate in the #ChucKDesigner channel in the [ChucK](https://chuck.stanford.edu/) Discord server. 14 | 15 | ## Tutorials 16 | 17 | Part 1 (Introduction): 18 | 19 | [![Demo Video Screenshot](https://img.youtube.com/vi/TmJQh1lWXso/0.jpg)](https://www.youtube.com/watch?v=TmJQh1lWXso "ChucKDesigner - Music Programming in TouchDesigner") 20 | 21 | Part 2 (Python API): 22 | 23 | [![Make a Drum Sequencer with ChucK in TouchDesigner](https://img.youtube.com/vi/U34PyL_zMJ0/0.jpg)](https://www.youtube.com/watch?v=U34PyL_zMJ0 "Make a Drum Sequencer with ChucK in TouchDesigner") 24 | 25 | ## Installation 26 | 27 | ### ChucK 28 | 29 | ChucKDesigner is currently being built for ChucK 1.5.2.3 for TouchDesigner with 3.11 (2023 builds) and Python 3.9 (2022 builds). If you need to support something else, please check out earlier [releases](https://github.com/DBraun/ChucKDesigner/releases) or commits of this repository. 30 | 31 | [Downloading ChucK](https://chuck.stanford.edu/release/) is optional but highly encouraged! Use miniAudicle and the examples that come with the installation to learn ChucK. More educational resources are available from the [ChucK homepage](https://chuck.stanford.edu/). ChucKDesigner is very similar to [Chunity](https://chuck.stanford.edu/chunity/) (the integration of ChucK with Unity), so you are also encouraged to learn about [Chunity](https://github.com/ccrma/chunity/)! 32 | 33 | ### Windows 34 | 35 | Download and unzip the latest Windows [release](https://github.com/DBraun/ChucKDesigner/releases). Copy the latest `.dll` files to this project's `Plugins` folder or `%USERPROFILE%/Documents/Derivative/Plugins`. That's all! 36 | 37 |
38 | Building on Windows (Optional) 39 |
40 | Clone this repository with git. Then update all submodules in the root of the repository with git submodule update --init --recursive. 41 |
42 | Install Python 3.11 to C:/Python311/ and confirm it's in your system PATH. 43 |
44 | Install CMake and confirm that it's installed by running cmake --version in a command prompt. 45 |
46 | Then in this repository, 47 | 48 | cmake . -DCMAKE_BUILD_TYPE=Release -Bbuild -DPYTHONVER="3.11" 49 | 50 |
51 | Then, cmake --build build --config Release to compile. 52 |
53 | 54 | ### MacOS 55 | 56 | Download and unzip the latest macOS [release](https://github.com/DBraun/ChucKDesigner/releases). Copy the latest `.plugin` and `.dylib` files to this project's `Plugins` folder or `~/Library/Application Support/Derivative/TouchDesigner099/Plugins`. That's all! 57 | 58 | If you'd like to build the ChucKDesigner plugins yourself, these are the instructions: 59 | 60 | 1. Clone this repository with git. Then update all submodules in the root of the repository with `git submodule update --init --recursive` 61 | 2. Install Xcode. 62 | 3. [Install CMake](https://cmake.org/download/) and confirm that it's installed by running `cmake --version` in Terminal. You may need to run `export PATH="/Applications/CMake.app/Contents/bin":"$PATH"` 63 | 4. In a Terminal Window, export a variable to the TouchDesigner.app to which you'd like to support. For example: `export TOUCHDESIGNER_APP=/Applications/TouchDesigner.app`, assuming this version is a 2023 build or higher. 64 | 5. Optional: depending on the Python version associated with the TouchDesigner you intend to use, run `export PYTHONVER=3.11` or `export PYTHONVER=3.9`. 65 | 6. In the same Terminal window, navigate to the root of this repository and run `sh build_macos.sh` 66 | 7. Open `ChucKDesigner.toe` and play around! 67 | 68 | ## API 69 | 70 | ### Python interface to ChucK Audio CHOP 71 | 72 | The ChucK Audio CHOP's getter methods: 73 | 74 | * `.get_float(name: str) -> float` 75 | * `.get_int(name: str) -> int` 76 | * `.get_string(name: str) -> str` 77 | * `.get_float_array(name: str) -> List[float]` 78 | * `.get_int_array(name: str) -> List[int]` 79 | 80 | Note that these getters return results with a one-frame delay. They will return `None` the first time they're called. If `None` is returned on later calls, it means that the requested global variable wasn't found. 81 | 82 | The ChucK Audio CHOP's setter methods: 83 | 84 | * `.set_float(name: str, val: float)` 85 | * `.set_int(name: str, val: int)` 86 | * `.set_string(name: str, val: int)` 87 | * `.set_float_array(name: str, vals: List[float])` **not numpy arrays!** 88 | * `.set_int_array(name: str, vals: List[int])` **not numpy arrays!** 89 | * `.set_float_array_value(name: str, index: int, val: float)` 90 | * `.set_int_array_value(name: str, index: int, val: int)` 91 | * `.set_associative_float_array_value(name: str, key: str, val: float)` 92 | * `.set_associative_int_array_value(name: str, key: str, val: int)` 93 | * `.broadcast_event(name: str)` 94 | * `.set_log_level(level: int)` **0 is None and 10 is "Crazy"** 95 | 96 | ### Example 97 | 98 | Suppose the ChucK Audio CHOP has compiled this code: 99 | 100 | ```chuck 101 | 440. => global float freq; 102 | 103 | SinOsc s => dac; 104 | 105 | while(true) { 106 | freq => s.freq; 107 | 10::ms => now; 108 | } 109 | ``` 110 | 111 | This can be seen in the following image: 112 | 113 | ![Float Example TouchDesigner Screenshot](docs/float_example.png?raw=true "Float Example TouchDesigner Screenshot") 114 | 115 | In TouchDesigner, we can execute the Python code 116 | `op('chuckaudio1').set_float("freq", 880.)` 117 | This will set the global float variable named `freq` to 880. The code has been written to update the sine oscillator's frequency every 10 milliseconds, so you will immediately hear a change in the frequency. Note that the code below would **not** have led to a change in sound. 118 | 119 | ```chuck 120 | SinOsc s => dac; 121 | 122 | 440. => global float freq => s.freq; 123 | 124 | while(true) { 125 | 10::ms => now; 126 | } 127 | ``` 128 | 129 | The reason is that global variables are not [Unit Generators](https://chuck.stanford.edu/doc/program/ugen.html). Although `freq` has been chucked to `s.freq`, we still need code in the `while(true)` loop to update the oscillator's frequency. 130 | 131 | ### Streaming global floats and integers in ChucK Listener CHOP 132 | 133 | The ChucK Listener has a custom parameter for the ChucK Audio CHOP to which it should listen. Suppose we had used a ChucK Audio CHOP to compile this: 134 | 135 | ```chuck 136 | 440. => global float freq; 137 | 0 => global int randInt; 138 | 139 | SinOsc s => dac; 140 | 141 | while(true) { 142 | freq => s.freq; 143 | Std.rand2(0, 10) => randInt; 144 | 10::ms => now; 145 | } 146 | ``` 147 | 148 | On the ChucK Audio Listener, there is a custom parameter for "Float Variables". In this field you can type any number of global variables, each separated by a single space. In this example, there's just one global float, so you can type `freq`. Similarly, in the "Int Variables" custom parameter, you can type `randInt`. The ChucK Listener will then output as ordinary CHOP information a single-sample with one channel named `freq` and another channel named `randInt`. The ordinary CHOP output of the Listener CHOP is only for global floats and global integers. However, all variable types can be received as Python callbacks, as explained in the next section. 149 | 150 | ### Python Callbacks in ChucK Listener CHOP 151 | 152 | In the example above, we used a `global float freq` and a `global int randInt`. Find the `Float Variables` and `Int Variables` custom parameters on the ChucK Listener CHOP and set them to `freq` and `randInt` respectively. Now `freq` will appear in the `getFloat` callback, and `randInt` will appear in the `getInt` callback. 153 | 154 | ```python 155 | # This is an example callbacks DAT for a ChucK Listener operator. 156 | # In all callback methods, "listener" is the ChucK Listener operator. 157 | 158 | def getFloat(listener, name, val): 159 | print(f'getFloat(name="{name}", val={val})') 160 | 161 | def getInt(listener, name, val): 162 | print(f'getInt(name="{name}", val={val})') 163 | 164 | def getString(listener, name, val): 165 | print(f'getString(name="{name}", val={val})') 166 | 167 | def getEvent(listener, name): 168 | print(f'getEvent(name="{name}")') 169 | 170 | def getFloatArray(listener, name, vals): 171 | print(f'getFloatArray(name="{name}", vals={vals})') 172 | 173 | def getIntArray(listener, name, vals): 174 | print(f'getIntArray(name="{name}", vals={vals})') 175 | ``` 176 | 177 | Most of these callbacks are straightforward to understand, but the `Event` type syntax is worth discussing. Let this be the compiled ChucK code: 178 | 179 | ```chuck 180 | global Event pulse; 181 | global Event notifier; 182 | 183 | fun void playImpact() { 184 | SndBuf buf => dac; 185 | "special:dope" => buf.read; 186 | 187 | // chuck enough time so that the buf plays 188 | 1::second => now; 189 | 190 | // invoke getEvent("notifier") in TouchDesigner 191 | notifier.broadcast(); 192 | } 193 | 194 | while( true ) { 195 | pulse => now; 196 | spork ~ playImpact(); 197 | } 198 | ``` 199 | 200 | At the beginning, there will be 2 channels of silent output by default. The line `pulse => now;` consumes time until we broadcast an event in TouchDesigner with Python: 201 | ```python 202 | op('chuckaudio1').broadcast_event('pulse') 203 | ``` 204 | 205 | The ChucK code will then spork a shred of `playImpact()`, which will play a short sound. After 1 second of playing the sound, ChucK will broadcast an event named `notifier` back to TouchDesigner. This event will show up in the `getEvent()` callback method, if `notifier` is in the ChucK Listener's custom parameter "Event Variables". 206 | 207 | 208 | ### Chugins 209 | 210 | ChucKDesigner supports [Chugins](https://github.com/ccrma/chugins/), which are custom pre-compiled ChucK "plugins". The chugin should be located in a subfolder named "Chugins" of the "working directory" custom parameter on the ChucK Audio CHOP. For example, if the working directory parameter is `assets`, then the chugins should be in `assets/Chugins`. 211 | -------------------------------------------------------------------------------- /assets/.gitignore: -------------------------------------------------------------------------------- 1 | bitKlavierGrand_PianoBar 2 | bass slide.wav 3 | Closed Hat.wav 4 | Crash2.wav 5 | House_Clap.wav 6 | kick.wav 7 | snare2.wav 8 | WaterDrop.wav -------------------------------------------------------------------------------- /assets/60988__folktelemetry__crash-fast-14.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DBraun/ChucKDesigner/f5ed4284efecedef2d177dea733f53afde22f8ac/assets/60988__folktelemetry__crash-fast-14.wav -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | [CC0 1.0 Universal (CC0 1.0) 2 | Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/) applies to the following files: 3 | * [60988__folktelemetry__crash-fast-14.wav](https://freesound.org/people/folktelemetry/sounds/60988/) -------------------------------------------------------------------------------- /assets/advanced_128_channels_out.ck: -------------------------------------------------------------------------------- 1 | Step s[128] => dac; 2 | 3 | while( true ) 4 | { 5 | for (0 => int i; i<128; i++) { 6 | Std.randf() => s[i].next; 7 | } 8 | 9 | 10::ms => now; 10 | } -------------------------------------------------------------------------------- /assets/advanced_fauck_flute.ck: -------------------------------------------------------------------------------- 1 | // name: flute.ck 2 | // desc: demo of Faust chugin in action! 3 | 4 | // instantiate and connect faust => ck 5 | Faust flute => dac; 6 | 7 | // evaluate Faust code 8 | flute.eval(` 9 | process = pm.flute_ui_MIDI <: _,_; 10 | `); 11 | 12 | // dump parameters 13 | flute.dump(); 14 | 15 | // time loop 16 | while( true ) 17 | { 18 | flute.v("/flute/gate",1); // start note 19 | flute.v("/flute/midi/freq", Math.random2f(100,800) ); // assign pitch 20 | 300::ms => now; // "sustain" 21 | flute.v("/flute/gate",0); // end note 22 | 100::ms => now; // give it some time to "breath" 23 | } -------------------------------------------------------------------------------- /assets/advanced_fauck_reverb.ck: -------------------------------------------------------------------------------- 1 | // name: reverb.ck 2 | // desc: demo of Faust chugin in action! 3 | 4 | // instantiate and connect faust => ck 5 | adc => Faust reverb => dac; 6 | 7 | // evaluate Faust code 8 | reverb.eval(` 9 | process = dm.zita_light ; 10 | `); 11 | 12 | // print the parameters of the Faust object 13 | reverb.dump(); 14 | 15 | 1. => global float Faust_reverb_mix; 16 | 17 | // time loop 18 | while( true ) 19 | { 20 | reverb.v("/Zita Light/Dry/Wet Mix", Faust_reverb_mix); 21 | // advance time 22 | 10::ms => now; 23 | } -------------------------------------------------------------------------------- /assets/advanced_global_float_example.ck: -------------------------------------------------------------------------------- 1 | SinOsc foo => dac; 2 | 3 | 300. => global float freq => foo.freq; 4 | 5 | while( true ) 6 | { 7 | freq => foo.freq; 8 | 9 | 10::ms => now; 10 | } -------------------------------------------------------------------------------- /assets/advanced_global_float_random_out.ck: -------------------------------------------------------------------------------- 1 | SinOsc foo => dac; 2 | 3 | 440. => foo.freq; 4 | 5 | Math.random2f(.2,.5) => global float randFloat; 6 | 7 | while( true ) 8 | { 9 | Math.random2f(.0,1.0) => randFloat; 10 | 11 | 10::ms => now; 12 | } -------------------------------------------------------------------------------- /assets/basic_adsr.ck: -------------------------------------------------------------------------------- 1 | // an ADSR envelope 2 | // (also see envelope.ck) 3 | SinOsc s => ADSR e => dac; 4 | 5 | // set a, d, s, and r 6 | e.set( 10::ms, 8::ms, .5, 500::ms ); 7 | // set gain 8 | .5 => s.gain; 9 | 10 | // infinite time-loop 11 | while( true ) 12 | { 13 | // choose freq 14 | Math.random2( 20, 120 ) => Std.mtof => s.freq; 15 | 16 | // key on - start attack 17 | e.keyOn(); 18 | // advance time by 800 ms 19 | 500::ms => now; 20 | // key off - start release 21 | e.keyOff(); 22 | // advance time by 800 ms 23 | 800::ms => now; 24 | } 25 | -------------------------------------------------------------------------------- /assets/basic_alarm.ck: -------------------------------------------------------------------------------- 1 | // how long 2 | 2::hour => dur T; 3 | // frequency 4 | 880 => float f; 5 | 6 | // remember 7 | now => time start; 8 | now + T => time later; 9 | 10 | // wait 11 | while( now < later ) 12 | { 13 | <<< (T - (now - start)) / second, "left..." >>>; 14 | 1::second => now; 15 | } 16 | 17 | // patch 18 | SinOsc s => JCRev r => dac; 19 | .025 => r.mix; 20 | f => s.freq; 21 | 22 | // infinite while loop 23 | while( true ) 24 | { 25 | // go 26 | 1.0 => s.gain; 27 | 300::ms => now; 28 | // stop 29 | 0.0 => s.gain; 30 | 300::ms => now; 31 | } 32 | -------------------------------------------------------------------------------- /assets/basic_array_melody.ck: -------------------------------------------------------------------------------- 1 | // Connect a Sine oscillator to the digital-to-audio-converter 2 | SinOsc sine => dac; 3 | 4 | // an array of some notes in a scale 5 | [ 0, 2, 4, 7, 9, 11 ] @=> int scale[]; 6 | 7 | // infinite time loop 8 | while( true ) 9 | { 10 | scale[Math.random2(0,scale.size()-1)] => int randomNote; 11 | 12 | 33 +=> randomNote; 13 | 14 | // use midi-to-frequency function 15 | Math.random2(0,3) * 12 + randomNote => Std.mtof => sine.freq; 16 | //sine.freq(Std.mtof(33 + Math.random2(0,3) * 12 + randomNote)); 17 | 18 | // chuck time to now. 19 | 100::ms => now; 20 | } -------------------------------------------------------------------------------- /assets/basic_blit.ck: -------------------------------------------------------------------------------- 1 | // patch 2 | Blit s => JCRev r => dac; 3 | .5 => s.gain; 4 | .05 => r.mix; 5 | 6 | // an array 7 | [ 0, 2, 4, 7, 9, 11 ] @=> int hi[]; 8 | // <<< hi.size() >>>; 9 | // <<< hi[0], hi[1], hi[2], hi[3], hi[4], hi[5] >>>; 10 | 11 | // infinite time loop 12 | while( true ) 13 | { 14 | // frequency 15 | Std.mtof( 33 + Math.random2(0,3) * 12 + 16 | hi[Math.random2(0,hi.size()-1)] ) => s.freq; 17 | 18 | // harmonics 19 | Math.random2( 1, 5 ) => s.harmonics; 20 | 21 | // advance time 22 | 120::ms => now; 23 | } 24 | -------------------------------------------------------------------------------- /assets/basic_blit2.ck: -------------------------------------------------------------------------------- 1 | // patch 2 | Blit s => ADSR e => JCRev r => dac; 3 | .5 => s.gain; 4 | .05 => r.mix; 5 | 6 | // set adsr 7 | e.set( 5::ms, 3::ms, .5, 5::ms ); 8 | 9 | // an array 10 | [ 0, 2, 4, 7, 9, 11 ] @=> int hi[]; 11 | 12 | // infinite time loop 13 | while( true ) 14 | { 15 | // frequency 16 | Std.mtof( 33 + Math.random2(0,3) * 12 + 17 | hi[Math.random2(0,hi.size()-1)] ) => s.freq; 18 | 19 | // harmonics 20 | Math.random2( 1, 5 ) => s.harmonics; 21 | 22 | // key on 23 | e.keyOn(); 24 | // advance time 25 | 120::ms => now; 26 | // key off 27 | e.keyOff(); 28 | // advance time 29 | 5::ms => now; 30 | } 31 | -------------------------------------------------------------------------------- /assets/basic_delay.ck: -------------------------------------------------------------------------------- 1 | // patch 2 | adc => DelayL delay[2] => dac; 3 | adc => dac; 4 | 5 | // set delay parameters 6 | .15::second => delay[0].max => delay[0].delay; 7 | .15::second => delay[1].max => delay[1].delay; 8 | 9 | // infinite time loop 10 | while( true ) 1::second => now; 11 | 12 | -------------------------------------------------------------------------------- /assets/basic_fm.ck: -------------------------------------------------------------------------------- 1 | // FM synthesis by hand 2 | 3 | // carrier 4 | SinOsc c => dac; 5 | // modulator 6 | SinOsc m => blackhole; 7 | 8 | // carrier frequency 9 | 220 => float cf; 10 | // modulator frequency 11 | 550 => float mf => m.freq; 12 | // index of modulation 13 | 200 => float index; 14 | 15 | // time-loop 16 | while( true ) 17 | { 18 | // modulate 19 | cf + (index * m.last()) => c.freq; 20 | // advance time by 1 samp 21 | 1::samp => now; 22 | } 23 | -------------------------------------------------------------------------------- /assets/basic_wind.ck: -------------------------------------------------------------------------------- 1 | // noise generator, biquad filter, dac (audio output) 2 | Noise n => BiQuad f => dac; 3 | // set biquad pole radius 4 | .99 => f.prad; 5 | // set biquad gain 6 | .05 => f.gain; 7 | // set equal zeros 8 | 1 => f.eqzs; 9 | // our float 10 | 0.0 => float t; 11 | 12 | // infinite time-loop 13 | while( true ) 14 | { 15 | // sweep the filter resonant frequency 16 | 100.0 + Std.fabs(Math.sin(t)) * 15000.0 => f.pfreq; 17 | t + .01 => t; 18 | // advance time 19 | 5::ms => now; 20 | } 21 | -------------------------------------------------------------------------------- /assets/chuckdesigner_file_playback.ck: -------------------------------------------------------------------------------- 1 | // sound file 2 | me.dir() + "60988__folktelemetry__crash-fast-14.wav" => string filename; 3 | if( me.args() ) me.arg(0) => filename; 4 | 5 | // 2 channel Sound Buffer 6 | SndBuf2 buf => dac; 7 | 8 | // load the file 9 | filename => buf.read; 10 | 11 | // time loop 12 | while( true ) 13 | { 14 | 0 => buf.pos; 15 | Math.random2f(.2,.5) => buf.gain; 16 | Math.random2f(.5,1.5) => buf.rate; 17 | 500::ms => now; 18 | } 19 | -------------------------------------------------------------------------------- /assets/chuckdesigner_passthrough.ck: -------------------------------------------------------------------------------- 1 | adc => dac; 2 | 3 | // time loop 4 | while( true ) 5 | { 6 | 500::ms => now; 7 | } 8 | -------------------------------------------------------------------------------- /assets/chuckdesigner_ping_pong_delay.ck: -------------------------------------------------------------------------------- 1 | DelayL delay[2] => dac; 2 | 3 | 0.7 => global float gainDecay; 4 | 0.5 => global float delaySec; 5 | 6 | 1.0::second => dur delayMax; 7 | 8 | gainDecay => delay[0].gain => delay[1].gain; 9 | 10 | // feedback between each delay 11 | delay[0] => delay[1]; 12 | delay[1] => delay[0]; 13 | 14 | // set delay parameters 15 | delayMax => delay[0].max => delay[1].max; 16 | 17 | adc => Gain mono => delay[0]; 18 | 19 | mono.gain(.6); 20 | 21 | adc => dac; 22 | 23 | // time loop 24 | while( true ) 25 | { 26 | // use global variables to update parameters 27 | gainDecay => delay[0].gain => delay[1].gain; 28 | delaySec::second => delay[0].delay => delay[1].delay; 29 | 30 | // chuck time 31 | 10::ms => now; 32 | } 33 | -------------------------------------------------------------------------------- /assets/chuckdesigner_print_directory.ck: -------------------------------------------------------------------------------- 1 | while (true) { 2 | <<< "working directory: " + me.dir() >>>; 3 | 1000::ms => now; 4 | } -------------------------------------------------------------------------------- /assets/machine_add.ck: -------------------------------------------------------------------------------- 1 | // more music for replicants 2 | 3 | me.dir() + "stk_rhodey.ck" => string filepath; 4 | 5 | <<< "Adding shred: " + filepath >>>; 6 | 7 | Machine.add(filepath); 8 | 9 | // our main loop 10 | while( true ) 11 | { 12 | 100::ms => now; 13 | } 14 | -------------------------------------------------------------------------------- /assets/sequencer.ck: -------------------------------------------------------------------------------- 1 | // number of tracks 2 | 7 => int NUM_TRACKS; 3 | 4 | // TouchDesigner sets these int arrays, one per track 5 | global int sequence0[4]; 6 | global int sequence1[4]; 7 | global int sequence2[4]; 8 | global int sequence3[4]; 9 | global int sequence4[8]; 10 | global int sequence5[8]; 11 | global int sequence6[16]; 12 | 13 | // ChucK will own the following global variables, 14 | // and TouchDesigner will receive them in callbacks 15 | global Event notifier0; 16 | global Event notifier1; 17 | global Event notifier2; 18 | global Event notifier3; 19 | global Event notifier4; 20 | global Event notifier5; 21 | global Event notifier6; 22 | 23 | global float adsr_curves[NUM_TRACKS]; 24 | 25 | // playheadPos loops between 0 and 1 every measure 26 | global float playheadPos; 27 | 28 | // Pick your own beats per minute. 29 | 110. => float BPM; 30 | 31 | // Our video rate is 60 frames per second, 32 | // so we pick something less than (1000./60.)=16.6 ms 33 | 10::ms => dur POS_RATE; 34 | 35 | // MEASURE_SEC is the amount of seconds in 4 beats 36 | (60. * 4./ BPM)::second => dur MEASURE_SEC; 37 | 38 | // posInc is how much playheadPos needs to increment 39 | // when now advances by POS_RATE 40 | POS_RATE/MEASURE_SEC => float posInc; 41 | 42 | class Track { 43 | 44 | string myName; 45 | int mySeq[]; 46 | 47 | SndBuf buf => Pan2 outlet; 48 | 49 | 0 => int trackID; 50 | 51 | int previousZone; 52 | float previousPlayhead; 53 | 54 | -1 => previousZone; 55 | 56 | Event myNotifier; 57 | 58 | // ADSR adsr => blackhole; 59 | // In order for the adsr to "cook", 60 | // it needs something driving it, 61 | // so we'll use a Step UGen whose value is set to 1. 62 | Step step => ADSR adsr => blackhole; 63 | 1. => step.next; 64 | 65 | .0625::second => dur minSustain; 66 | 67 | time offTime; 68 | 69 | fun void setup(int id, string name, string file_path, 70 | int seq[], Event notifier) { 71 | id => trackID; 72 | name => myName; 73 | seq @=> mySeq; 74 | me.dir() + file_path => buf.read; 75 | 0 => buf.pos; 76 | 0. => buf.rate; 77 | notifier @=> myNotifier; 78 | 79 | (62.5::ms, 20::ms, 1., 62.5::ms) => adsr.set; 80 | 0.2 => buf.gain; 81 | } 82 | 83 | fun void play() { 84 | 85 | while (true) { 86 | 87 | (playheadPos * mySeq.size()) $ int => int newZone; 88 | 89 | if (newZone != previousZone || previousPlayhead > playheadPos) { 90 | if (mySeq[newZone]) { 91 | // <<< "Note! " + myName>>>; 92 | now + minSustain => offTime; 93 | adsr.keyOn(); 94 | 0 => buf.pos; 95 | 1. => buf.rate; 96 | myNotifier.broadcast(); 97 | } 98 | } 99 | 100 | if (now >= offTime) { 101 | adsr.keyOff(); 102 | } 103 | 104 | newZone => previousZone; 105 | playheadPos => previousPlayhead; 106 | 107 | adsr.last() => adsr_curves[trackID]; 108 | 109 | // <<< adsr.last() >>>; 110 | 111 | POS_RATE => now; 112 | } 113 | } 114 | } 115 | 116 | Track tracks[NUM_TRACKS]; 117 | 118 | tracks[0].setup(0, "track0", "Crash2.wav", sequence0, notifier0); 119 | tracks[1].setup(1, "track1", "House_Clap.wav", sequence1, notifier1); 120 | tracks[2].setup(2, "track2", "bass slide.wav", sequence2, notifier2); 121 | tracks[3].setup(3, "track3", "WaterDrop.wav", sequence3, notifier3); 122 | tracks[4].setup(4, "track4", "kick.wav", sequence4, notifier4); 123 | tracks[5].setup(5, "track5", "House_Clap.wav", sequence5, notifier5); 124 | tracks[6].setup(6, "track6", "Closed Hat.wav", sequence6, notifier6); 125 | 126 | Pan2 allTracks => dac; 127 | 128 | for (int i; i< NUM_TRACKS; i++) { 129 | tracks[i].outlet => allTracks; 130 | spork ~tracks[i].play(); 131 | } 132 | 133 | // updates the global playheadPos with fine granularity, 134 | // for visualizing the playhead smoothly in TouchDesigner 135 | fun void playheadPosUpdate() 136 | { 137 | while( true ) 138 | { 139 | // increment 140 | (playheadPos+posInc) % 1. => playheadPos; 141 | 142 | // <<< playheadPos >>>; 143 | 144 | // advance time 145 | POS_RATE => now; 146 | } 147 | } 148 | 149 | // this function will go forever 150 | playheadPosUpdate(); -------------------------------------------------------------------------------- /assets/sequencer_simple.ck: -------------------------------------------------------------------------------- 1 | // number of tracks 2 | 1 => int NUM_TRACKS; 3 | 4 | // TouchDesigner sets these int arrays, one per track 5 | global float noteVels[16]; 6 | 7 | // ChucK will own the following global variables, 8 | // and TouchDesigner will receive them in callbacks 9 | global Event notifier; 10 | 11 | global float adsr_curves[NUM_TRACKS]; 12 | 13 | // playheadPos loops between 0 and 1 every measure 14 | global float playheadPos; 15 | 16 | // Pick your own beats per minute. 17 | 110. => global float BPM; 18 | 19 | // update BPM once: 20 | // Our video rate is 60 frames per second, 21 | // so we pick something less than (1000./60.)=16.6 ms 22 | 10::ms => dur POS_RATE; 23 | 24 | // MEASURE_SEC is the amount of seconds in 4 beats 25 | (60. * 4./ BPM)::second => dur MEASURE_SEC; 26 | 27 | // posInc is how much playheadPos needs to increment 28 | // when now advances by POS_RATE 29 | POS_RATE/MEASURE_SEC => float posInc; 30 | 10::ms => now; 31 | // end bpm 32 | 33 | fun void updateBPM() { 34 | while(true) { 35 | // Our video rate is 60 frames per second, 36 | // so we pick something less than (1000./60.)=16.6 ms 37 | 10::ms => POS_RATE; 38 | 39 | // MEASURE_SEC is the amount of seconds in 4 beats 40 | (60. * 4./ BPM)::second => MEASURE_SEC; 41 | 42 | // posInc is how much playheadPos needs to increment 43 | // when now advances by POS_RATE 44 | POS_RATE/MEASURE_SEC => posInc; 45 | 10::ms => now; 46 | } 47 | } 48 | 49 | spork ~updateBPM(); 50 | 51 | class Track { 52 | 53 | string myName; 54 | float mySeq[]; 55 | 56 | SndBuf buf => Pan2 outlet; 57 | 58 | 0 => int trackID; 59 | 60 | int previousZone; 61 | float previousPlayhead; 62 | 63 | -1 => previousZone; 64 | 65 | Event myNotifier; 66 | 67 | // ADSR adsr => blackhole; 68 | // In order for the adsr to "cook", 69 | // it needs something driving it, 70 | // so we'll use a Step UGen whose value is set to 1. 71 | Step step => ADSR adsr => blackhole; 72 | 1. => step.next; 73 | 74 | .0625::second => dur minSustain; 75 | 76 | time offTime; 77 | 78 | fun void setup(int id, string name, string file_path, 79 | float seq[], Event notifier) { 80 | id => trackID; 81 | name => myName; 82 | seq @=> mySeq; 83 | me.dir() + file_path => buf.read; 84 | 0 => buf.pos; 85 | 0. => buf.rate; 86 | notifier @=> myNotifier; 87 | 88 | (30.5::ms, 20::ms, 1., 62.5::ms) => adsr.set; 89 | 0.2 => buf.gain; 90 | } 91 | 92 | fun void play() { 93 | 94 | while (true) { 95 | 96 | (playheadPos * mySeq.size()) $ int => int newZone; 97 | 98 | if (newZone != previousZone || previousPlayhead > playheadPos) { 99 | mySeq[newZone] => float vel; 100 | if (vel > 0.5) { 101 | // <<< "Note! " + myName>>>; 102 | now + minSustain => offTime; 103 | vel => buf.gain; 104 | adsr.keyOn(); 105 | 0 => buf.pos; 106 | 1. => buf.rate; 107 | myNotifier.broadcast(); 108 | } 109 | } 110 | 111 | if (now >= offTime) { 112 | adsr.keyOff(); 113 | } 114 | 115 | newZone => previousZone; 116 | playheadPos => previousPlayhead; 117 | 118 | adsr.last() => adsr_curves[trackID]; 119 | 120 | // <<< adsr.last() >>>; 121 | 122 | POS_RATE => now; 123 | } 124 | } 125 | } 126 | 127 | Track tracks[NUM_TRACKS]; 128 | 129 | tracks[0].setup(0, "track0", "Closed Hat.wav", noteVels, notifier); 130 | 131 | Pan2 allTracks => dac; 132 | 133 | for (int i; i< NUM_TRACKS; i++) { 134 | tracks[i].outlet => allTracks; 135 | spork ~tracks[i].play(); 136 | } 137 | 138 | // updates the global playheadPos with fine granularity, 139 | // for visualizing the playhead smoothly in TouchDesigner 140 | fun void playheadPosUpdate() 141 | { 142 | while( true ) 143 | { 144 | // increment 145 | (playheadPos+posInc) % 1. => playheadPos; 146 | 147 | // <<< playheadPos >>>; 148 | 149 | // advance time 150 | POS_RATE => now; 151 | } 152 | } 153 | 154 | // this function will go forever 155 | playheadPosUpdate(); -------------------------------------------------------------------------------- /assets/stk_mandolin.ck: -------------------------------------------------------------------------------- 1 | // STK Mandolin 2 | 3 | // patch 4 | Mandolin m => JCRev r => dac; 5 | .75 => r.gain; 6 | .025 => r.mix; 7 | 8 | // our notes 9 | [ 61, 63, 65, 66, 68, 66, 65, 63 ] @=> int notes[]; 10 | 11 | // infinite time-loop 12 | while( true ) 13 | { 14 | // set 15 | Math.random2f( 0, 1 ) => m.bodySize; 16 | Math.random2f( 0, 1 ) => m.pluckPos; 17 | // Math.random2f( 0, 1 ) => m.stringDamping; 18 | // Math.random2f( 0, 1 ) => m.stringDetune; 19 | 20 | // print 21 | <<< "---", "" >>>; 22 | <<< "body size:", m.bodySize() >>>; 23 | <<< "pluck position:", m.pluckPos() >>>; 24 | <<< "string damping:", m.stringDamping() >>>; 25 | <<< "string detune:", m.stringDetune() >>>; 26 | 27 | // factor 28 | Math.random2f( 1, 4 ) => float factor; 29 | 30 | for( int i; i < notes.size(); i++ ) 31 | { 32 | play( Math.random2(0,2)*12 + notes[i], Math.random2f( .6, .9 ) ); 33 | 100::ms * factor => now; 34 | } 35 | } 36 | 37 | // basic play function (add more arguments as needed) 38 | fun void play( float note, float velocity ) 39 | { 40 | // start the note 41 | Std.mtof( note ) => m.freq; 42 | velocity => m.pluck; 43 | } 44 | -------------------------------------------------------------------------------- /assets/stk_modalbar.ck: -------------------------------------------------------------------------------- 1 | // STK ModalBar 2 | 3 | // patch 4 | ModalBar bar => dac; 5 | 6 | // scale 7 | [0, 2, 4, 7, 8, 11] @=> int scale[]; 8 | 9 | // infinite time loop 10 | while( true ) 11 | { 12 | // ding! 13 | Math.random2( 0, 8 ) => bar.preset; 14 | Math.random2f( 0, 1 ) => bar.stickHardness; 15 | Math.random2f( 0, 1 ) => bar.strikePosition; 16 | Math.random2f( 0, 1 ) => bar.vibratoGain; 17 | Math.random2f( 0, 60 ) => bar.vibratoFreq; 18 | Math.random2f( 0, 1 ) => bar.volume; 19 | Math.random2f( .5, 1 ) => bar.directGain; 20 | Math.random2f( .5, 1 ) => bar.masterGain; 21 | 22 | // print 23 | <<< "---", "" >>>; 24 | <<< "preset:", bar.preset() >>>; 25 | <<< "stick hardness:", bar.stickHardness() >>>; 26 | <<< "strike position:", bar.strikePosition() >>>; 27 | <<< "vibrato gain:", bar.vibratoGain() >>>; 28 | <<< "vibrato freq:", bar.vibratoFreq() >>>; 29 | <<< "volume:", bar.volume() >>>; 30 | <<< "direct gain:", bar.directGain() >>>; 31 | <<< "master gain:", bar.masterGain() >>>; 32 | 33 | // set freq 34 | scale[Math.random2(0,scale.size()-1)] => int winner; 35 | 57 + Math.random2(0,2)*12 + winner => Std.mtof => bar.freq; 36 | // go 37 | .8 => bar.noteOn; 38 | 39 | // advance time 40 | .5::second => now; 41 | } 42 | -------------------------------------------------------------------------------- /assets/stk_rhodey.ck: -------------------------------------------------------------------------------- 1 | // more music for replicants 2 | 3 | // patch 4 | Rhodey voc => JCRev r => Echo a => Echo b => Echo c => dac; 5 | 6 | 220.0 => voc.freq; 7 | 0.8 => voc.gain; 8 | .8 => r.gain; 9 | .2 => r.mix; 10 | 1000::ms => a.max => b.max => c.max; 11 | 750::ms => a.delay => b.delay => c.delay; 12 | .50 => a.mix => b.mix => c.mix; 13 | 14 | // shred to modulate the mix 15 | fun void vecho_Shred( ) 16 | { 17 | 0.0 => float decider; 18 | 0.0 => float mix; 19 | 0.0 => float old; 20 | 0.0 => float inc; 21 | 0 => int n; 22 | 23 | // time loop 24 | while( true ) 25 | { 26 | Math.random2f( 0, 1 ) => decider; 27 | if( decider < .3 ) 0.0 => mix; 28 | else if( decider < .6 ) .08 => mix; 29 | else if( decider < .8 ) .5 => mix; 30 | else .15 => mix; 31 | 32 | // find the increment 33 | (mix-old)/1000.0 => inc; 34 | 1000 => n; 35 | while( n-- ) 36 | { 37 | old + inc => old; 38 | old => a.mix => b.mix => c.mix; 39 | 1::ms => now; 40 | } 41 | mix => old; 42 | Math.random2(2,6)::second => now; 43 | } 44 | } 45 | 46 | 47 | // let echo shred go 48 | spork ~ vecho_Shred(); 49 | 50 | // scale 51 | [ 0, 2, 4, 7, 9 ] @=> int scale[]; 52 | 53 | // our main loop 54 | while( true ) 55 | { 56 | // pentatonic 57 | scale[Math.random2(0,scale.size()-1)] => int freq; 58 | 59 | Std.mtof( ( 33 + Math.random2(0,1) * 12 + freq ) ) => voc.freq; 60 | Math.random2f( 0.6, 0.8 ) => voc.noteOn; 61 | 62 | // note: Math.randomf() returns value between 0 and 1 63 | if( Math.randomf() > 0.85 ) 64 | { 1000::ms => now; } 65 | else if( Math.randomf() > .85 ) 66 | { 500::ms => now; } 67 | else if( Math.randomf() > .1 ) 68 | { .250::second => now; } 69 | else 70 | { 71 | 0 => int i; 72 | 2 * Math.random2( 1, 3 ) => int pick; 73 | 0 => int pick_dir; 74 | 0.0 => float pluck; 75 | 76 | for( ; i < pick; i++ ) 77 | { 78 | Math.random2f(.4,.6) + i*.035 => pluck; 79 | pluck + -0.02 * (i * pick_dir) => voc.noteOn; 80 | !pick_dir => pick_dir; 81 | 250::ms => now; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /assets/stk_shake_cycle.ck: -------------------------------------------------------------------------------- 1 | // shake-cycle.ck : effects abuse 2 | // author: Adam Tindale 3 | 4 | // the patch 5 | // Shakers s => Chorus c1 => JCRev rev => Chorus c2 => Chorus c3 =>dac; 6 | Shakers s => dac; 7 | 8 | 0 => s.which; 9 | 1 => s.gain; 10 | 100 => float theTime; 11 | 12 | while( true ) 13 | { 14 | 1.0 => s.noteOn; 15 | theTime::ms => now; 16 | 17 | 1.0 => s.noteOff; 18 | theTime::ms => now; 19 | 20 | ( s.which() + 1 ) % 20 => s.which; 21 | Math.random2f( 20, 140 ) => theTime; 22 | } 23 | -------------------------------------------------------------------------------- /assets/stk_wurley.ck: -------------------------------------------------------------------------------- 1 | // even more music for replicants 2 | 3 | // patch 4 | Wurley voc=> JCRev r => dac; 5 | 6 | // initial settings 7 | 220.0 => voc.freq; 8 | 0.95 => voc.gain; 9 | .8 => r.gain; 10 | .1 => r.mix; 11 | 12 | // scale 13 | [ 0, 3, 7, 8, 11 ] @=> int scale[]; 14 | 15 | // our main time loop 16 | while( true ) 17 | { 18 | // scale 19 | scale[Math.random2(0,scale.size()-1)] => int freq; 20 | Std.mtof( ( 45 + Math.random2(0,1) * 12 + freq ) ) => voc.freq; 21 | Math.random2f( 0.6, 0.8 ) => voc.noteOn; 22 | 23 | // note: Math.randomf() returns value between 0 and 1 24 | if( Math.randomf() > 0.9 ) 25 | { 26 | // 1000::ms => now; 27 | repeat( 100 ) 28 | { 29 | voc.freq() * 1.01 => voc.freq; 30 | 10::ms => now; 31 | } 32 | } 33 | else if( Math.randomf() > .75 ) 34 | { 35 | // 500::ms => now; 36 | repeat( 50 ) 37 | { 38 | voc.freq() * .99 => voc.freq; 39 | 10::ms => now; 40 | } 41 | } 42 | else if( Math.randomf() > .1 ) 43 | { 44 | 250::ms => now; 45 | 46 | } 47 | else 48 | { 49 | 0 => int i; 50 | 2 * Math.random2( 1, 3 ) => int pick; 51 | 0 => int pick_dir; 52 | 0.0 => float pluck; 53 | 54 | for( ; i < pick; i++ ) 55 | { 56 | Math.random2f(.4,.6) + i*.035 => pluck; 57 | pluck + 0.03 * (i * pick_dir) => voc.noteOn; 58 | !pick_dir => pick_dir; 59 | 250::ms => now; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /build_macos.sh: -------------------------------------------------------------------------------- 1 | if [ "$TOUCHDESIGNER_APP" == "" ]; then 2 | # a reasonable default in case you forget to set the path to TouchDesigner. 3 | export TOUCHDESIGNER_APP=/Applications/TouchDesigner.app 4 | fi 5 | echo Assuming TouchDesigner is located at $TOUCHDESIGNER_APP 6 | 7 | # if building on Apple Silicon 8 | if [[ $(uname -m) == 'arm64' ]]; then 9 | echo "Building universal for Apple Silicon." 10 | export CMAKE_OSX_ARCHITECTURES="arm64" 11 | else 12 | echo "Building for x86_64." 13 | export CMAKE_OSX_ARCHITECTURES="x86_64" 14 | fi 15 | 16 | if [ "$PYTHONVER" == "" ]; then 17 | # Guess which Python version TD uses. 18 | export PYTHONVER="3.11" 19 | fi 20 | echo Building for Python $PYTHONVER 21 | 22 | # Remove any old plugins and dylib 23 | rm Plugins/libChucKDesignerShared.dylib 24 | rm -r Plugins/ChucKDesignerCHOP.plugin 25 | rm -r Plugins/ChucKListenerCHOP.plugin 26 | 27 | # Setup steps, related to compiling ChucK 28 | cd thirdparty/chuck/src/core 29 | bison -dv -b chuck chuck.y 30 | flex -ochuck.yy.c chuck.lex 31 | cd ../../../.. 32 | 33 | # Steps for making the Xcode project and compiling with it 34 | cmake -Bbuild -G "Xcode" -DCMAKE_OSX_ARCHITECTURES=$CMAKE_OSX_ARCHITECTURES -DPYTHONVER=$PYTHONVER -DPython_ROOT_DIR=$TOUCHDESIGNER_APP/Contents/Frameworks/Python.framework/Versions/$PYTHONVER 35 | cmake --build build --config Release 36 | 37 | # Copy to Plugins directory 38 | cp build/Release/libChucKDesignerShared.dylib Plugins 39 | cp -R build/Release/ChucKDesignerCHOP.plugin Plugins 40 | cp -R build/Release/ChucKListenerCHOP.plugin Plugins 41 | 42 | echo "All Done!" -------------------------------------------------------------------------------- /build_windows.bat: -------------------------------------------------------------------------------- 1 | if "%PYTHONVER%"=="" ( 2 | set PYTHONVER=3.11 3 | ) 4 | echo "Using Python version: %PYTHONVER%" 5 | 6 | cmake . -DCMAKE_BUILD_TYPE=Release -Bbuild -DPYTHONVER=%PYTHONVER% 7 | cmake --build build --config Release 8 | echo "All Done!" 9 | -------------------------------------------------------------------------------- /docs/float_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DBraun/ChucKDesigner/f5ed4284efecedef2d177dea733f53afde22f8ac/docs/float_example.png -------------------------------------------------------------------------------- /mac/miniAudicle.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.disable-library-validation 8 | 9 | com.apple.security.device.audio-input 10 | 11 | com.apple.security.get-task-allow 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ChucKDesignerCHOP.cpp: -------------------------------------------------------------------------------- 1 | /* Shared Use License: This file is owned by Derivative Inc. (Derivative) 2 | * and can only be used, and/or modified for use, in conjunction with 3 | * Derivative's TouchDesigner software, and only if you are a licensee who has 4 | * accepted Derivative's TouchDesigner license or assignment agreement 5 | * (which also govern the use of this file). You may share or redistribute 6 | * a modified version of this file provided the following conditions are met: 7 | * 8 | * 1. The shared file or redistribution must retain the information set out 9 | * above and this list of conditions. 10 | * 2. Derivative's name (Derivative Inc.) or its trademarks may not be used 11 | * to endorse or promote products derived from this file without specific 12 | * prior written permission from Derivative. 13 | */ 14 | 15 | #include "ChucKDesignerCHOP.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "chuck_globals.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define FAIL_IN_CUSTOM_OPERATOR_METHOD Py_INCREF(Py_None);return Py_None; 28 | 29 | static PyObject* 30 | pySetGlobalFloat(PyObject* self, PyObject* args, void*) 31 | { 32 | PY_Struct* me = (PY_Struct*)self; 33 | 34 | PY_GetInfo info; 35 | info.autoCook = false; 36 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 37 | // It's possible the instance will be nullptr, such as if the node has been deleted 38 | // while the Python class is still being held on and used elsewhere. 39 | if (inst) 40 | { 41 | PyObject* name; 42 | PyObject* val; 43 | 44 | if (!PyArg_UnpackTuple(args, "ref", 2, 2, &name, &val)) { 45 | // error 46 | FAIL_IN_CUSTOM_OPERATOR_METHOD 47 | } 48 | 49 | PyObject * ascii_mystring=PyUnicode_AsASCIIString(name); 50 | 51 | const char* castName = PyBytes_AsString(ascii_mystring); 52 | t_CKFLOAT castVal = PyFloat_AsDouble(val); 53 | 54 | inst->setGlobalFloat(castName, castVal); 55 | me->context->makeNodeDirty(); 56 | } 57 | 58 | // We need to inc-ref the None object if we are going to return it. 59 | FAIL_IN_CUSTOM_OPERATOR_METHOD 60 | } 61 | 62 | static PyObject* 63 | pySetGlobalInt(PyObject* self, PyObject* args, void*) 64 | { 65 | PY_Struct* me = (PY_Struct*)self; 66 | 67 | PY_GetInfo info; 68 | info.autoCook = false; 69 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 70 | // It's possible the instance will be nullptr, such as if the node has been deleted 71 | // while the Python class is still being held on and used elsewhere. 72 | if (inst) 73 | { 74 | PyObject* name; 75 | PyObject* val; 76 | 77 | if (!PyArg_UnpackTuple(args, "ref", 2, 2, &name, &val)) { 78 | // error 79 | FAIL_IN_CUSTOM_OPERATOR_METHOD 80 | } 81 | 82 | PyObject* ascii_mystring = PyUnicode_AsASCIIString(name); 83 | 84 | const char* castName = PyBytes_AsString(ascii_mystring); 85 | t_CKINT castVal = _PyLong_AsInt(val); 86 | 87 | inst->setGlobalInt(castName, castVal); 88 | me->context->makeNodeDirty(); 89 | } 90 | 91 | // We need to inc-ref the None object if we are going to return it. 92 | FAIL_IN_CUSTOM_OPERATOR_METHOD 93 | } 94 | 95 | static PyObject* 96 | pySetGlobalString(PyObject* self, PyObject* args, void*) 97 | { 98 | PY_Struct* me = (PY_Struct*)self; 99 | 100 | PY_GetInfo info; 101 | info.autoCook = false; 102 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 103 | // It's possible the instance will be nullptr, such as if the node has been deleted 104 | // while the Python class is still being held on and used elsewhere. 105 | if (inst) 106 | { 107 | PyObject* name; 108 | PyObject* val; 109 | 110 | if (!PyArg_UnpackTuple(args, "ref", 2, 2, &name, &val)) { 111 | // error 112 | FAIL_IN_CUSTOM_OPERATOR_METHOD 113 | } 114 | 115 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 116 | const char* castVal = PyBytes_AsString(PyUnicode_AsASCIIString(val)); 117 | 118 | inst->setGlobalString(castName, castVal); 119 | me->context->makeNodeDirty(); 120 | } 121 | 122 | // We need to inc-ref the None object if we are going to return it. 123 | FAIL_IN_CUSTOM_OPERATOR_METHOD 124 | } 125 | 126 | static PyObject* 127 | pySetGlobalFloatArray(PyObject* self, PyObject* args, void*) 128 | { 129 | PY_Struct* me = (PY_Struct*)self; 130 | 131 | PY_GetInfo info; 132 | info.autoCook = false; 133 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 134 | // It's possible the instance will be nullptr, such as if the node has been deleted 135 | // while the Python class is still being held on and used elsewhere. 136 | if (inst) 137 | { 138 | PyObject* name; 139 | PyObject* vals; 140 | 141 | if (!PyArg_UnpackTuple(args, "ref", 2, 2, &name, &vals)) { 142 | // error 143 | FAIL_IN_CUSTOM_OPERATOR_METHOD 144 | } 145 | 146 | PyObject* ascii_mystring = PyUnicode_AsASCIIString(name); 147 | 148 | const char* castName = PyBytes_AsString(ascii_mystring); 149 | 150 | Py_ssize_t numValues = PyList_Size(vals); 151 | 152 | auto nums = new t_CKFLOAT[numValues]; 153 | for (int i = 0; i < numValues; i++) { 154 | nums[i] = PyFloat_AsDouble(PyList_GetItem(vals, i));; 155 | } 156 | 157 | inst->setGlobalFloatArray(castName, nums, numValues); 158 | delete[] nums; 159 | me->context->makeNodeDirty(); 160 | } 161 | 162 | // We need to inc-ref the None object if we are going to return it. 163 | FAIL_IN_CUSTOM_OPERATOR_METHOD 164 | } 165 | 166 | static PyObject* 167 | pySetGlobalIntArray(PyObject* self, PyObject* args, void*) 168 | { 169 | PY_Struct* me = (PY_Struct*)self; 170 | 171 | PY_GetInfo info; 172 | info.autoCook = false; 173 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 174 | // It's possible the instance will be nullptr, such as if the node has been deleted 175 | // while the Python class is still being held on and used elsewhere. 176 | if (inst) 177 | { 178 | PyObject* name; 179 | PyObject* vals; 180 | 181 | if (!PyArg_UnpackTuple(args, "ref", 2, 2, &name, &vals)) { 182 | // error 183 | FAIL_IN_CUSTOM_OPERATOR_METHOD 184 | } 185 | 186 | PyObject* ascii_mystring = PyUnicode_AsASCIIString(name); 187 | 188 | const char* castName = PyBytes_AsString(ascii_mystring); 189 | 190 | Py_ssize_t numValues = PyList_Size(vals); 191 | 192 | auto nums = new t_CKINT[numValues]; 193 | for (int i = 0; i < numValues; i++) { 194 | nums[i] = PyLong_AsLongLong(PyList_GetItem(vals, i));; 195 | } 196 | 197 | inst->setGlobalIntArray(castName, nums, numValues); 198 | delete[] nums; 199 | me->context->makeNodeDirty(); 200 | } 201 | 202 | // We need to inc-ref the None object if we are going to return it. 203 | FAIL_IN_CUSTOM_OPERATOR_METHOD 204 | } 205 | 206 | static PyObject* 207 | pySetGlobalFloatArrayValue(PyObject* self, PyObject* args, void*) 208 | { 209 | PY_Struct* me = (PY_Struct*)self; 210 | 211 | PY_GetInfo info; 212 | info.autoCook = false; 213 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 214 | // It's possible the instance will be nullptr, such as if the node has been deleted 215 | // while the Python class is still being held on and used elsewhere. 216 | if (inst) 217 | { 218 | PyObject* name; 219 | PyObject* index; 220 | PyObject* value; 221 | 222 | if (!PyArg_UnpackTuple(args, "ref", 3, 3, &name, &index, &value)) { 223 | // error 224 | FAIL_IN_CUSTOM_OPERATOR_METHOD 225 | } 226 | 227 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 228 | 229 | int castIndex = _PyLong_AsInt(index); 230 | t_CKFLOAT castValue = PyFloat_AsDouble(value); 231 | 232 | inst->setGlobalFloatArrayValue(castName, castIndex, castValue); 233 | me->context->makeNodeDirty(); 234 | } 235 | 236 | // We need to inc-ref the None object if we are going to return it. 237 | FAIL_IN_CUSTOM_OPERATOR_METHOD 238 | } 239 | 240 | static PyObject* 241 | pySetGlobalIntArrayValue(PyObject* self, PyObject* args, void*) 242 | { 243 | PY_Struct* me = (PY_Struct*)self; 244 | 245 | PY_GetInfo info; 246 | info.autoCook = false; 247 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 248 | // It's possible the instance will be nullptr, such as if the node has been deleted 249 | // while the Python class is still being held on and used elsewhere. 250 | if (inst) 251 | { 252 | PyObject* name; 253 | PyObject* index; 254 | PyObject* value; 255 | 256 | if (!PyArg_UnpackTuple(args, "ref", 3, 3, &name, &index, &value)) { 257 | // error 258 | FAIL_IN_CUSTOM_OPERATOR_METHOD 259 | } 260 | 261 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 262 | 263 | int castIndex = _PyLong_AsInt(index); 264 | t_CKINT castValue = PyLong_AsLongLong(value); 265 | 266 | inst->setGlobalIntArrayValue(castName, castIndex, castValue); 267 | me->context->makeNodeDirty(); 268 | } 269 | 270 | // We need to inc-ref the None object if we are going to return it. 271 | FAIL_IN_CUSTOM_OPERATOR_METHOD 272 | } 273 | 274 | static PyObject* 275 | pySetGlobalAssociativeFloatArrayValue(PyObject* self, PyObject* args, void*) 276 | { 277 | PY_Struct* me = (PY_Struct*)self; 278 | 279 | PY_GetInfo info; 280 | info.autoCook = false; 281 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 282 | // It's possible the instance will be nullptr, such as if the node has been deleted 283 | // while the Python class is still being held on and used elsewhere. 284 | if (inst) 285 | { 286 | PyObject* name; 287 | PyObject* key; 288 | PyObject* value; 289 | 290 | if (!PyArg_UnpackTuple(args, "ref", 3, 3, &name, &key, &value)) { 291 | // error 292 | FAIL_IN_CUSTOM_OPERATOR_METHOD 293 | } 294 | 295 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 296 | 297 | char* castKey = PyBytes_AsString(PyUnicode_AsASCIIString(key)); 298 | 299 | t_CKFLOAT castValue = PyFloat_AsDouble(value); 300 | 301 | inst->setGlobalAssociativeFloatArrayValue(castName, castKey, castValue); 302 | me->context->makeNodeDirty(); 303 | } 304 | 305 | // We need to inc-ref the None object if we are going to return it. 306 | FAIL_IN_CUSTOM_OPERATOR_METHOD 307 | } 308 | 309 | static PyObject* 310 | pySetGlobalAssociativeIntArrayValue(PyObject* self, PyObject* args, void*) 311 | { 312 | PY_Struct* me = (PY_Struct*)self; 313 | 314 | PY_GetInfo info; 315 | info.autoCook = false; 316 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 317 | // It's possible the instance will be nullptr, such as if the node has been deleted 318 | // while the Python class is still being held on and used elsewhere. 319 | if (inst) 320 | { 321 | PyObject* name; 322 | PyObject* key; 323 | PyObject* value; 324 | 325 | if (!PyArg_UnpackTuple(args, "ref", 3, 3, &name, &key, &value)) { 326 | // error 327 | FAIL_IN_CUSTOM_OPERATOR_METHOD 328 | } 329 | 330 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 331 | 332 | char* castKey = PyBytes_AsString(PyUnicode_AsASCIIString(key)); 333 | 334 | t_CKINT castValue = PyLong_AsLongLong(value); 335 | 336 | inst->setGlobalAssociativeIntArrayValue(castName, castKey, castValue); 337 | me->context->makeNodeDirty(); 338 | } 339 | 340 | // We need to inc-ref the None object if we are going to return it. 341 | FAIL_IN_CUSTOM_OPERATOR_METHOD 342 | } 343 | 344 | static PyObject* pyGetGlobalFloat(PyObject* self, PyObject* args, void*) { 345 | PY_Struct* me = (PY_Struct*)self; 346 | 347 | PY_GetInfo info; 348 | info.autoCook = false; 349 | ChucKDesignerCHOP* inst = 350 | (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 351 | // It's possible the instance will be nullptr, such as if the node has been 352 | // deleted while the Python class is still being held on and used elsewhere. 353 | if (inst) { 354 | PyObject* name; 355 | 356 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &name)) { 357 | // error 358 | FAIL_IN_CUSTOM_OPERATOR_METHOD 359 | } 360 | 361 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 362 | 363 | t_CKFLOAT val; 364 | if (inst->getGlobalFloat(castName, val)) { 365 | return PyFloat_FromDouble(val); 366 | } 367 | } 368 | 369 | // We need to inc-ref the None object if we are going to return it. 370 | FAIL_IN_CUSTOM_OPERATOR_METHOD 371 | } 372 | 373 | static PyObject* pyGetGlobalInt(PyObject* self, PyObject* args, void*) { 374 | PY_Struct* me = (PY_Struct*)self; 375 | 376 | PY_GetInfo info; 377 | info.autoCook = false; 378 | ChucKDesignerCHOP* inst = 379 | (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 380 | // It's possible the instance will be nullptr, such as if the node has been 381 | // deleted while the Python class is still being held on and used elsewhere. 382 | if (inst) { 383 | PyObject* name; 384 | 385 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &name)) { 386 | // error 387 | FAIL_IN_CUSTOM_OPERATOR_METHOD 388 | } 389 | 390 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 391 | 392 | t_CKINT val; 393 | if (inst->getGlobalInt(castName, val)) { 394 | return PyLong_FromLongLong(val); 395 | } 396 | } 397 | 398 | // We need to inc-ref the None object if we are going to return it. 399 | FAIL_IN_CUSTOM_OPERATOR_METHOD 400 | } 401 | 402 | static PyObject* pyGetGlobalString(PyObject* self, PyObject* args, void*) { 403 | PY_Struct* me = (PY_Struct*)self; 404 | 405 | PY_GetInfo info; 406 | info.autoCook = false; 407 | ChucKDesignerCHOP* inst = 408 | (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 409 | // It's possible the instance will be nullptr, such as if the node has been 410 | // deleted while the Python class is still being held on and used elsewhere. 411 | if (inst) { 412 | PyObject* name; 413 | 414 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &name)) { 415 | // error 416 | FAIL_IN_CUSTOM_OPERATOR_METHOD 417 | } 418 | 419 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 420 | 421 | std::string val; 422 | if (inst->getGlobalString(castName, val)) { 423 | return PyUnicode_FromString(val.c_str()); 424 | } 425 | } 426 | 427 | // We need to inc-ref the None object if we are going to return it. 428 | FAIL_IN_CUSTOM_OPERATOR_METHOD 429 | } 430 | 431 | static PyObject* pyGetGlobalFloatArray(PyObject* self, PyObject* args, void*) { 432 | PY_Struct* me = (PY_Struct*)self; 433 | 434 | PY_GetInfo info; 435 | info.autoCook = false; 436 | ChucKDesignerCHOP* inst = 437 | (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 438 | // It's possible the instance will be nullptr, such as if the node has been 439 | // deleted while the Python class is still being held on and used elsewhere. 440 | if (inst) { 441 | PyObject* name; 442 | 443 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &name)) { 444 | // error 445 | FAIL_IN_CUSTOM_OPERATOR_METHOD 446 | } 447 | 448 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 449 | 450 | t_CKFLOAT* vec = nullptr; 451 | int numItems = 0; 452 | if (inst->getGlobalFloatArray(castName, &vec, numItems)) { 453 | // todo: return a numpy array 454 | PyObject* lst = PyList_New(numItems); 455 | for (int i = 0; i < numItems; i++) { 456 | PyList_SET_ITEM(lst, i, PyFloat_FromDouble(*(vec++))); 457 | } 458 | return lst; 459 | } 460 | } 461 | 462 | // We need to inc-ref the None object if we are going to return it. 463 | FAIL_IN_CUSTOM_OPERATOR_METHOD 464 | } 465 | 466 | static PyObject* pyGetGlobalIntArray(PyObject* self, PyObject* args, void*) { 467 | PY_Struct* me = (PY_Struct*)self; 468 | 469 | PY_GetInfo info; 470 | info.autoCook = false; 471 | ChucKDesignerCHOP* inst = 472 | (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 473 | // It's possible the instance will be nullptr, such as if the node has been 474 | // deleted while the Python class is still being held on and used elsewhere. 475 | if (inst) { 476 | PyObject* name; 477 | 478 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &name)) { 479 | // error 480 | FAIL_IN_CUSTOM_OPERATOR_METHOD 481 | } 482 | 483 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 484 | 485 | t_CKINT* vec = nullptr; 486 | int numItems = 0; 487 | if (inst->getGlobalIntArray(castName, &vec, numItems)) { 488 | // todo: return a numpy array 489 | PyObject* lst = PyList_New(numItems); 490 | for (int i = 0; i < numItems; i++) { 491 | PyList_SET_ITEM(lst, i, PyLong_FromLongLong(*(vec++))); 492 | } 493 | return lst; 494 | } 495 | } 496 | 497 | // We need to inc-ref the None object if we are going to return it. 498 | FAIL_IN_CUSTOM_OPERATOR_METHOD 499 | } 500 | 501 | static PyObject* 502 | pyBroadcastChuckEvent(PyObject* self, PyObject* args, void*) 503 | { 504 | PY_Struct* me = (PY_Struct*)self; 505 | 506 | PY_GetInfo info; 507 | info.autoCook = false; 508 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 509 | // It's possible the instance will be nullptr, such as if the node has been deleted 510 | // while the Python class is still being held on and used elsewhere. 511 | if (inst) 512 | { 513 | PyObject* name; 514 | 515 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &name)) { 516 | // error 517 | FAIL_IN_CUSTOM_OPERATOR_METHOD 518 | } 519 | 520 | const char* castName = PyBytes_AsString(PyUnicode_AsASCIIString(name)); 521 | 522 | inst->broadcastChuckEvent(castName); 523 | me->context->makeNodeDirty(); 524 | } 525 | 526 | // We need to inc-ref the None object if we are going to return it. 527 | FAIL_IN_CUSTOM_OPERATOR_METHOD 528 | } 529 | 530 | static PyObject* 531 | pySetLogLevel(PyObject* self, PyObject* args, void*) 532 | { 533 | PY_Struct* me = (PY_Struct*)self; 534 | 535 | PY_GetInfo info; 536 | info.autoCook = false; 537 | ChucKDesignerCHOP* inst = (ChucKDesignerCHOP*)me->context->getNodeInstance(info); 538 | // It's possible the instance will be nullptr, such as if the node has been deleted 539 | // while the Python class is still being held on and used elsewhere. 540 | if (inst) 541 | { 542 | PyObject* level; 543 | 544 | if (!PyArg_UnpackTuple(args, "ref", 1, 1, &level)) { 545 | // error 546 | FAIL_IN_CUSTOM_OPERATOR_METHOD 547 | } 548 | 549 | inst->setLogLevel(_PyLong_AsInt(level)); 550 | me->context->makeNodeDirty(); 551 | } 552 | 553 | // We need to inc-ref the None object if we are going to return it. 554 | FAIL_IN_CUSTOM_OPERATOR_METHOD 555 | } 556 | 557 | static PyMethodDef methods[] = 558 | { 559 | {"set_float", (PyCFunction)pySetGlobalFloat, METH_VARARGS, "Set a ChucK global float variable."}, 560 | {"set_int", (PyCFunction)pySetGlobalInt, METH_VARARGS, "Set a ChucK global float variable."}, 561 | {"set_string", (PyCFunction)pySetGlobalString, METH_VARARGS, "Set a ChucK global string variable."}, 562 | 563 | {"set_float_array", (PyCFunction)pySetGlobalFloatArray, METH_VARARGS, "Set a ChucK global float array variable."}, 564 | {"set_int_array", (PyCFunction)pySetGlobalIntArray, METH_VARARGS, "Set a ChucK global int array variable."}, 565 | 566 | {"set_float_array_value", (PyCFunction)pySetGlobalFloatArrayValue, METH_VARARGS, "Set a single value in a ChucK global float array variable."}, 567 | {"set_int_array_value", (PyCFunction)pySetGlobalIntArrayValue, METH_VARARGS, "Set a single value in a ChucK global int array variable."}, 568 | 569 | {"set_associative_float_array_value", (PyCFunction)pySetGlobalAssociativeFloatArrayValue, METH_VARARGS, "Set a single value in an associative ChucK global float array variable."}, 570 | {"set_associative_float_int_value", (PyCFunction)pySetGlobalAssociativeIntArrayValue, METH_VARARGS, "Set a single value in an associative ChucK global int array variable."}, 571 | 572 | {"broadcast_event", (PyCFunction)pyBroadcastChuckEvent, METH_VARARGS, "Broadcast an event to ChucK."}, 573 | 574 | {"get_float", (PyCFunction)pyGetGlobalFloat, METH_VARARGS, "Get a ChucK global float variable."}, 575 | {"get_int", (PyCFunction)pyGetGlobalInt, METH_VARARGS, "Get a ChucK global int variable."}, 576 | {"get_string", (PyCFunction)pyGetGlobalString, METH_VARARGS, "Get a ChucK global string variable."}, 577 | {"get_float_array", (PyCFunction)pyGetGlobalFloatArray, METH_VARARGS, "Get a ChucK global float array variable."}, 578 | {"get_int_array", (PyCFunction)pyGetGlobalIntArray, METH_VARARGS, "Get a ChucK global int array variable."}, 579 | 580 | {"set_log_level", (PyCFunction)pySetLogLevel, METH_VARARGS, "Set ChucK's log level."}, 581 | 582 | {0} 583 | }; 584 | 585 | 586 | extern "C" 587 | { 588 | 589 | DLLEXPORT 590 | void 591 | FillCHOPPluginInfo(CHOP_PluginInfo* info) 592 | { 593 | // Always set this to CHOPCPlusPlusAPIVersion. 594 | info->apiVersion = CHOPCPlusPlusAPIVersion; 595 | 596 | // The opType is the unique name for this CHOP. It must start with a 597 | // capital A-Z character, and all the following characters must lower case 598 | // or numbers (a-z, 0-9) 599 | info->customOPInfo.opType->setString("Chuckaudio"); 600 | 601 | // The opLabel is the text that will show up in the OP Create Dialog 602 | info->customOPInfo.opLabel->setString("ChucK Audio"); 603 | info->customOPInfo.opIcon->setString("CKA"); 604 | 605 | // Information about the author of this OP 606 | info->customOPInfo.authorName->setString("David Braun"); 607 | info->customOPInfo.authorEmail->setString("github.com/DBraun"); 608 | 609 | info->customOPInfo.majorVersion = 0; 610 | info->customOPInfo.minorVersion = 3; 611 | 612 | info->customOPInfo.minInputs = 0; 613 | info->customOPInfo.maxInputs = 1; 614 | 615 | info->customOPInfo.pythonVersion->setString(PY_VERSION); 616 | info->customOPInfo.pythonMethods = methods; 617 | //info->customOPInfo.pythonGetSets = getSets; // todo: 618 | 619 | ChucK_For_TouchDesigner::setStderrCallback( 620 | [](const char* text) { 621 | std::cerr << text << std::endl; 622 | } 623 | ); 624 | 625 | ChucK_For_TouchDesigner::setStdoutCallback( 626 | [](const char* text) { 627 | std::cout << text << std::endl; 628 | } 629 | ); 630 | } 631 | 632 | DLLEXPORT 633 | CHOP_CPlusPlusBase* 634 | CreateCHOPInstance(const OP_NodeInfo* info) 635 | { 636 | // Return a new instance of your class every time this is called. 637 | // It will be called once per CHOP that is using the .dll 638 | return new ChucKDesignerCHOP(info); 639 | } 640 | 641 | DLLEXPORT 642 | void 643 | DestroyCHOPInstance(CHOP_CPlusPlusBase* instance) 644 | { 645 | // Delete the instance here, this will be called when 646 | // Touch is shutting down, when the CHOP using that instance is deleted, or 647 | // if the CHOP loads a different DLL 648 | delete (ChucKDesignerCHOP*)instance; 649 | } 650 | 651 | }; 652 | 653 | 654 | ChucKDesignerCHOP::ChucKDesignerCHOP(const OP_NodeInfo* info) : myNodeInfo(info) 655 | { 656 | m_chuckID = ChucK_For_TouchDesigner::getNextValidID(myNodeInfo->opId); 657 | 658 | myExecuteCount = 0; 659 | } 660 | 661 | 662 | ChucKDesignerCHOP::~ChucKDesignerCHOP() 663 | { 664 | reset(); 665 | } 666 | 667 | 668 | void 669 | ChucKDesignerCHOP::getGeneralInfo(CHOP_GeneralInfo* ginfo, const OP_Inputs* inputs, void* reserved1) 670 | { 671 | // This will cause the node to cook every frame 672 | ginfo->cookEveryFrameIfAsked = true; 673 | ginfo->timeslice = true; 674 | } 675 | 676 | 677 | bool 678 | ChucKDesignerCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, void* reserved1) 679 | { 680 | ChucK_For_TouchDesigner::getInstanceInfo(m_chuckID, info->numChannels, info->numSamples, info->sampleRate); 681 | 682 | return true; 683 | } 684 | 685 | 686 | void 687 | ChucKDesignerCHOP::getChannelName(int32_t index, OP_String *name, const OP_Inputs* inputs, void* reserved1) 688 | { 689 | std::stringstream ss; 690 | ss << "chan" << (index + 1); 691 | name->setString(ss.str().c_str()); 692 | } 693 | 694 | 695 | void 696 | ChucKDesignerCHOP::reset() { 697 | myError.str(""); 698 | ChucK_For_TouchDesigner::clearGlobals(m_chuckID); 699 | ChucK_For_TouchDesigner::clearChuckInstance(m_chuckID); 700 | ChucK_For_TouchDesigner::cleanupChuckInstance(m_chuckID, myNodeInfo->opId); 701 | 702 | m_chuckID = ChucK_For_TouchDesigner::getNextValidID(myNodeInfo->opId); 703 | } 704 | 705 | void 706 | ChucKDesignerCHOP::execute(CHOP_Output* output, 707 | const OP_Inputs* inputs, 708 | void* reserved) 709 | { 710 | myExecuteCount++; 711 | 712 | if (needReset) { 713 | needReset = false; 714 | ChucKDesignerCHOP::reset(); 715 | } 716 | 717 | if (needCompile) { 718 | needCompile = false; 719 | 720 | string globalDir = inputs->getParFilePath("Workingdirectory"); 721 | 722 | double sample_rate = inputs->getParDouble("Samplerate"); 723 | 724 | delete[] inChucKBuffer; 725 | delete[] outChucKBuffer; 726 | 727 | m_inChannels = inputs->getParInt("Inchannels"); 728 | m_outChannels = inputs->getParInt("Outchannels"); 729 | 730 | inChucKBuffer = new float[CHUCKDESIGNERCHOP_BUFFER_SIZE * m_inChannels]; 731 | outChucKBuffer = new float[CHUCKDESIGNERCHOP_BUFFER_SIZE * m_outChannels]; 732 | 733 | memset(inChucKBuffer, 0.f, sizeof(float) * CHUCKDESIGNERCHOP_BUFFER_SIZE * m_inChannels); 734 | memset(outChucKBuffer, 0.f, sizeof(float) * CHUCKDESIGNERCHOP_BUFFER_SIZE * m_outChannels); 735 | 736 | ChucK_For_TouchDesigner::initChuckInstance(m_chuckID, sample_rate, m_inChannels, m_outChannels, globalDir); 737 | 738 | const OP_DATInput* codeInput = inputs->getParDAT("Code"); 739 | 740 | myError.str(""); 741 | 742 | if (codeInput) { 743 | const char* code = *(codeInput->cellData); 744 | bool result = ChucK_For_TouchDesigner::runChuckCode(m_chuckID, code); 745 | if (!result) { 746 | myError.str("ChucK code did not compile correctly."); 747 | } 748 | } else { 749 | myError.str("You must specify ChucK Code."); 750 | } 751 | } 752 | 753 | const OP_CHOPInput* Globalfloat_CHOPInput = inputs->getParCHOP("Globalfloat"); 754 | if (Globalfloat_CHOPInput) { 755 | for (size_t chanIndex = 0; chanIndex < Globalfloat_CHOPInput->numChannels; chanIndex++) 756 | { 757 | const char* chanName = Globalfloat_CHOPInput->getChannelName(chanIndex); 758 | float chanVal = Globalfloat_CHOPInput->channelData[chanIndex][0]; 759 | ChucK_For_TouchDesigner::setChuckFloat(m_chuckID, chanName, chanVal); 760 | } 761 | } 762 | 763 | const float** inBuffer = nullptr; 764 | int inBufferNumChannels = 0; 765 | int inBufferNumSamples = 0; 766 | 767 | if (auto chopInput = inputs->getInputCHOP(0)) { 768 | inBuffer = chopInput->channelData; 769 | inBufferNumChannels = chopInput->numChannels; 770 | inBufferNumSamples = chopInput->numSamples; 771 | } 772 | 773 | bool result = ChucK_For_TouchDesigner::processBlock(m_chuckID, inBuffer, inBufferNumChannels, inBufferNumSamples, inChucKBuffer, outChucKBuffer, output->channels, output->numSamples, output->numChannels); 774 | if (!result) { 775 | // fill zeros 776 | for (int chan = 0; chan < output->numChannels; chan++) { 777 | memset(output->channels[chan], 0.f, sizeof(float) * output->numSamples); 778 | } 779 | } 780 | } 781 | 782 | void 783 | ChucKDesignerCHOP::getErrorString(OP_String* error, void* reserved1) { 784 | error->setString(myError.str().c_str()); 785 | } 786 | 787 | int32_t 788 | ChucKDesignerCHOP::getNumInfoCHOPChans(void * reserved1) 789 | { 790 | // We return the number of channel we want to output to any Info CHOP 791 | // connected to the CHOP. In this example we are just going to send one channel. 792 | return 2; 793 | } 794 | 795 | void 796 | ChucKDesignerCHOP::getInfoCHOPChan(int32_t index, 797 | OP_InfoCHOPChan* chan, 798 | void* reserved1) 799 | { 800 | // This function will be called once for each channel we said we'd want to return 801 | // In this example it'll only be called once. 802 | 803 | if (index == 0) 804 | { 805 | chan->name->setString("executeCount"); 806 | chan->value = (float)myExecuteCount; 807 | } 808 | 809 | else if (index == 1) 810 | { 811 | chan->name->setString("chuck_id"); 812 | chan->value = (float)m_chuckID; 813 | } 814 | } 815 | 816 | bool 817 | ChucKDesignerCHOP::getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved1) 818 | { 819 | infoSize->rows = 1; 820 | infoSize->cols = 2; 821 | // Setting this to false means we'll be assigning values to the table 822 | // one row at a time. True means we'll do it one column at a time. 823 | infoSize->byColumn = false; 824 | return true; 825 | } 826 | 827 | void 828 | ChucKDesignerCHOP::getInfoDATEntries(int32_t index, 829 | int32_t nEntries, 830 | OP_InfoDATEntries* entries, 831 | void* reserved1) 832 | { 833 | char tempBuffer[4096]; 834 | 835 | if (index == 0) 836 | { 837 | // Set the value for the first column 838 | entries->values[0]->setString("executeCount"); 839 | 840 | // Set the value for the second column 841 | #ifdef _WIN32 842 | sprintf_s(tempBuffer, "%d", myExecuteCount); 843 | #else // macOS 844 | snprintf(tempBuffer, sizeof(tempBuffer), "%d", myExecuteCount); 845 | #endif 846 | entries->values[1]->setString(tempBuffer); 847 | } 848 | } 849 | 850 | void 851 | ChucKDesignerCHOP::setupParameters(OP_ParameterManager* manager, void *reserved1) 852 | { 853 | 854 | // Sample Rate 855 | { 856 | OP_NumericParameter np; 857 | 858 | np.name = "Samplerate"; 859 | np.label = "Sample Rate"; 860 | np.defaultValues[0] = 44100; 861 | np.minSliders[0] = 0.0; 862 | np.maxSliders[0] = 96000.0; 863 | np.clampMins[0] = true; 864 | 865 | OP_ParAppendResult res = manager->appendFloat(np); 866 | assert(res == OP_ParAppendResult::Success); 867 | } 868 | 869 | // Num in channels 870 | { 871 | OP_NumericParameter np; 872 | 873 | np.name = "Inchannels"; 874 | np.label = "In Channels"; 875 | np.defaultValues[0] = 2; 876 | np.minSliders[0] = 2; 877 | np.maxSliders[0] = 2; 878 | np.minValues[0] = 0; 879 | np.clampMins[0] = true; 880 | 881 | OP_ParAppendResult res = manager->appendInt(np); 882 | assert(res == OP_ParAppendResult::Success); 883 | } 884 | 885 | // Num out channels 886 | { 887 | OP_NumericParameter np; 888 | 889 | np.name = "Outchannels"; 890 | np.label = "Out Channels"; 891 | np.defaultValues[0] = 2; 892 | np.minSliders[0] = 2; 893 | np.maxSliders[0] = 2; 894 | np.minValues[0] = 0; 895 | np.clampMins[0] = true; 896 | 897 | OP_ParAppendResult res = manager->appendInt(np); 898 | assert(res == OP_ParAppendResult::Success); 899 | } 900 | 901 | //// speed 902 | //{ 903 | // OP_NumericParameter np; 904 | 905 | // np.name = "Speed"; 906 | // np.label = "Speed"; 907 | // np.defaultValues[0] = 1.0; 908 | // np.minSliders[0] = -10.0; 909 | // np.maxSliders[0] = 10.0; 910 | // 911 | // OP_ParAppendResult res = manager->appendFloat(np); 912 | // assert(res == OP_ParAppendResult::Success); 913 | //} 914 | 915 | //// scale 916 | //{ 917 | // OP_NumericParameter np; 918 | 919 | // np.name = "Scale"; 920 | // np.label = "Scale"; 921 | // np.defaultValues[0] = 1.0; 922 | // np.minSliders[0] = -10.0; 923 | // np.maxSliders[0] = 10.0; 924 | // 925 | // OP_ParAppendResult res = manager->appendFloat(np); 926 | // assert(res == OP_ParAppendResult::Success); 927 | //} 928 | 929 | //// shape 930 | //{ 931 | // OP_StringParameter sp; 932 | 933 | // sp.name = "Shape"; 934 | // sp.label = "Shape"; 935 | 936 | // sp.defaultValue = "Sine"; 937 | 938 | // const char *names[] = { "Sine", "Square", "Ramp" }; 939 | // const char *labels[] = { "Sine", "Square", "Ramp" }; 940 | 941 | // OP_ParAppendResult res = manager->appendMenu(sp, 3, names, labels); 942 | // assert(res == OP_ParAppendResult::Success); 943 | //} 944 | 945 | // chuck source code DAT 946 | { 947 | OP_StringParameter sp; 948 | 949 | sp.name = "Code"; 950 | sp.label = "Code"; 951 | 952 | sp.defaultValue = ""; 953 | 954 | OP_ParAppendResult res = manager->appendDAT(sp); 955 | assert(res == OP_ParAppendResult::Success); 956 | } 957 | 958 | // Working directory path 959 | { 960 | OP_NumericParameter np; 961 | OP_StringParameter sp; 962 | 963 | sp.name = "Workingdirectory"; 964 | sp.label = "Working Directory"; 965 | 966 | OP_ParAppendResult res = manager->appendFolder(sp); 967 | assert(res == OP_ParAppendResult::Success); 968 | } 969 | 970 | // pulse 971 | { 972 | OP_NumericParameter np; 973 | 974 | np.name = "Addchuckcode"; 975 | np.label = "Add ChucK Code"; 976 | 977 | OP_ParAppendResult res = manager->appendPulse(np); 978 | assert(res == OP_ParAppendResult::Success); 979 | } 980 | 981 | { 982 | OP_NumericParameter np; 983 | 984 | np.name = "Replacechuckcode"; 985 | np.label = "Replace ChucK Code"; 986 | 987 | OP_ParAppendResult res = manager->appendPulse(np); 988 | assert(res == OP_ParAppendResult::Success); 989 | } 990 | 991 | // Reset 992 | { 993 | OP_NumericParameter np; 994 | 995 | np.name = "Reset"; 996 | np.label = "Reset"; 997 | 998 | OP_ParAppendResult res = manager->appendPulse(np); 999 | assert(res == OP_ParAppendResult::Success); 1000 | } 1001 | 1002 | // Global In Float CHOP 1003 | { 1004 | OP_StringParameter sp; 1005 | 1006 | sp.name = "Globalfloat"; 1007 | sp.label = "Global Float"; 1008 | 1009 | sp.defaultValue = ""; 1010 | 1011 | OP_ParAppendResult res = manager->appendCHOP(sp); 1012 | assert(res == OP_ParAppendResult::Success); 1013 | } 1014 | } 1015 | 1016 | void 1017 | ChucKDesignerCHOP::pulsePressed(const char* name, void* reserved1) 1018 | { 1019 | // Note that we avoid bugs by just changing bool values here. 1020 | 1021 | if (!strcmp(name, "Addchuckcode")) 1022 | { 1023 | needCompile = true; 1024 | } 1025 | 1026 | if (!strcmp(name, "Replacechuckcode")) 1027 | { 1028 | needReset = true; 1029 | needCompile = true; 1030 | } 1031 | 1032 | if (!strcmp(name, "Reset")) 1033 | { 1034 | needReset = true; 1035 | } 1036 | } 1037 | -------------------------------------------------------------------------------- /src/ChucKDesignerCHOP.h: -------------------------------------------------------------------------------- 1 | /* Shared Use License: This file is owned by Derivative Inc. (Derivative) 2 | * and can only be used, and/or modified for use, in conjunction with 3 | * Derivative's TouchDesigner software, and only if you are a licensee who has 4 | * accepted Derivative's TouchDesigner license or assignment agreement 5 | * (which also govern the use of this file). You may share or redistribute 6 | * a modified version of this file provided the following conditions are met: 7 | * 8 | * 1. The shared file or redistribution must retain the information set out 9 | * above and this list of conditions. 10 | * 2. Derivative's name (Derivative Inc.) or its trademarks may not be used 11 | * to endorse or promote products derived from this file without specific 12 | * prior written permission from Derivative. 13 | */ 14 | 15 | #pragma once 16 | 17 | #include "CHOP_CPlusPlusBase.h" 18 | using namespace TD; 19 | 20 | #include "Plugin_ChucK.h" 21 | 22 | #include 23 | #include 24 | 25 | // To get more help about these functions, look at CHOP_CPlusPlusBase.h 26 | class ChucKDesignerCHOP : public CHOP_CPlusPlusBase 27 | { 28 | public: 29 | ChucKDesignerCHOP(const OP_NodeInfo* info); 30 | virtual ~ChucKDesignerCHOP(); 31 | 32 | virtual void getGeneralInfo(CHOP_GeneralInfo*, const OP_Inputs*, void* ) override; 33 | virtual bool getOutputInfo(CHOP_OutputInfo*, const OP_Inputs*, void*) override; 34 | virtual void getChannelName(int32_t index, OP_String *name, const OP_Inputs*, void* reserved) override; 35 | 36 | virtual void execute(CHOP_Output*, 37 | const OP_Inputs*, 38 | void* reserved) override; 39 | 40 | 41 | virtual int32_t getNumInfoCHOPChans(void* reserved1) override; 42 | virtual void getInfoCHOPChan(int index, 43 | OP_InfoCHOPChan* chan, 44 | void* reserved1) override; 45 | 46 | virtual bool getInfoDATSize(OP_InfoDATSize* infoSize, void* resereved1) override; 47 | virtual void getInfoDATEntries(int32_t index, 48 | int32_t nEntries, 49 | OP_InfoDATEntries* entries, 50 | void* reserved1) override; 51 | 52 | virtual void setupParameters(OP_ParameterManager* manager, void *reserved1) override; 53 | virtual void pulsePressed(const char* name, void* reserved1) override; 54 | 55 | virtual void getErrorString(OP_String* error, void* reserved1) override; 56 | 57 | void 58 | setGlobalFloat(const char* name, t_CKFLOAT val) 59 | { 60 | ChucK_For_TouchDesigner::setChuckFloat(m_chuckID, name, val); 61 | } 62 | 63 | void 64 | setGlobalInt(const char* name, t_CKINT val) 65 | { 66 | ChucK_For_TouchDesigner::setChuckInt(m_chuckID, name, val); 67 | } 68 | 69 | void 70 | setGlobalString(const char* name, const char* val) 71 | { 72 | ChucK_For_TouchDesigner::setChuckString(m_chuckID, name, val); 73 | } 74 | 75 | void 76 | setGlobalIntArray(const char* name, t_CKINT arrayValues[], unsigned int numValues) 77 | { 78 | ChucK_For_TouchDesigner::setGlobalIntArray(m_chuckID, name, arrayValues, numValues); 79 | } 80 | 81 | void 82 | setGlobalFloatArray(const char* name, t_CKFLOAT arrayValues[], unsigned int numValues) 83 | { 84 | ChucK_For_TouchDesigner::setGlobalFloatArray(m_chuckID, name, arrayValues, numValues); 85 | } 86 | 87 | void 88 | setGlobalFloatArrayValue(const char* name, unsigned int index, t_CKFLOAT value) 89 | { 90 | ChucK_For_TouchDesigner::setGlobalFloatArrayValue(m_chuckID, name, index, value); 91 | } 92 | 93 | void 94 | setGlobalIntArrayValue(const char* name, unsigned int index, t_CKINT value) 95 | { 96 | ChucK_For_TouchDesigner::setGlobalIntArrayValue(m_chuckID, name, index, value); 97 | } 98 | 99 | void 100 | setGlobalAssociativeFloatArrayValue(const char* name, char* key, t_CKFLOAT value) 101 | { 102 | ChucK_For_TouchDesigner::setGlobalAssociativeFloatArrayValue(m_chuckID, name, key, value); 103 | } 104 | 105 | void 106 | setGlobalAssociativeIntArrayValue(const char* name, char* key, t_CKINT value) 107 | { 108 | ChucK_For_TouchDesigner::setGlobalAssociativeIntArrayValue(m_chuckID, name, key, value); 109 | } 110 | 111 | bool getGlobalFloat(const char* name, t_CKFLOAT &val) 112 | { 113 | ChucK_For_TouchDesigner::getNamedChuckFloat(m_chuckID, name, ChucK_For_TouchDesigner::sharedFloatCallback); 114 | return ChucK_For_TouchDesigner::getFloat(name, val); 115 | } 116 | 117 | bool getGlobalInt(const char* name, t_CKINT& val) 118 | { 119 | ChucK_For_TouchDesigner::getNamedChuckInt(m_chuckID, name, ChucK_For_TouchDesigner::sharedIntCallback); 120 | return ChucK_For_TouchDesigner::getInt(name, val); 121 | } 122 | 123 | bool getGlobalString(const char* name, std::string& val) 124 | { 125 | ChucK_For_TouchDesigner::getNamedChuckString(m_chuckID, name, ChucK_For_TouchDesigner::sharedStringCallback); 126 | return ChucK_For_TouchDesigner::getString(name, val); 127 | } 128 | 129 | bool getGlobalIntArray(const char* name, t_CKINT** vec, int& numValues) 130 | { 131 | ChucK_For_TouchDesigner::getNamedGlobalIntArray(m_chuckID, name, ChucK_For_TouchDesigner::sharedIntArrayCallback); 132 | return ChucK_For_TouchDesigner::getIntArray(name, vec, numValues); 133 | } 134 | 135 | bool getGlobalFloatArray(const char* name, t_CKFLOAT** vec, int& numValues) 136 | { 137 | ChucK_For_TouchDesigner::getNamedGlobalFloatArray(m_chuckID, name, ChucK_For_TouchDesigner::sharedFloatArrayCallback); 138 | return ChucK_For_TouchDesigner::getFloatArray(name, vec, numValues); 139 | } 140 | 141 | void 142 | broadcastChuckEvent(const char* name) 143 | { 144 | ChucK_For_TouchDesigner::broadcastChuckEvent(m_chuckID, name); 145 | } 146 | 147 | void 148 | setLogLevel(unsigned int level) 149 | { 150 | ChucK_For_TouchDesigner::setLogLevel(level); 151 | } 152 | 153 | private: 154 | 155 | // We don't need to store this pointer, but we do for the example. 156 | // The OP_NodeInfo class store information about the node that's using 157 | // this instance of the class (like its name). 158 | const OP_NodeInfo* myNodeInfo; 159 | 160 | // In this example this value will be incremented each time the execute() 161 | // function is called, then passes back to the CHOP 162 | int32_t myExecuteCount; 163 | 164 | void reset(); 165 | 166 | bool needReset = false; 167 | bool needCompile = false; 168 | 169 | float* inChucKBuffer = new float[1]; 170 | float* outChucKBuffer = new float[1]; 171 | 172 | std::stringstream myError; 173 | 174 | int m_chuckID = 0; 175 | int m_inChannels = 0; 176 | int m_outChannels = 0; 177 | }; 178 | -------------------------------------------------------------------------------- /src/ChucKListenerCHOP.cpp: -------------------------------------------------------------------------------- 1 | /* Shared Use License: This file is owned by Derivative Inc. (Derivative) 2 | * and can only be used, and/or modified for use, in conjunction with 3 | * Derivative's TouchDesigner software, and only if you are a licensee who has 4 | * accepted Derivative's TouchDesigner license or assignment agreement 5 | * (which also govern the use of this file). You may share or redistribute 6 | * a modified version of this file provided the following conditions are met: 7 | * 8 | * 1. The shared file or redistribution must retain the information set out 9 | * above and this list of conditions. 10 | * 2. Derivative's name (Derivative Inc.) or its trademarks may not be used 11 | * to endorse or promote products derived from this file without specific 12 | * prior written permission from Derivative. 13 | */ 14 | 15 | #include "ChucKListenerCHOP.h" 16 | #include "Plugin_ChucK.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | //#include 24 | 25 | #include 26 | #include 27 | 28 | const char* PythonCallbacksDATStubs = 29 | "# This is an example callbacks DAT for a ChucK Listener Operator.\n" 30 | "# In all callback methods, \"listener\" is the ChucK Listener operator.\n" 31 | "\n\n" 32 | "def getFloat(listener, name, val):\n" 33 | " # print(f'getFloat(name=\"{name}\", val={val})')\n" 34 | " pass\n" 35 | "\n\n" 36 | "def getInt(listener, name, val):\n" 37 | " # print(f'getInt(name=\"{name}\", val={val})')\n" 38 | " pass\n" 39 | "\n\n" 40 | "def getString(listener, name, val):\n" 41 | " # print(f'getString(name=\"{name}\", val={val})')\n" 42 | " pass\n" 43 | "\n\n" 44 | "def getEvent(listener, name):\n" 45 | " # print(f'getEvent(name=\"{name}\")')\n" 46 | " pass\n" 47 | "\n\n" 48 | "def getFloatArray(listener, name, vals):\n" 49 | " # print(f'getFloatArray(name=\"{name}\", vals={vals})')\n" 50 | " pass\n" 51 | "\n\n" 52 | "def getIntArray(listener, name, vals):\n" 53 | " # print(f'getIntArray(name=\"{name}\", vals={vals})')\n" 54 | " pass\n" 55 | ; 56 | 57 | // These functions are basic C function, which the DLL loader can find 58 | // much easier than finding a C++ Class. 59 | // The DLLEXPORT prefix is needed so the compile exports these functions from the .dll 60 | // you are creating 61 | extern "C" 62 | { 63 | 64 | DLLEXPORT 65 | void 66 | FillCHOPPluginInfo(CHOP_PluginInfo *info) 67 | { 68 | // Always set this to CHOPCPlusPlusAPIVersion. 69 | info->apiVersion = CHOPCPlusPlusAPIVersion; 70 | 71 | // The opType is the unique name for this CHOP. It must start with a 72 | // capital A-Z character, and all the following characters must lower case 73 | // or numbers (a-z, 0-9) 74 | info->customOPInfo.opType->setString("Chucklistener"); 75 | 76 | // The opLabel is the text that will show up in the OP Create Dialog 77 | info->customOPInfo.opLabel->setString("ChucK Listener"); 78 | info->customOPInfo.opIcon->setString("CKL"); 79 | 80 | // Information about the author of this OP 81 | info->customOPInfo.authorName->setString("David Braun"); 82 | info->customOPInfo.authorEmail->setString("github.com/DBraun"); 83 | 84 | info->customOPInfo.minInputs = 0; 85 | info->customOPInfo.maxInputs = 0; 86 | 87 | info->customOPInfo.pythonVersion->setString(PY_VERSION); 88 | //info->customOPInfo.pythonMethods = methods; 89 | //info->customOPInfo.pythonGetSets = getSets; 90 | info->customOPInfo.pythonCallbacksDAT = PythonCallbacksDATStubs; 91 | } 92 | 93 | DLLEXPORT 94 | CHOP_CPlusPlusBase* 95 | CreateCHOPInstance(const OP_NodeInfo* info) 96 | { 97 | // Return a new instance of your class every time this is called. 98 | // It will be called once per CHOP that is using the .dll 99 | return new ChucKListenerCHOP(info); 100 | } 101 | 102 | DLLEXPORT 103 | void 104 | DestroyCHOPInstance(CHOP_CPlusPlusBase* instance) 105 | { 106 | // Delete the instance here, this will be called when 107 | // Touch is shutting down, when the CHOP using that instance is deleted, or 108 | // if the CHOP loads a different DLL 109 | delete (ChucKListenerCHOP*)instance; 110 | } 111 | 112 | }; 113 | 114 | vector split(const string& s, char delim) { 115 | vector result; 116 | stringstream ss(s); 117 | string item; 118 | 119 | while (getline(ss, item, delim)) { 120 | if (item.compare("") != 0) { 121 | result.push_back(item); 122 | } 123 | } 124 | 125 | return result; 126 | } 127 | 128 | 129 | ChucKListenerCHOP::ChucKListenerCHOP(const OP_NodeInfo* info) : myNodeInfo(info) 130 | { 131 | myExecuteCount = 0; 132 | } 133 | 134 | ChucKListenerCHOP::~ChucKListenerCHOP() 135 | { 136 | for (auto varName : myEventVarNames) { 137 | ChucK_For_TouchDesigner::removeListenerCHOP(varName.c_str(), this->myNodeInfo->opId); 138 | } 139 | } 140 | 141 | void 142 | ChucKListenerCHOP::getGeneralInfo(CHOP_GeneralInfo* ginfo, const OP_Inputs* inputs, void* reserved1) 143 | { 144 | // This will cause the node to cook every frame 145 | ginfo->cookEveryFrameIfAsked = true; 146 | 147 | // Note: To disable timeslicing you'll need to turn this off, as well as ensure that 148 | // getOutputInfo() returns true, and likely also set the info->numSamples to how many 149 | // samples you want to generate for this CHOP. Otherwise it'll take on length of the 150 | // input CHOP, which may be timesliced. 151 | ginfo->timeslice = false; 152 | 153 | ginfo->inputMatchIndex = 0; 154 | } 155 | 156 | bool 157 | ChucKListenerCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, void* reserved1) 158 | { 159 | std::string Floatvars = inputs->getParString("Floatvars"); 160 | std::string Intvars = inputs->getParString("Intvars"); 161 | std::string Stringvars = inputs->getParString("Stringvars"); 162 | std::string Floatarrayvars = inputs->getParString("Floatarrayvars"); 163 | std::string Intarrayvars = inputs->getParString("Intarrayvars"); 164 | 165 | auto Floatvarstrings = split(Floatvars, ' '); 166 | auto Intvarstrings = split(Intvars, ' '); 167 | auto Stringvarstrings = split(Stringvars, ' '); 168 | auto Floatarrayvarstrings = split(Floatarrayvars, ' '); 169 | auto Intarrayvarstrings = split(Intarrayvars, ' '); 170 | 171 | myFloatVarNames.clear(); 172 | myIntVarNames.clear(); 173 | myStringVarNames.clear(); 174 | myFloatArrayVarNames.clear(); 175 | myIntArrayVarNames.clear(); 176 | 177 | //std::vector myAssociativeFloatArrayVarNames; 178 | //std::vector myAssociativeIntArrayVarNames; 179 | 180 | for (auto& str : Floatvarstrings) { 181 | myFloatVarNames.push_back(str); 182 | } 183 | 184 | for (auto& str : Intvarstrings) { 185 | myIntVarNames.push_back(str); 186 | } 187 | 188 | for (auto& str : Stringvarstrings) { 189 | myStringVarNames.push_back(str); 190 | } 191 | 192 | for (auto& str : Floatarrayvarstrings) { 193 | myFloatArrayVarNames.push_back(str); 194 | } 195 | 196 | for (auto& str : Intarrayvarstrings) { 197 | myIntArrayVarNames.push_back(str); 198 | } 199 | 200 | //info->sampleRate = 44100.; 201 | info->numSamples = 1; 202 | info->startIndex = 0; 203 | info->numChannels = myFloatVarNames.size() + myIntVarNames.size(); 204 | 205 | return true; 206 | } 207 | 208 | void 209 | ChucKListenerCHOP::getChannelName(int32_t index, OP_String *name, const OP_Inputs* inputs, void* reserved1) 210 | { 211 | 212 | if (index < myFloatVarNames.size()) { 213 | name->setString(myFloatVarNames.at(index).c_str()); 214 | return; 215 | } 216 | 217 | index -= myFloatVarNames.size(); 218 | if (index < myIntVarNames.size()) { 219 | name->setString(myIntVarNames.at(index).c_str()); 220 | return; 221 | } 222 | 223 | name->setString("chan1"); 224 | } 225 | 226 | 227 | void 228 | ChucKListenerCHOP::execute(CHOP_Output* output, 229 | const OP_Inputs* inputs, 230 | void* reserved) 231 | { 232 | myStatus = true; 233 | myError.str(""); 234 | 235 | const OP_CHOPInput* chuckDesignerCHOP = inputs->getParCHOP("Chuck"); 236 | 237 | if (!chuckDesignerCHOP) { 238 | myStatus = false; 239 | myError << "Please select a ChucK instance."; 240 | return; 241 | } 242 | 243 | int chuck_id = ChucK_For_TouchDesigner::getChucKIDForOpID(chuckDesignerCHOP->opId); 244 | 245 | if (chuck_id != m_chuckID) { 246 | // stop all listeners 247 | for (auto varName : myEventVarNames) { 248 | ChucK_For_TouchDesigner::removeListenerCHOP(varName.c_str(), this->myNodeInfo->opId); 249 | } 250 | myEventVarNames.clear(); 251 | 252 | m_chuckID = chuck_id; 253 | } 254 | 255 | if (chuck_id < 0) { 256 | return; 257 | } 258 | 259 | for (const std::string varName : myFloatVarNames) { 260 | ChucK_For_TouchDesigner::getNamedChuckFloat(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedFloatCallback); 261 | } 262 | for (const std::string varName : myIntVarNames) { 263 | ChucK_For_TouchDesigner::getNamedChuckInt(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedIntCallback); 264 | } 265 | for (const std::string varName : myStringVarNames) { 266 | ChucK_For_TouchDesigner::getNamedChuckString(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedStringCallback); 267 | } 268 | for (const std::string varName : myFloatArrayVarNames) { 269 | ChucK_For_TouchDesigner::getNamedGlobalFloatArray(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedFloatArrayCallback); 270 | } 271 | for (const std::string varName : myIntArrayVarNames) { 272 | ChucK_For_TouchDesigner::getNamedGlobalIntArray(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedIntArrayCallback); 273 | } 274 | 275 | #pragma region Events 276 | std::string Eventvars = inputs->getParString("Eventvars"); 277 | 278 | auto Eventvarstrings = split(Eventvars, ' '); 279 | 280 | std::set newEventStrings, eventsToStartListening, eventsToStopListening; 281 | 282 | for (auto& str : Eventvarstrings) { 283 | newEventStrings.insert(str); 284 | } 285 | 286 | set_difference(newEventStrings.begin(), newEventStrings.end(), myEventVarNames.begin(), myEventVarNames.end(), inserter(eventsToStartListening, eventsToStartListening.end())); 287 | set_difference(myEventVarNames.begin(), myEventVarNames.end(), newEventStrings.begin(), newEventStrings.end(), inserter(eventsToStopListening, eventsToStopListening.end())); 288 | 289 | for (const std::string varName : eventsToStopListening) { 290 | bool result = ChucK_For_TouchDesigner::stopListeningForNamedChuckEvent(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedEventNonCallback); 291 | if (result) { 292 | ChucK_For_TouchDesigner::removeListenerCHOP(varName.c_str(), this->myNodeInfo->opId); 293 | } 294 | } 295 | 296 | for (const std::string varName : eventsToStartListening) { 297 | bool result = ChucK_For_TouchDesigner::startListeningForNamedChuckEvent(chuck_id, varName.c_str(), ChucK_For_TouchDesigner::sharedEventCallback); 298 | if (result) { 299 | ChucK_For_TouchDesigner::addListenerCHOP(varName.c_str(), this->myNodeInfo->opId); 300 | 301 | myEventVarNames.insert(varName); 302 | } 303 | } 304 | #pragma endregion Events 305 | 306 | int i = 0; 307 | for (const std::string varName : myFloatVarNames) 308 | { 309 | auto name = varName.c_str(); 310 | t_CKFLOAT val = 0; 311 | if (!ChucK_For_TouchDesigner::getFloat(name, val)) 312 | { 313 | continue; 314 | } 315 | if (i < output->numChannels) { 316 | output->channels[i][0] = val; 317 | } 318 | 319 | i += 1; 320 | 321 | // We'll only be adding one extra argument 322 | PyObject* args = myNodeInfo->context->createArgumentsTuple(2, nullptr); 323 | // The first argument is already set to the 'op' variable, so we set the second argument to our speed value 324 | PyTuple_SET_ITEM(args, 1, PyUnicode_FromString(name)); 325 | PyTuple_SET_ITEM(args, 2, PyFloat_FromDouble(val)); 326 | 327 | 328 | PyObject *result = myNodeInfo->context->callPythonCallback("getFloat", args, nullptr, nullptr); 329 | // callPythonCallback doesn't take ownership of the argts 330 | Py_DECREF(args); 331 | 332 | // We own result now, so we need to Py_DECREF it unless we want to hold onto it 333 | if (result) { Py_DECREF(result); } 334 | } 335 | 336 | for (const std::string varName : myIntVarNames) 337 | { 338 | auto name = varName.c_str(); 339 | t_CKINT val = 0; 340 | if (!ChucK_For_TouchDesigner::getInt(name, val)) { 341 | continue; 342 | } 343 | if (i < output->numChannels) { 344 | output->channels[i][0] = val; 345 | } 346 | 347 | i += 1; 348 | 349 | // We'll only be adding one extra argument 350 | PyObject* args = myNodeInfo->context->createArgumentsTuple(2, nullptr); 351 | // The first argument is already set to the 'op' variable, so we set the second argument to our speed value 352 | PyTuple_SET_ITEM(args, 1, PyUnicode_FromString(name)); 353 | PyTuple_SET_ITEM(args, 2, PyLong_FromLongLong(val)); 354 | 355 | PyObject* result = myNodeInfo->context->callPythonCallback("getInt", args, nullptr, nullptr); 356 | // callPythonCallback doesn't take ownership of the argts 357 | Py_DECREF(args); 358 | 359 | // We own result now, so we need to Py_DECREF it unless we want to hold onto it 360 | if (result) { Py_DECREF(result); } 361 | } 362 | 363 | for (const std::string varName : myStringVarNames) 364 | { 365 | auto name = varName.c_str(); 366 | std::string str = ""; 367 | if (!ChucK_For_TouchDesigner::getString(name, str)) { 368 | continue; 369 | } 370 | 371 | // We'll only be adding one extra argument 372 | PyObject* args = myNodeInfo->context->createArgumentsTuple(2, nullptr); 373 | // The first argument is already set to the 'op' variable, so we set the second argument to our speed value 374 | PyTuple_SET_ITEM(args, 1, PyUnicode_FromString(name)); 375 | PyTuple_SET_ITEM(args, 2, PyUnicode_FromString(str.c_str())); 376 | 377 | PyObject* result = myNodeInfo->context->callPythonCallback("getString", args, nullptr, nullptr); 378 | // callPythonCallback doesn't take ownership of the argts 379 | Py_DECREF(args); 380 | 381 | // We own result now, so we need to Py_DECREF it unless we want to hold onto it 382 | if (result) { Py_DECREF(result); } 383 | } 384 | 385 | for (const std::string varName : myFloatArrayVarNames) 386 | { 387 | auto name = varName.c_str(); 388 | int numItems = 0; 389 | t_CKFLOAT* vec = nullptr; 390 | if (!ChucK_For_TouchDesigner::getFloatArray(name, &vec, numItems)) { 391 | continue; 392 | } 393 | 394 | // We'll only be adding one extra argument 395 | PyObject* args = myNodeInfo->context->createArgumentsTuple(2, nullptr); 396 | // The first argument is already set to the 'op' variable, so we set the second argument to our speed value 397 | PyTuple_SET_ITEM(args, 1, PyUnicode_FromString(name)); 398 | 399 | // todo: return a numpy array 400 | PyObject *lst = PyList_New(numItems); 401 | for (i = 0; i < numItems; i++) { 402 | PyList_SET_ITEM(lst, i, PyFloat_FromDouble(*(vec++))); 403 | } 404 | 405 | PyTuple_SET_ITEM(args, 2, lst); 406 | 407 | PyObject *result = myNodeInfo->context->callPythonCallback("getFloatArray", args, nullptr, nullptr); 408 | // callPythonCallback doesn't take ownership of the argts 409 | Py_DECREF(args); 410 | //Py_DECREF(lst); // todo? 411 | 412 | // We own result now, so we need to Py_DECREF it unless we want to hold onto it 413 | if (result) { Py_DECREF(result); } 414 | } 415 | 416 | for (const std::string varName : myIntArrayVarNames) 417 | { 418 | auto name = varName.c_str(); 419 | int numItems = 0; 420 | t_CKINT* vec = nullptr; 421 | 422 | if (!ChucK_For_TouchDesigner::getIntArray(name, &vec, numItems)) { 423 | continue; 424 | } 425 | 426 | // We'll only be adding one extra argument 427 | PyObject* args = myNodeInfo->context->createArgumentsTuple(2, nullptr); 428 | // The first argument is already set to the 'op' variable, so we set the second argument to our speed value 429 | PyTuple_SET_ITEM(args, 1, PyUnicode_FromString(name)); 430 | 431 | // todo: return a numpy array 432 | PyObject *lst = PyList_New(numItems); 433 | for (i = 0; i < numItems; i++) { 434 | PyList_SET_ITEM(lst, i, PyLong_FromLongLong(*(vec++))); 435 | } 436 | 437 | PyTuple_SET_ITEM(args, 2, lst); 438 | 439 | PyObject* result = myNodeInfo->context->callPythonCallback("getIntArray", args, nullptr, nullptr); 440 | // callPythonCallback doesn't take ownership of the argts 441 | Py_DECREF(args); 442 | //Py_DECREF(lst); // todo? 443 | 444 | // We own result now, so we need to Py_DECREF it unless we want to hold onto it 445 | if (result) { Py_DECREF(result); } 446 | } 447 | 448 | for (const std::string varName : myEventVarNames) 449 | { 450 | auto name = varName.c_str(); 451 | int count = ChucK_For_TouchDesigner::queryEvent(name, this->myNodeInfo->opId); 452 | for (int i = 0; i < count; i++ ) { 453 | // We'll only be adding one extra argument 454 | PyObject* args = myNodeInfo->context->createArgumentsTuple(1, nullptr); 455 | // The first argument is already set to the 'op' variable, so we set the second argument to our speed value 456 | PyTuple_SET_ITEM(args, 1, PyUnicode_FromString(name)); 457 | 458 | PyObject* result = myNodeInfo->context->callPythonCallback("getEvent", args, nullptr, nullptr); 459 | // callPythonCallback doesn't take ownership of the argts 460 | Py_DECREF(args); 461 | 462 | // We own result now, so we need to Py_DECREF it unless we want to hold onto it 463 | if (result) { Py_DECREF(result); } 464 | } 465 | } 466 | 467 | } 468 | 469 | void 470 | ChucKListenerCHOP::getErrorString(OP_String* error, void* reserved1) { 471 | 472 | if (!myStatus) { 473 | error->setString(myError.str().c_str()); 474 | } 475 | } 476 | 477 | int32_t 478 | ChucKListenerCHOP::getNumInfoCHOPChans(void * reserved1) 479 | { 480 | // We return the number of channel we want to output to any Info CHOP 481 | // connected to the CHOP. In this example we are just going to send one channel. 482 | return 1; 483 | } 484 | 485 | void 486 | ChucKListenerCHOP::getInfoCHOPChan(int32_t index, 487 | OP_InfoCHOPChan* chan, 488 | void* reserved1) 489 | { 490 | // This function will be called once for each channel we said we'd want to return 491 | // In this example it'll only be called once. 492 | 493 | if (index == 0) 494 | { 495 | chan->name->setString("executeCount"); 496 | chan->value = (float)myExecuteCount; 497 | } 498 | 499 | // todo: show number of shreds here 500 | } 501 | 502 | bool 503 | ChucKListenerCHOP::getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved1) 504 | { 505 | infoSize->rows = 1; 506 | infoSize->cols = 2; 507 | // Setting this to false means we'll be assigning values to the table 508 | // one row at a time. True means we'll do it one column at a time. 509 | infoSize->byColumn = false; 510 | return true; 511 | } 512 | 513 | void 514 | ChucKListenerCHOP::getInfoDATEntries(int32_t index, 515 | int32_t nEntries, 516 | OP_InfoDATEntries* entries, 517 | void* reserved1) 518 | { 519 | char tempBuffer[4096]; 520 | 521 | if (index == 0) 522 | { 523 | // Set the value for the first column 524 | entries->values[0]->setString("executeCount"); 525 | 526 | // Set the value for the second column 527 | #ifdef _WIN32 528 | sprintf_s(tempBuffer, "%d", myExecuteCount); 529 | #else // macOS 530 | snprintf(tempBuffer, sizeof(tempBuffer), "%d", myExecuteCount); 531 | #endif 532 | entries->values[1]->setString(tempBuffer); 533 | } 534 | } 535 | 536 | void 537 | ChucKListenerCHOP::setupParameters(OP_ParameterManager* manager, void *reserved1) 538 | { 539 | // main instance 540 | { 541 | OP_StringParameter sp; 542 | 543 | sp.name = "Chuck"; 544 | sp.label = "Chuck Instance"; 545 | 546 | sp.defaultValue = ""; 547 | 548 | OP_ParAppendResult res = manager->appendCHOP(sp); 549 | assert(res == OP_ParAppendResult::Success); 550 | } 551 | 552 | { 553 | OP_StringParameter sp; 554 | 555 | sp.name = "Floatvars"; 556 | sp.label = "Float Variables"; 557 | 558 | sp.defaultValue = ""; 559 | 560 | OP_ParAppendResult res = manager->appendString(sp); 561 | assert(res == OP_ParAppendResult::Success); 562 | } 563 | 564 | { 565 | OP_StringParameter sp; 566 | 567 | sp.name = "Intvars"; 568 | sp.label = "Int Variables"; 569 | 570 | sp.defaultValue = ""; 571 | 572 | OP_ParAppendResult res = manager->appendString(sp); 573 | assert(res == OP_ParAppendResult::Success); 574 | } 575 | 576 | { 577 | OP_StringParameter sp; 578 | 579 | sp.name = "Stringvars"; 580 | sp.label = "String Variables"; 581 | 582 | sp.defaultValue = ""; 583 | 584 | OP_ParAppendResult res = manager->appendString(sp); 585 | assert(res == OP_ParAppendResult::Success); 586 | } 587 | 588 | { 589 | OP_StringParameter sp; 590 | 591 | sp.name = "Floatarrayvars"; 592 | sp.label = "Float Array Variables"; 593 | 594 | sp.defaultValue = ""; 595 | 596 | OP_ParAppendResult res = manager->appendString(sp); 597 | assert(res == OP_ParAppendResult::Success); 598 | } 599 | 600 | { 601 | OP_StringParameter sp; 602 | 603 | sp.name = "Intarrayvars"; 604 | sp.label = "Int Array Variables"; 605 | 606 | sp.defaultValue = ""; 607 | 608 | OP_ParAppendResult res = manager->appendString(sp); 609 | assert(res == OP_ParAppendResult::Success); 610 | } 611 | 612 | { 613 | OP_StringParameter sp; 614 | 615 | sp.name = "Eventvars"; 616 | sp.label = "Event Variables"; 617 | 618 | sp.defaultValue = ""; 619 | 620 | OP_ParAppendResult res = manager->appendString(sp); 621 | assert(res == OP_ParAppendResult::Success); 622 | } 623 | } 624 | 625 | void 626 | ChucKListenerCHOP::pulsePressed(const char* name, void* reserved1) 627 | { 628 | 629 | } 630 | -------------------------------------------------------------------------------- /src/ChucKListenerCHOP.h: -------------------------------------------------------------------------------- 1 | /* Shared Use License: This file is owned by Derivative Inc. (Derivative) 2 | * and can only be used, and/or modified for use, in conjunction with 3 | * Derivative's TouchDesigner software, and only if you are a licensee who has 4 | * accepted Derivative's TouchDesigner license or assignment agreement 5 | * (which also govern the use of this file). You may share or redistribute 6 | * a modified version of this file provided the following conditions are met: 7 | * 8 | * 1. The shared file or redistribution must retain the information set out 9 | * above and this list of conditions. 10 | * 2. Derivative's name (Derivative Inc.) or its trademarks may not be used 11 | * to endorse or promote products derived from this file without specific 12 | * prior written permission from Derivative. 13 | */ 14 | 15 | #pragma once 16 | #include "CHOP_CPlusPlusBase.h" 17 | using namespace TD; 18 | 19 | #include "chuck.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // To get more help about these functions, look at CHOP_CPlusPlusBase.h 27 | class ChucKListenerCHOP : public CHOP_CPlusPlusBase 28 | { 29 | public: 30 | ChucKListenerCHOP(const OP_NodeInfo* info); 31 | virtual ~ChucKListenerCHOP(); 32 | 33 | virtual void getGeneralInfo(CHOP_GeneralInfo*, const OP_Inputs*, void*) override; 34 | virtual bool getOutputInfo(CHOP_OutputInfo*, const OP_Inputs*, void*) override; 35 | virtual void getChannelName(int32_t index, OP_String* name, const OP_Inputs*, void* reserved) override; 36 | 37 | virtual void execute(CHOP_Output*, 38 | const OP_Inputs*, 39 | void* reserved) override; 40 | 41 | 42 | virtual int32_t getNumInfoCHOPChans(void* reserved1) override; 43 | virtual void getInfoCHOPChan(int index, 44 | OP_InfoCHOPChan* chan, 45 | void* reserved1) override; 46 | 47 | virtual bool getInfoDATSize(OP_InfoDATSize* infoSize, void* resereved1) override; 48 | virtual void getInfoDATEntries(int32_t index, 49 | int32_t nEntries, 50 | OP_InfoDATEntries* entries, 51 | void* reserved1) override; 52 | 53 | virtual void setupParameters(OP_ParameterManager* manager, void* reserved1) override; 54 | virtual void pulsePressed(const char* name, void* reserved1) override; 55 | 56 | virtual void getErrorString(OP_String* error, void* reserved1) override; 57 | 58 | private: 59 | 60 | // We don't need to store this pointer, but we do for the example. 61 | // The OP_NodeInfo class store information about the node that's using 62 | // this instance of the class (like its name). 63 | const OP_NodeInfo* myNodeInfo; 64 | 65 | // In this example this value will be incremented each time the execute() 66 | // function is called, then passes back to the CHOP 67 | int32_t myExecuteCount; 68 | 69 | bool myStatus = false; 70 | 71 | std::stringstream myError; 72 | std::vector myFloatVarNames; 73 | std::vector myIntVarNames; 74 | std::vector myStringVarNames; 75 | std::vector myFloatArrayVarNames; 76 | std::vector myIntArrayVarNames; 77 | std::set myEventVarNames; 78 | 79 | std::vector myAssociativeFloatArrayVarNames; 80 | std::vector myAssociativeIntArrayVarNames; 81 | 82 | unsigned int m_chuckID = -1; 83 | }; 84 | -------------------------------------------------------------------------------- /src/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2021 Derivative. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Plugin_ChucK.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Plugin_ChucK.cpp 3 | // AudioPluginDemo 4 | // 5 | // Created by Jack Atherton on 4/19/17. 6 | // Modified by David Braun for TouchDesigner on 12/19/21 7 | // 8 | // 9 | 10 | 11 | #include "Plugin_ChucK.h" 12 | #include "chuck_globals.h" 13 | 14 | #include "chuck_vm.h" 15 | #include "util_platforms.h" // for ck_usleep 16 | 17 | #include 18 | #include 19 | 20 | #ifndef WIN32 21 | #include 22 | #endif 23 | 24 | #include 25 | 26 | namespace ChucK_For_TouchDesigner 27 | { 28 | enum Param 29 | { 30 | P_CHUCKID, 31 | P_NUM 32 | }; 33 | 34 | struct EffectData 35 | { 36 | struct Data 37 | { 38 | float p[P_NUM]; 39 | t_CKINT myId; 40 | bool initialized; 41 | }; 42 | union 43 | { 44 | Data data; 45 | unsigned char pad[(sizeof(Data) + 15) & ~15]; // This entire structure must be a multiple of 16 bytes (and and instance 16 byte aligned) for PS3 SPU DMA requirements 46 | }; 47 | }; 48 | 49 | std::map< unsigned int, ChucK* > chuck_instances; 50 | std::map< unsigned int, EffectData::Data* > data_instances; 51 | unsigned int _nextValidID = 0; 52 | std::map< unsigned int, unsigned int> op_ids_to_chuck_ids; 53 | static std::map myFloatVars; 54 | static std::map myIntVars; 55 | static std::map myStringVars; 56 | 57 | static std::map myFloatArrayVars; 58 | static std::map myFloatArrayVarSizes; 59 | 60 | static std::map myIntArrayVars; 61 | static std::map myIntArrayVarSizes; 62 | 63 | static std::map myVoidEventCallbacks; 64 | 65 | static std::map> myEventToListenerIDs; 66 | 67 | static std::map> myEventToListenerIDCount; 68 | 69 | // C# "string" corresponds to passing char * 70 | CHUCKDESIGNERSHARED_API bool runChuckCode(unsigned int chuckID, const char* code) 71 | { 72 | if (chuck_instances.count(chuckID) == 0) { return false; } 73 | 74 | // don't want to replace dac 75 | // (a safeguard in case compiler got interrupted while replacing dac) 76 | chuck_instances[chuckID]->compiler()->setReplaceDac(FALSE, ""); 77 | 78 | // compile it! 79 | return chuck_instances[chuckID]->compileCode( 80 | std::string(code), std::string("")); 81 | } 82 | 83 | 84 | CHUCKDESIGNERSHARED_API bool runChuckCodeWithReplacementDac( 85 | unsigned int chuckID, const char* code, const char* replacement_dac) 86 | { 87 | if (chuck_instances.count(chuckID) == 0) { return false; } 88 | 89 | // replace dac 90 | chuck_instances[chuckID]->compiler()->setReplaceDac(TRUE, 91 | std::string(replacement_dac)); 92 | 93 | // compile it! 94 | bool ret = chuck_instances[chuckID]->compileCode( 95 | std::string(code), std::string("")); 96 | 97 | // don't replace dac for future compilations 98 | chuck_instances[chuckID]->compiler()->setReplaceDac(FALSE, ""); 99 | 100 | return ret; 101 | } 102 | 103 | 104 | CHUCKDESIGNERSHARED_API bool runChuckFile(unsigned int chuckID, 105 | const char* filename) 106 | { 107 | // run with empty args 108 | return runChuckFileWithArgs(chuckID, filename, ""); 109 | } 110 | 111 | 112 | CHUCKDESIGNERSHARED_API bool runChuckFileWithArgs(unsigned int chuckID, 113 | const char* filename, const char* args) 114 | { 115 | if (chuck_instances.count(chuckID) == 0) { return false; } 116 | 117 | // don't want to replace dac 118 | // (a safeguard in case compiler got interrupted while replacing dac) 119 | chuck_instances[chuckID]->compiler()->setReplaceDac(FALSE, ""); 120 | 121 | // compile it! 122 | return chuck_instances[chuckID]->compileFile( 123 | std::string(filename), std::string(args) 124 | ); 125 | } 126 | 127 | 128 | CHUCKDESIGNERSHARED_API bool runChuckFileWithReplacementDac( 129 | unsigned int chuckID, const char* filename, 130 | const char* replacement_dac) 131 | { 132 | // run with empty args 133 | return runChuckFileWithArgsWithReplacementDac( 134 | chuckID, filename, "", replacement_dac 135 | ); 136 | } 137 | 138 | 139 | CHUCKDESIGNERSHARED_API bool runChuckFileWithArgsWithReplacementDac( 140 | unsigned int chuckID, const char* filename, const char* args, 141 | const char* replacement_dac) 142 | { 143 | if (chuck_instances.count(chuckID) == 0) { return false; } 144 | 145 | // replace dac 146 | chuck_instances[chuckID]->compiler()->setReplaceDac(TRUE, 147 | std::string(replacement_dac)); 148 | 149 | // compile it! 150 | bool ret = chuck_instances[chuckID]->compileFile( 151 | std::string(filename), std::string(args) 152 | ); 153 | 154 | // don't replace dac for future compilations 155 | chuck_instances[chuckID]->compiler()->setReplaceDac(FALSE, ""); 156 | 157 | return ret; 158 | } 159 | 160 | 161 | CHUCKDESIGNERSHARED_API bool setChuckInt(unsigned int chuckID, const char* name, t_CKINT val) 162 | { 163 | if (chuck_instances.count(chuckID) == 0) { return false; } 164 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 165 | if (gm == NULL) { return false; } 166 | 167 | return gm->setGlobalInt(name, val); 168 | } 169 | 170 | 171 | CHUCKDESIGNERSHARED_API bool getChuckInt(unsigned int chuckID, const char* name, void (*callback)(t_CKINT)) 172 | { 173 | if (chuck_instances.count(chuckID) == 0) { return false; } 174 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 175 | if (gm == NULL) { return false; } 176 | 177 | return gm->getGlobalInt(name, callback); 178 | } 179 | 180 | 181 | CHUCKDESIGNERSHARED_API bool getNamedChuckInt(unsigned int chuckID, const char* name, void (*callback)(const char*, t_CKINT)) 182 | { 183 | if (chuck_instances.count(chuckID) == 0) { return false; } 184 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 185 | if (gm == NULL) { return false; } 186 | 187 | return gm->getGlobalInt(name, callback); 188 | } 189 | 190 | 191 | CHUCKDESIGNERSHARED_API bool getChuckIntWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT, t_CKINT)) 192 | { 193 | if (chuck_instances.count(chuckID) == 0) { return false; } 194 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 195 | if (gm == NULL) { return false; } 196 | 197 | return gm->getGlobalInt(name, callbackID, callback); 198 | } 199 | 200 | 201 | CHUCKDESIGNERSHARED_API bool setChuckFloat(unsigned int chuckID, const char* name, t_CKFLOAT val) 202 | { 203 | if (chuck_instances.count(chuckID) == 0) { return false; } 204 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 205 | if (gm == NULL) { return false; } 206 | 207 | return gm->setGlobalFloat(name, val); 208 | } 209 | 210 | 211 | CHUCKDESIGNERSHARED_API bool getChuckFloat(unsigned int chuckID, const char* name, void (*callback)(t_CKFLOAT)) 212 | { 213 | if (chuck_instances.count(chuckID) == 0) { return false; } 214 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 215 | if (gm == NULL) { return false; } 216 | 217 | return gm->getGlobalFloat(name, callback); 218 | } 219 | 220 | 221 | CHUCKDESIGNERSHARED_API bool getNamedChuckFloat(unsigned int chuckID, const char* name, void (*callback)(const char*, t_CKFLOAT)) 222 | { 223 | if (chuck_instances.count(chuckID) == 0) { return false; } 224 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 225 | if (gm == NULL) { return false; } 226 | 227 | return gm->getGlobalFloat(name, callback); 228 | } 229 | 230 | 231 | CHUCKDESIGNERSHARED_API bool getChuckFloatWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT, t_CKFLOAT)) 232 | { 233 | if (chuck_instances.count(chuckID) == 0) { return false; } 234 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 235 | if (gm == NULL) { return false; } 236 | 237 | return gm->getGlobalFloat(name, callbackID, callback); 238 | } 239 | 240 | 241 | CHUCKDESIGNERSHARED_API bool setChuckString(unsigned int chuckID, const char* name, const char* val) 242 | { 243 | if (chuck_instances.count(chuckID) == 0) { return false; } 244 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 245 | if (gm == NULL) { return false; } 246 | 247 | return gm->setGlobalString(name, val); 248 | } 249 | 250 | 251 | CHUCKDESIGNERSHARED_API bool getChuckString(unsigned int chuckID, const char* name, void (*callback)(const char*)) 252 | { 253 | if (chuck_instances.count(chuckID) == 0) { return false; } 254 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 255 | if (gm == NULL) { return false; } 256 | 257 | return gm->getGlobalString(name, callback); 258 | } 259 | 260 | 261 | CHUCKDESIGNERSHARED_API bool getNamedChuckString(unsigned int chuckID, const char* name, void (*callback)(const char*, const char*)) 262 | { 263 | if (chuck_instances.count(chuckID) == 0) { return false; } 264 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 265 | if (gm == NULL) { return false; } 266 | 267 | return gm->getGlobalString(name, callback); 268 | } 269 | 270 | 271 | CHUCKDESIGNERSHARED_API bool getChuckStringWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT, const char*)) 272 | { 273 | if (chuck_instances.count(chuckID) == 0) { return false; } 274 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 275 | if (gm == NULL) { return false; } 276 | 277 | return gm->getGlobalString(name, callbackID, callback); 278 | } 279 | 280 | 281 | CHUCKDESIGNERSHARED_API bool signalChuckEvent(unsigned int chuckID, const char* name) 282 | { 283 | if (chuck_instances.count(chuckID) == 0) { return false; } 284 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 285 | if (gm == NULL) { return false; } 286 | 287 | return gm->signalGlobalEvent(name); 288 | } 289 | 290 | 291 | CHUCKDESIGNERSHARED_API bool broadcastChuckEvent(unsigned int chuckID, const char* name) 292 | { 293 | if (chuck_instances.count(chuckID) == 0) { return false; } 294 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 295 | if (gm == NULL) { return false; } 296 | 297 | return gm->broadcastGlobalEvent(name); 298 | } 299 | 300 | 301 | CHUCKDESIGNERSHARED_API bool listenForChuckEventOnce(unsigned int chuckID, const char* name, void (*callback)(void)) 302 | { 303 | if (chuck_instances.count(chuckID) == 0) { return false; } 304 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 305 | if (gm == NULL) { return false; } 306 | 307 | return gm->listenForGlobalEvent( 308 | name, callback, FALSE); 309 | } 310 | 311 | 312 | CHUCKDESIGNERSHARED_API bool listenForNamedChuckEventOnce(unsigned int chuckID, const char* name, void (*callback)(const char*)) 313 | { 314 | if (chuck_instances.count(chuckID) == 0) { return false; } 315 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 316 | if (gm == NULL) { return false; } 317 | 318 | return gm->listenForGlobalEvent( 319 | name, callback, FALSE); 320 | } 321 | 322 | 323 | CHUCKDESIGNERSHARED_API bool listenForChuckEventOnceWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT)) 324 | { 325 | if (chuck_instances.count(chuckID) == 0) { return false; } 326 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 327 | if (gm == NULL) { return false; } 328 | 329 | return gm->listenForGlobalEvent( 330 | name, callbackID, callback, FALSE); 331 | } 332 | 333 | 334 | CHUCKDESIGNERSHARED_API bool startListeningForChuckEvent(unsigned int chuckID, const char* name, void (*callback)(void)) 335 | { 336 | if (chuck_instances.count(chuckID) == 0) { return false; } 337 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 338 | if (gm == NULL) { return false; } 339 | 340 | return gm->listenForGlobalEvent( 341 | name, callback, TRUE); 342 | } 343 | 344 | 345 | CHUCKDESIGNERSHARED_API bool startListeningForNamedChuckEvent(unsigned int chuckID, const char* name, void (*callback)(const char*)) 346 | { 347 | if (chuck_instances.count(chuckID) == 0) { return false; } 348 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 349 | if (gm == NULL) { return false; } 350 | 351 | return gm->listenForGlobalEvent( 352 | name, callback, TRUE); 353 | } 354 | 355 | 356 | CHUCKDESIGNERSHARED_API bool startListeningForChuckEventWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT)) 357 | { 358 | if (chuck_instances.count(chuckID) == 0) { return false; } 359 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 360 | if (gm == NULL) { return false; } 361 | 362 | return gm->listenForGlobalEvent( 363 | name, callbackID, callback, TRUE); 364 | } 365 | 366 | 367 | CHUCKDESIGNERSHARED_API bool stopListeningForChuckEvent(unsigned int chuckID, const char* name, void (*callback)(void)) 368 | { 369 | if (chuck_instances.count(chuckID) == 0) { return false; } 370 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 371 | if (gm == NULL) { return false; } 372 | 373 | return gm->stopListeningForGlobalEvent( 374 | name, callback); 375 | } 376 | 377 | 378 | CHUCKDESIGNERSHARED_API bool stopListeningForNamedChuckEvent(unsigned int chuckID, const char* name, void (*callback)(const char*)) 379 | { 380 | if (chuck_instances.count(chuckID) == 0) { return false; } 381 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 382 | if (gm == NULL) { return false; } 383 | 384 | return gm->stopListeningForGlobalEvent( 385 | name, callback); 386 | } 387 | 388 | 389 | CHUCKDESIGNERSHARED_API bool stopListeningForChuckEventWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT)) 390 | { 391 | if (chuck_instances.count(chuckID) == 0) { return false; } 392 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 393 | if (gm == NULL) { return false; } 394 | 395 | return gm->stopListeningForGlobalEvent( 396 | name, callbackID, callback); 397 | } 398 | 399 | 400 | CHUCKDESIGNERSHARED_API bool getGlobalUGenSamples(unsigned int chuckID, 401 | const char* name, SAMPLE* buffer, int numSamples) 402 | { 403 | if (chuck_instances.count(chuckID) == 0) { return false; } 404 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 405 | if (gm == NULL) { return false; } 406 | 407 | if (!gm->getGlobalUGenSamples( 408 | name, buffer, numSamples)) 409 | { 410 | // failed. fill with zeroes. 411 | memset(buffer, 0, sizeof(SAMPLE) * numSamples); 412 | return false; 413 | } 414 | 415 | return true; 416 | } 417 | 418 | 419 | // int array methods 420 | CHUCKDESIGNERSHARED_API bool setGlobalIntArray(unsigned int chuckID, 421 | const char* name, t_CKINT arrayValues[], unsigned int numValues) 422 | { 423 | if (chuck_instances.count(chuckID) == 0) { return false; } 424 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 425 | if (gm == NULL) { return false; } 426 | 427 | return gm->setGlobalIntArray( 428 | name, arrayValues, numValues); 429 | } 430 | 431 | 432 | CHUCKDESIGNERSHARED_API bool getGlobalIntArray(unsigned int chuckID, 433 | const char* name, void (*callback)(t_CKINT[], t_CKUINT)) 434 | { 435 | if (chuck_instances.count(chuckID) == 0) { return false; } 436 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 437 | if (gm == NULL) { return false; } 438 | 439 | return gm->getGlobalIntArray( 440 | name, callback); 441 | } 442 | 443 | 444 | CHUCKDESIGNERSHARED_API bool getNamedGlobalIntArray(unsigned int chuckID, 445 | const char* name, void (*callback)(const char*, t_CKINT[], t_CKUINT)) 446 | { 447 | if (chuck_instances.count(chuckID) == 0) { return false; } 448 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 449 | if (gm == NULL) { return false; } 450 | 451 | return gm->getGlobalIntArray( 452 | name, callback); 453 | } 454 | 455 | 456 | CHUCKDESIGNERSHARED_API bool getGlobalIntArrayWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT, t_CKINT[], t_CKUINT)) 457 | { 458 | if (chuck_instances.count(chuckID) == 0) { return false; } 459 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 460 | if (gm == NULL) { return false; } 461 | 462 | return gm->getGlobalIntArray( 463 | name, callbackID, callback); 464 | } 465 | 466 | 467 | CHUCKDESIGNERSHARED_API bool setGlobalIntArrayValue(unsigned int chuckID, 468 | const char* name, unsigned int index, t_CKINT value) 469 | { 470 | if (chuck_instances.count(chuckID) == 0) { return false; } 471 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 472 | if (gm == NULL) { return false; } 473 | 474 | return gm->setGlobalIntArrayValue( 475 | name, index, value); 476 | } 477 | 478 | 479 | CHUCKDESIGNERSHARED_API bool getGlobalIntArrayValue(unsigned int chuckID, 480 | const char* name, unsigned int index, void (*callback)(t_CKINT)) 481 | { 482 | if (chuck_instances.count(chuckID) == 0) { return false; } 483 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 484 | if (gm == NULL) { return false; } 485 | 486 | return gm->getGlobalIntArrayValue( 487 | name, index, callback); 488 | } 489 | 490 | 491 | CHUCKDESIGNERSHARED_API bool getNamedGlobalIntArrayValue(unsigned int chuckID, 492 | const char* name, unsigned int index, void (*callback)(const char*, t_CKINT)) 493 | { 494 | if (chuck_instances.count(chuckID) == 0) { return false; } 495 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 496 | if (gm == NULL) { return false; } 497 | 498 | return gm->getGlobalIntArrayValue( 499 | name, index, callback); 500 | } 501 | 502 | 503 | CHUCKDESIGNERSHARED_API bool getGlobalIntArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, unsigned int index, void(*callback)(t_CKINT, t_CKINT)) 504 | { 505 | if (chuck_instances.count(chuckID) == 0) { return false; } 506 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 507 | if (gm == NULL) { return false; } 508 | 509 | return gm->getGlobalIntArrayValue( 510 | name, callbackID, index, callback); 511 | } 512 | 513 | 514 | CHUCKDESIGNERSHARED_API bool setGlobalAssociativeIntArrayValue( 515 | unsigned int chuckID, const char* name, char* key, t_CKINT value) 516 | { 517 | if (chuck_instances.count(chuckID) == 0) { return false; } 518 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 519 | if (gm == NULL) { return false; } 520 | 521 | return gm->setGlobalAssociativeIntArrayValue( 522 | name, key, value); 523 | } 524 | 525 | 526 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeIntArrayValue( 527 | unsigned int chuckID, const char* name, char* key, 528 | void (*callback)(t_CKINT)) 529 | { 530 | if (chuck_instances.count(chuckID) == 0) { return false; } 531 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 532 | if (gm == NULL) { return false; } 533 | 534 | return gm->getGlobalAssociativeIntArrayValue( 535 | name, key, callback); 536 | } 537 | 538 | 539 | CHUCKDESIGNERSHARED_API bool getNamedGlobalAssociativeIntArrayValue( 540 | unsigned int chuckID, const char* name, char* key, 541 | void (*callback)(const char*, t_CKINT)) 542 | { 543 | if (chuck_instances.count(chuckID) == 0) { return false; } 544 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 545 | if (gm == NULL) { return false; } 546 | 547 | return gm->getGlobalAssociativeIntArrayValue( 548 | name, key, callback); 549 | } 550 | 551 | 552 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeIntArrayValueWithID( 553 | unsigned int chuckID, t_CKINT callbackID, const char* name, 554 | char* key, void(*callback)(t_CKINT, t_CKINT)) 555 | { 556 | if (chuck_instances.count(chuckID) == 0) { return false; } 557 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 558 | if (gm == NULL) { return false; } 559 | 560 | return gm->getGlobalAssociativeIntArrayValue( 561 | name, callbackID, key, callback); 562 | } 563 | 564 | // internal/audio-thread-friendly global array setter 565 | CHUCKDESIGNERSHARED_API bool setGlobalIntArray_AT( 566 | unsigned int chuckID,const char* name, t_CKINT arrayValues[], unsigned int numValues) 567 | { 568 | if (chuck_instances.count(chuckID) == 0) { return false; } 569 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 570 | if (gm == NULL) { return false; } 571 | 572 | return gm->set_global_int_array(name, arrayValues, numValues); 573 | } 574 | 575 | // internal/audio-thread-friendly 576 | CHUCKDESIGNERSHARED_API bool setGlobalIntArrayValue_AT( 577 | unsigned int chuckID, const char* name, unsigned int index, t_CKINT value) 578 | { 579 | if (chuck_instances.count(chuckID) == 0) { return false; } 580 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 581 | if (gm == NULL) { return false; } 582 | 583 | return gm->set_global_float_array_value(name, index, value); 584 | } 585 | 586 | // float array methods 587 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArray(unsigned int chuckID, 588 | const char* name, t_CKFLOAT arrayValues[], unsigned int numValues) 589 | { 590 | if (chuck_instances.count(chuckID) == 0) { return false; } 591 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 592 | if (gm == NULL) { return false; } 593 | 594 | return gm->setGlobalFloatArray( 595 | name, arrayValues, numValues); 596 | } 597 | 598 | 599 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArray(unsigned int chuckID, 600 | const char* name, void (*callback)(t_CKFLOAT[], t_CKUINT)) 601 | { 602 | if (chuck_instances.count(chuckID) == 0) { return false; } 603 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 604 | if (gm == NULL) { return false; } 605 | 606 | return gm->getGlobalFloatArray( 607 | name, callback); 608 | } 609 | 610 | 611 | CHUCKDESIGNERSHARED_API bool getNamedGlobalFloatArray(unsigned int chuckID, 612 | const char* name, void (*callback)(const char*, t_CKFLOAT[], t_CKUINT)) 613 | { 614 | if (chuck_instances.count(chuckID) == 0) { return false; } 615 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 616 | if (gm == NULL) { return false; } 617 | 618 | return gm->getGlobalFloatArray( 619 | name, callback); 620 | } 621 | 622 | 623 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArrayWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void(*callback)(t_CKINT, t_CKFLOAT[], t_CKUINT)) 624 | { 625 | if (chuck_instances.count(chuckID) == 0) { return false; } 626 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 627 | if (gm == NULL) { return false; } 628 | 629 | return gm->getGlobalFloatArray( 630 | name, callbackID, callback); 631 | } 632 | 633 | 634 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArrayValue(unsigned int chuckID, 635 | const char* name, unsigned int index, t_CKFLOAT value) 636 | { 637 | if (chuck_instances.count(chuckID) == 0) { return false; } 638 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 639 | if (gm == NULL) { return false; } 640 | 641 | return gm->setGlobalFloatArrayValue( 642 | name, index, value); 643 | } 644 | 645 | 646 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArrayValue(unsigned int chuckID, 647 | const char* name, unsigned int index, void (*callback)(t_CKFLOAT)) 648 | { 649 | if (chuck_instances.count(chuckID) == 0) { return false; } 650 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 651 | if (gm == NULL) { return false; } 652 | 653 | return gm->getGlobalFloatArrayValue( 654 | name, index, callback); 655 | } 656 | 657 | 658 | CHUCKDESIGNERSHARED_API bool getNamedGlobalFloatArrayValue(unsigned int chuckID, 659 | const char* name, unsigned int index, void (*callback)(const char*, t_CKFLOAT)) 660 | { 661 | if (chuck_instances.count(chuckID) == 0) { return false; } 662 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 663 | if (gm == NULL) { return false; } 664 | 665 | return gm->getGlobalFloatArrayValue( 666 | name, index, callback); 667 | } 668 | 669 | 670 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, unsigned int index, void(*callback)(t_CKINT, t_CKFLOAT)) 671 | { 672 | if (chuck_instances.count(chuckID) == 0) { return false; } 673 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 674 | if (gm == NULL) { return false; } 675 | 676 | return gm->getGlobalFloatArrayValue( 677 | name, callbackID, index, callback); 678 | } 679 | 680 | 681 | CHUCKDESIGNERSHARED_API bool setGlobalAssociativeFloatArrayValue( 682 | unsigned int chuckID, const char* name, char* key, t_CKFLOAT value) 683 | { 684 | if (chuck_instances.count(chuckID) == 0) { return false; } 685 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 686 | if (gm == NULL) { return false; } 687 | 688 | return gm->setGlobalAssociativeFloatArrayValue( 689 | name, key, value); 690 | } 691 | 692 | 693 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeFloatArrayValue( 694 | unsigned int chuckID, const char* name, char* key, 695 | void (*callback)(t_CKFLOAT)) 696 | { 697 | if (chuck_instances.count(chuckID) == 0) { return false; } 698 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 699 | if (gm == NULL) { return false; } 700 | 701 | return gm->getGlobalAssociativeFloatArrayValue( 702 | name, key, callback); 703 | } 704 | 705 | 706 | CHUCKDESIGNERSHARED_API bool getNamedGlobalAssociativeFloatArrayValue( 707 | unsigned int chuckID, const char* name, char* key, 708 | void (*callback)(const char*, t_CKFLOAT)) 709 | { 710 | if (chuck_instances.count(chuckID) == 0) { return false; } 711 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 712 | if (gm == NULL) { return false; } 713 | 714 | return gm->getGlobalAssociativeFloatArrayValue( 715 | name, key, callback); 716 | } 717 | 718 | 719 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeFloatArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, char* key, void(*callback)(t_CKINT, t_CKFLOAT)) 720 | { 721 | if (chuck_instances.count(chuckID) == 0) { return false; } 722 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 723 | if (gm == NULL) { return false; } 724 | 725 | return gm->getGlobalAssociativeFloatArrayValue( 726 | name, callbackID, key, callback); 727 | } 728 | 729 | 730 | // internal/audio-thread-friendly global array setter 731 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArray_AT( 732 | unsigned int chuckID, const char* name, t_CKFLOAT arrayValues[], unsigned int numValues) 733 | { 734 | if (chuck_instances.count(chuckID) == 0) { return false; } 735 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 736 | if (gm == NULL) { return false; } 737 | 738 | return gm->set_global_float_array(name, arrayValues, numValues); 739 | } 740 | 741 | 742 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArrayValue_AT( 743 | unsigned int chuckID, const char* name, unsigned int index, t_CKFLOAT value) { 744 | if (chuck_instances.count(chuckID) == 0) { return false; } 745 | Chuck_Globals_Manager* gm = chuck_instances[chuckID]->globals(); 746 | if (gm == NULL) { return false; } 747 | 748 | return gm->set_global_float_array_value(name, index, value); 749 | } 750 | 751 | 752 | CHUCKDESIGNERSHARED_API bool setChoutCallback(unsigned int chuckID, void (*callback)(const char*)) 753 | { 754 | return chuck_instances[chuckID]->setChoutCallback(callback); 755 | } 756 | 757 | 758 | CHUCKDESIGNERSHARED_API bool setCherrCallback(unsigned int chuckID, void (*callback)(const char*)) 759 | { 760 | return chuck_instances[chuckID]->setCherrCallback(callback); 761 | } 762 | 763 | 764 | CHUCKDESIGNERSHARED_API bool setStdoutCallback(void (*callback)(const char*)) 765 | { 766 | return ChucK::setStdoutCallback(callback); 767 | } 768 | 769 | 770 | CHUCKDESIGNERSHARED_API bool setStderrCallback(void (*callback)(const char*)) 771 | { 772 | return ChucK::setStderrCallback(callback); 773 | } 774 | 775 | CHUCKDESIGNERSHARED_API bool setLogLevel(unsigned int level) 776 | { 777 | EM_setlog(level); 778 | return true; 779 | } 780 | 781 | CHUCKDESIGNERSHARED_API unsigned int getNextValidID(uint32_t opID) 782 | { 783 | int nextID = ++_nextValidID; 784 | 785 | op_ids_to_chuck_ids[opID] = nextID; 786 | 787 | return nextID; 788 | } 789 | 790 | CHUCKDESIGNERSHARED_API unsigned int getChucKIDForOpID(uint32_t opID) 791 | { 792 | 793 | if (op_ids_to_chuck_ids.count(opID)) { 794 | return op_ids_to_chuck_ids[opID]; 795 | } 796 | 797 | return -1; 798 | } 799 | 800 | CHUCKDESIGNERSHARED_API bool getInstanceInfo(unsigned int chuckID, int& numChannels, int& numSamples, float& sampleRate) { 801 | 802 | if (chuck_instances.count(chuckID) == 0) { 803 | numChannels = 0; 804 | numSamples = 0; 805 | } 806 | else { 807 | auto chuck_inst = chuck_instances[chuckID]; 808 | auto vm = chuck_inst->vm(); 809 | numChannels = (int)vm->m_num_dac_channels; 810 | sampleRate = std::fmax(1.f, vm->srate()); 811 | } 812 | 813 | return true; 814 | } 815 | 816 | 817 | CHUCKDESIGNERSHARED_API bool processBlock(unsigned int chuckID, const float** inBuffer, int inBufferNumChannels, int inBufferNumSamples, float* inChucKBuffer, float* outChucKBuffer, float** outBuffer, int numOutSamples, int numOutChannels) { 818 | 819 | if (chuck_instances.count(chuckID) == 0) { 820 | return false; 821 | } 822 | ChucK* chuck = chuck_instances[chuckID]; 823 | 824 | int numOutChans = chuck->vm()->m_num_dac_channels; 825 | if (numOutChans != numOutChannels) { 826 | return false; 827 | } 828 | 829 | int numSamples; 830 | int numInChannels = std::min(inBufferNumChannels, (int)chuck->vm()->m_num_adc_channels); 831 | 832 | for (int i = 0; i < numOutSamples; i += CHUCKDESIGNERCHOP_BUFFER_SIZE) { 833 | 834 | // chuck->run(inbuffer, *output->channels, output->numSamples); // this doesn't work because of interleaved samples. 835 | // Chuck returns LRLRLRLR but for touchdesigner we want LLLLRRRR. 836 | // Therefore we must use an intermediate buffer 837 | float* inPtr = inChucKBuffer; 838 | 839 | numSamples = min(CHUCKDESIGNERCHOP_BUFFER_SIZE, numOutSamples - i); 840 | 841 | if (inBuffer) { 842 | for (int samp = i; samp < std::min(inBufferNumSamples, i + CHUCKDESIGNERCHOP_BUFFER_SIZE); samp++) { 843 | for (int chan = 0; chan < numInChannels; chan++) { 844 | *(inPtr++) = inBuffer[chan][samp]; 845 | } 846 | } 847 | } 848 | float* outPtr = outChucKBuffer; 849 | 850 | chuck->run(inChucKBuffer, outChucKBuffer, numSamples); 851 | 852 | for (int samp = 0; samp < numSamples; samp++) { 853 | for (int chan = 0; chan < numOutChans; chan++) { 854 | outBuffer[chan][i + samp] = *outPtr++; 855 | } 856 | } 857 | 858 | } 859 | 860 | return true; 861 | } 862 | 863 | 864 | CHUCKDESIGNERSHARED_API void sharedFloatCallback(const char* varName, t_CKFLOAT val) { 865 | myFloatVars[varName] = val; 866 | } 867 | 868 | CHUCKDESIGNERSHARED_API void sharedIntCallback(const char* varName, t_CKINT val) { 869 | myIntVars[varName] = val; 870 | } 871 | 872 | CHUCKDESIGNERSHARED_API void sharedStringCallback(const char* varName, const char* val) { 873 | myStringVars[varName] = val; 874 | } 875 | 876 | CHUCKDESIGNERSHARED_API void sharedFloatArrayCallback(const char* varName, t_CKFLOAT vals[], t_CKUINT numItems) { 877 | auto vec = new t_CKFLOAT[numItems]; 878 | for (t_CKUINT i=0; i< numItems; i++) { 879 | vec[i] = vals[i]; 880 | } 881 | 882 | if (myFloatArrayVars.find(varName) != myFloatArrayVars.end()) { 883 | delete[] myFloatArrayVars[varName]; 884 | } 885 | 886 | myFloatArrayVars[varName] = vec; 887 | myFloatArrayVarSizes[varName] = numItems; 888 | } 889 | 890 | CHUCKDESIGNERSHARED_API void sharedIntArrayCallback(const char* varName, t_CKINT vals[], t_CKUINT numItems) { 891 | auto vec = new t_CKINT[numItems]; 892 | for (t_CKUINT i = 0; i < numItems; i++) { 893 | vec[i] = vals[i]; 894 | } 895 | 896 | if (myIntArrayVars.find(varName) != myIntArrayVars.end()) { 897 | delete[] myIntArrayVars[varName]; 898 | } 899 | 900 | myIntArrayVars[varName] = vec; 901 | myIntArrayVarSizes[varName] = numItems; 902 | } 903 | 904 | CHUCKDESIGNERSHARED_API void sharedEventCallback(const char* varName) { 905 | auto name = std::string(varName); 906 | if ((myEventToListenerIDs.find(name) == myEventToListenerIDs.end()) || 907 | (myEventToListenerIDCount.find(name) == myEventToListenerIDCount.end())) { 908 | return; 909 | } 910 | 911 | auto id_map = myEventToListenerIDs[name]; 912 | // For each listener, add to its queue. 913 | for (const auto& myPair : id_map) { 914 | auto listenerID = myPair.first; 915 | myEventToListenerIDCount[name][listenerID] = myEventToListenerIDCount[name][listenerID] + 1; 916 | } 917 | } 918 | 919 | CHUCKDESIGNERSHARED_API void sharedEventNonCallback(const char*) { 920 | 921 | } 922 | 923 | CHUCKDESIGNERSHARED_API int queryEvent(const char* varName, uint32_t opID) { 924 | 925 | auto name = std::string(varName); 926 | if ((myEventToListenerIDs.find(name) == myEventToListenerIDs.end()) || 927 | (myEventToListenerIDCount.find(name) == myEventToListenerIDCount.end())) { 928 | return 0; 929 | } 930 | 931 | int count = myEventToListenerIDCount[name][opID]; 932 | 933 | myEventToListenerIDCount[name][opID] = 0; 934 | 935 | return count; 936 | } 937 | 938 | CHUCKDESIGNERSHARED_API bool getFloat(const char* varName, t_CKFLOAT &val) { 939 | if (myFloatVars.find(varName) != myFloatVars.end()) { 940 | val = myFloatVars[varName]; 941 | return true; 942 | } 943 | return false; 944 | } 945 | 946 | CHUCKDESIGNERSHARED_API bool getInt(const char* varName, t_CKINT& val) { 947 | if (myIntVars.find(varName) != myIntVars.end()) { 948 | val = myIntVars[varName]; 949 | return true; 950 | } 951 | return false; 952 | } 953 | 954 | CHUCKDESIGNERSHARED_API bool getString(const char* varName, std::string& val) { 955 | if (myStringVars.find(varName) != myStringVars.end()) { 956 | val = myStringVars[varName]; 957 | return true; 958 | } 959 | return false; 960 | } 961 | 962 | CHUCKDESIGNERSHARED_API bool getFloatArray(const char* varName, t_CKFLOAT** vec, int& numItems) { 963 | if ( 964 | (myFloatArrayVars.find(varName) != myFloatArrayVars.end()) && 965 | (myFloatArrayVarSizes.find(varName) != myFloatArrayVarSizes.end()) 966 | ) { 967 | numItems = myFloatArrayVarSizes[varName]; 968 | *vec = myFloatArrayVars[varName]; 969 | return true; 970 | } 971 | numItems = 0; 972 | return false; 973 | } 974 | 975 | CHUCKDESIGNERSHARED_API bool getIntArray(const char* varName, t_CKINT** vec, int& numItems) { 976 | if ( 977 | (myIntArrayVars.find(varName) != myIntArrayVars.end()) && 978 | (myIntArrayVarSizes.find(varName) != myIntArrayVarSizes.end()) 979 | ) { 980 | numItems = myIntArrayVarSizes[varName]; 981 | *vec = myIntArrayVars[varName]; 982 | return true; 983 | } 984 | numItems = 0; 985 | return false; 986 | } 987 | 988 | CHUCKDESIGNERSHARED_API bool getFloatArrayValue(const char* varName, unsigned int index, t_CKFLOAT& val) { 989 | if ((myFloatArrayVars.find(varName) != myFloatArrayVars.end()) && 990 | (myFloatArrayVarSizes.find(varName) != myFloatArrayVarSizes.end())) { 991 | val = myFloatArrayVars[varName][index]; 992 | return true; 993 | } 994 | return false; 995 | } 996 | 997 | CHUCKDESIGNERSHARED_API bool getIntArrayValue(const char* varName, unsigned int index, t_CKINT& val) { 998 | if ((myIntArrayVars.find(varName) != myIntArrayVars.end()) && 999 | (myIntArrayVars.find(varName) != myIntArrayVars.end())) { 1000 | val = myIntArrayVars[varName][index]; 1001 | return true; 1002 | } 1003 | return false; 1004 | } 1005 | 1006 | CHUCKDESIGNERSHARED_API bool initChuckInstance( unsigned int chuckID, unsigned int sampleRate, unsigned int numInChannels, unsigned int numOutChannels, string globalDir ) 1007 | { 1008 | if( chuck_instances.count( chuckID ) == 0 ) 1009 | { 1010 | // if we aren't tracking a chuck vm on this ID, create a new one 1011 | ChucK * chuck = new ChucK(); 1012 | 1013 | // set params: sample rate, 2 in channels, 2 out channels, 1014 | // don't halt the vm, and use our data directory 1015 | chuck->setParam( CHUCK_PARAM_SAMPLE_RATE, (t_CKINT) sampleRate ); 1016 | chuck->setParam( CHUCK_PARAM_INPUT_CHANNELS, (t_CKINT) numInChannels); 1017 | chuck->setParam( CHUCK_PARAM_OUTPUT_CHANNELS, (t_CKINT) numOutChannels); 1018 | chuck->setParam( CHUCK_PARAM_VM_HALT, (t_CKINT) 0 ); 1019 | chuck->setParam( CHUCK_PARAM_DUMP_INSTRUCTIONS, (t_CKINT) 0 ); 1020 | // directory for compiled.code 1021 | chuck->setParam( CHUCK_PARAM_WORKING_DIRECTORY, globalDir); 1022 | // directories to search for chugins and auto-run ck files 1023 | std::list< std::string > chugin_search; 1024 | chugin_search.push_back(globalDir + "/Chugins" ); 1025 | chugin_search.push_back(globalDir + "/ChuGins" ); 1026 | chugin_search.push_back(globalDir + "/chugins" ); 1027 | 1028 | chuck->setParam( CHUCK_PARAM_USER_CHUGIN_DIRECTORIES, chugin_search ); 1029 | 1030 | // initialize and start 1031 | chuck->init(); 1032 | chuck->start(); 1033 | 1034 | chuck_instances[chuckID] = chuck; 1035 | } 1036 | return true; 1037 | } 1038 | 1039 | 1040 | CHUCKDESIGNERSHARED_API bool clearChuckInstance( unsigned int chuckID ) 1041 | { 1042 | if( chuck_instances.count( chuckID ) > 0 ) 1043 | { 1044 | // the chuck to clear 1045 | ChucK * chuck = chuck_instances[chuckID]; 1046 | 1047 | // create a msg asking to clear the VM 1048 | Chuck_Msg * msg = new Chuck_Msg; 1049 | msg->type = CK_MSG_CLEARVM; 1050 | 1051 | // null reply so that VM will delete for us when it's done 1052 | msg->reply_queue = FALSE; 1053 | 1054 | // tell the VM to clear 1055 | chuck->vm()->globals_manager()->execute_chuck_msg_with_globals( msg ); 1056 | 1057 | return true; 1058 | } 1059 | 1060 | return false; 1061 | } 1062 | 1063 | 1064 | CHUCKDESIGNERSHARED_API bool clearGlobals( unsigned int chuckID ) 1065 | { 1066 | if( chuck_instances.count( chuckID ) > 0 ) 1067 | { 1068 | // the chuck to clear 1069 | ChucK * chuck = chuck_instances[chuckID]; 1070 | 1071 | // create a msg asking to clear the globals 1072 | Chuck_Msg * msg = new Chuck_Msg; 1073 | msg->type = CK_MSG_CLEARGLOBALS; 1074 | 1075 | // null reply so that VM will delete for us when it's done 1076 | msg->reply_queue = FALSE; 1077 | 1078 | // tell the VM to clear 1079 | chuck->vm()->globals_manager()->execute_chuck_msg_with_globals( msg ); 1080 | 1081 | return true; 1082 | } 1083 | 1084 | return false; 1085 | } 1086 | 1087 | 1088 | CHUCKDESIGNERSHARED_API bool cleanupChuckInstance( unsigned int chuckID, unsigned int opId) 1089 | { 1090 | if( chuck_instances.count( chuckID ) > 0 ) 1091 | { 1092 | ChucK * chuck = chuck_instances[chuckID]; 1093 | 1094 | // don't track it anymore 1095 | chuck_instances.erase( chuckID ); 1096 | 1097 | if( data_instances.count( chuckID ) > 0 ) 1098 | { 1099 | data_instances[chuckID]->myId = -1; 1100 | data_instances.erase( chuckID ); 1101 | } 1102 | 1103 | // wait a bit 1104 | ck_usleep(30000); 1105 | 1106 | op_ids_to_chuck_ids.erase(opId); 1107 | 1108 | // todo: is this dangerous? 1109 | // todo: design consideration, do we even want to clear them? 1110 | // clear all global vars 1111 | //myFloatArrayVars.clear(); 1112 | //myFloatVars.clear(); 1113 | //myIntVars.clear(); 1114 | //myStringVars.clear(); 1115 | 1116 | //myFloatArrayVars.clear(); 1117 | //myFloatArrayVarSizes.clear(); 1118 | 1119 | //myIntArrayVars.clear(); 1120 | //myIntArrayVarSizes.clear(); 1121 | 1122 | // cleanup this chuck early 1123 | delete chuck; 1124 | 1125 | } 1126 | 1127 | return true; 1128 | } 1129 | 1130 | 1131 | CHUCKDESIGNERSHARED_API bool chuckManualAudioCallback( unsigned int chuckID, float * inBuffer, float * outBuffer, unsigned int numFrames, unsigned int inChannels, unsigned int outChannels ) 1132 | { 1133 | if( chuck_instances.count( chuckID ) > 0 ) 1134 | { 1135 | // zero out the output buffer, in case chuck isn't running 1136 | for( unsigned int n = 0; n < numFrames * outChannels; n++ ) 1137 | { 1138 | outBuffer[n] = 0; 1139 | } 1140 | 1141 | // call callback 1142 | // TODO: check inChannels, outChannels 1143 | chuck_instances[chuckID]->run( inBuffer, outBuffer, numFrames ); 1144 | 1145 | } 1146 | 1147 | return true; 1148 | } 1149 | 1150 | 1151 | // on launch, reset all ids (necessary when relaunching a lot in unity editor) 1152 | CHUCKDESIGNERSHARED_API void cleanRegisteredChucks() { 1153 | 1154 | // first, invalidate all callbacks' references to chucks 1155 | for (std::map::iterator it = data_instances.begin(); it != data_instances.end(); it++) 1156 | { 1157 | EffectData::Data* data = it->second; 1158 | data->myId = -1; 1159 | } 1160 | 1161 | // wait for callbacks to finish their current run 1162 | ck_usleep(30000); 1163 | 1164 | // next, delete chucks 1165 | for( std::map< unsigned int, ChucK * >::iterator it = 1166 | chuck_instances.begin(); it != chuck_instances.end(); it++ ) 1167 | { 1168 | ChucK * chuck = it->second; 1169 | delete chuck; 1170 | } 1171 | 1172 | // delete stored chuck pointers 1173 | chuck_instances.clear(); 1174 | // delete data instances 1175 | data_instances.clear(); 1176 | 1177 | // clear out callbacks also 1178 | setStdoutCallback( NULL ); 1179 | setStderrCallback( NULL ); 1180 | } 1181 | 1182 | 1183 | bool RegisterChuckData( EffectData::Data * data, const unsigned int id ) 1184 | { 1185 | // only store if id has been used / a chuck is already initialized 1186 | if( chuck_instances.count( id ) == 0 ) 1187 | { 1188 | return false; 1189 | } 1190 | 1191 | // store id on data; note we might be replacing a non-zero id 1192 | // in the case when unity is reusing an audio callback the next time 1193 | // the scene is entered. 1194 | data->myId = id; 1195 | 1196 | // store the data pointer, for validation later. 1197 | // the chuck associated with id should only work with *this* data. 1198 | data_instances[id] = data; 1199 | 1200 | return true; 1201 | } 1202 | 1203 | void addListenerCHOP(const char* varName, uint32_t opID) { 1204 | auto name = std::string(varName); 1205 | myEventToListenerIDs[name][opID] = true; 1206 | myEventToListenerIDCount[name][opID] = 0; 1207 | } 1208 | void removeListenerCHOP(const char* varName, uint32_t opID) { 1209 | 1210 | auto name = std::string(varName); 1211 | 1212 | if (myEventToListenerIDs.find(name) != myEventToListenerIDs.end()) { 1213 | myEventToListenerIDs[name].erase(opID); 1214 | } 1215 | 1216 | if (myEventToListenerIDCount.find(name) != myEventToListenerIDCount.end()) { 1217 | myEventToListenerIDCount[name].erase(opID); 1218 | } 1219 | } 1220 | } 1221 | -------------------------------------------------------------------------------- /src/Plugin_ChucK.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // name: Plugin_ChucK.h 3 | // desc: ChucK in Unity (Chunity) plugin; creates AudioPluginChucK 4 | // 5 | // author: Jack Atherton 6 | // date: created 4/19/17 7 | //----------------------------------------------------------------------------- 8 | 9 | #pragma once 10 | 11 | #include "chuck.h" 12 | 13 | #define CHUCKDESIGNERCHOP_BUFFER_SIZE 256 14 | 15 | #ifdef WIN32 16 | 17 | #ifdef CHUCKDESIGNERSHARED_EXPORTS 18 | #define CHUCKDESIGNERSHARED_API __declspec(dllexport) 19 | #else 20 | #define CHUCKDESIGNERSHARED_API __declspec(dllimport) 21 | #endif 22 | 23 | #else 24 | #define CHUCKDESIGNERSHARED_API 25 | #endif 26 | 27 | 28 | extern "C" { 29 | 30 | namespace ChucK_For_TouchDesigner { 31 | 32 | CHUCKDESIGNERSHARED_API bool runChuckCode(unsigned int chuckID, const char* code); 33 | CHUCKDESIGNERSHARED_API bool runChuckCodeWithReplacementDac(unsigned int chuckID, const char* code, const char* replacement_dac); 34 | CHUCKDESIGNERSHARED_API bool runChuckFile(unsigned int chuckID, const char* filename); 35 | CHUCKDESIGNERSHARED_API bool runChuckFileWithReplacementDac(unsigned int chuckID, const char* filename, const char* replacement_dac); 36 | CHUCKDESIGNERSHARED_API bool runChuckFileWithArgs(unsigned int chuckID, const char* filename, const char* args); 37 | CHUCKDESIGNERSHARED_API bool runChuckFileWithArgsWithReplacementDac(unsigned int chuckID, const char* filename, const char* args, const char* replacement_dac); 38 | 39 | CHUCKDESIGNERSHARED_API bool setChuckInt(unsigned int chuckID, const char* name, t_CKINT val); 40 | CHUCKDESIGNERSHARED_API bool getChuckInt(unsigned int chuckID, const char* name, void (*callback)(t_CKINT)); 41 | CHUCKDESIGNERSHARED_API bool getNamedChuckInt(unsigned int chuckID, const char* name, void (*callback)(const char*, t_CKINT)); 42 | CHUCKDESIGNERSHARED_API bool getChuckIntWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT, t_CKINT)); 43 | 44 | CHUCKDESIGNERSHARED_API bool setChuckFloat(unsigned int chuckID, const char* name, t_CKFLOAT val); 45 | CHUCKDESIGNERSHARED_API bool getChuckFloat(unsigned int chuckID, const char* name, void (*callback)(t_CKFLOAT)); 46 | CHUCKDESIGNERSHARED_API bool getNamedChuckFloat(unsigned int chuckID, const char* name, void (*callback)(const char*, t_CKFLOAT)); 47 | CHUCKDESIGNERSHARED_API bool getChuckFloatWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT, t_CKFLOAT)); 48 | 49 | CHUCKDESIGNERSHARED_API bool setChuckString(unsigned int chuckID, const char* name, const char* val); 50 | CHUCKDESIGNERSHARED_API bool getChuckString(unsigned int chuckID, const char* name, void (*callback)(const char*)); 51 | CHUCKDESIGNERSHARED_API bool getNamedChuckString(unsigned int chuckID, const char* name, void (*callback)(const char*, const char*)); 52 | CHUCKDESIGNERSHARED_API bool getChuckStringWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT, const char*)); 53 | 54 | CHUCKDESIGNERSHARED_API bool signalChuckEvent(unsigned int chuckID, const char* name); 55 | CHUCKDESIGNERSHARED_API bool broadcastChuckEvent(unsigned int chuckID, const char* name); 56 | CHUCKDESIGNERSHARED_API bool listenForChuckEventOnce(unsigned int chuckID, const char* name, void (*callback)(void)); 57 | CHUCKDESIGNERSHARED_API bool listenForNamedChuckEventOnce(unsigned int chuckID, const char* name, void (*callback)(const char*)); 58 | CHUCKDESIGNERSHARED_API bool listenForChuckEventOnceWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT)); 59 | CHUCKDESIGNERSHARED_API bool startListeningForChuckEvent(unsigned int chuckID, const char* name, void (*callback)(void)); 60 | CHUCKDESIGNERSHARED_API bool startListeningForNamedChuckEvent(unsigned int chuckID, const char* name, void (*callback)(const char*)); 61 | CHUCKDESIGNERSHARED_API bool startListeningForChuckEventWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT)); 62 | CHUCKDESIGNERSHARED_API bool stopListeningForChuckEvent(unsigned int chuckID, const char* name, void (*callback)(void)); 63 | CHUCKDESIGNERSHARED_API bool stopListeningForNamedChuckEvent(unsigned int chuckID, const char* name, void (*callback)(const char*)); 64 | CHUCKDESIGNERSHARED_API bool stopListeningForChuckEventWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT)); 65 | 66 | CHUCKDESIGNERSHARED_API bool getGlobalUGenSamples(unsigned int chuckID, const char* name, SAMPLE* buffer, int numSamples); 67 | 68 | // int array methods 69 | CHUCKDESIGNERSHARED_API bool setGlobalIntArray(unsigned int chuckID, const char* name, t_CKINT arrayValues[], unsigned int numValues); 70 | CHUCKDESIGNERSHARED_API bool getGlobalIntArray(unsigned int chuckID, const char* name, void (*callback)(t_CKINT[], t_CKUINT)); 71 | CHUCKDESIGNERSHARED_API bool getNamedGlobalIntArray(unsigned int chuckID, const char* name, void (*callback)(const char*, t_CKINT[], t_CKUINT)); 72 | CHUCKDESIGNERSHARED_API bool getGlobalIntArrayWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT, t_CKINT[], t_CKUINT)); 73 | CHUCKDESIGNERSHARED_API bool setGlobalIntArrayValue(unsigned int chuckID, const char* name, unsigned int index, t_CKINT value); 74 | CHUCKDESIGNERSHARED_API bool getGlobalIntArrayValue(unsigned int chuckID, const char* name, unsigned int index, void (*callback)(t_CKINT)); 75 | CHUCKDESIGNERSHARED_API bool getNamedGlobalIntArrayValue(unsigned int chuckID, const char* name, unsigned int index, void (*callback)(const char*, t_CKINT)); 76 | CHUCKDESIGNERSHARED_API bool getGlobalIntArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, unsigned int index, void (*callback)(t_CKINT, t_CKINT)); 77 | CHUCKDESIGNERSHARED_API bool setGlobalAssociativeIntArrayValue(unsigned int chuckID, const char* name, char* key, t_CKINT value); 78 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeIntArrayValue(unsigned int chuckID, const char* name, char* key, void (*callback)(t_CKINT)); 79 | CHUCKDESIGNERSHARED_API bool getNamedGlobalAssociativeIntArrayValue(unsigned int chuckID, const char* name, char* key, void (*callback)(const char*, t_CKINT)); 80 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeIntArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, char* key, void (*callback)(t_CKINT, t_CKINT)); 81 | CHUCKDESIGNERSHARED_API bool setGlobalIntArray_AT(unsigned int chuckID, const char* name, t_CKINT arrayValues[], unsigned int numValues); // internal/audio-thread-friendly global array setter 82 | CHUCKDESIGNERSHARED_API bool setGlobalIntArrayValue_AT(unsigned int chuckID, const char* name, unsigned int index, t_CKINT value); // internal/audio-thread-friendly 83 | // TODO: set entire dict, add to dict in batch; get entire dict 84 | 85 | // float array methods 86 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArray(unsigned int chuckID, const char* name, t_CKFLOAT arrayValues[], unsigned int numValues); 87 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArray(unsigned int chuckID, const char* name, void (*callback)(t_CKFLOAT[], t_CKUINT)); 88 | CHUCKDESIGNERSHARED_API bool getNamedGlobalFloatArray(unsigned int chuckID, const char* name, void (*callback)(const char*, t_CKFLOAT[], t_CKUINT)); 89 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArrayWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, void (*callback)(t_CKINT, t_CKFLOAT[], t_CKUINT)); 90 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArrayValue(unsigned int chuckID, const char* name, unsigned int index, t_CKFLOAT value); 91 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArrayValue(unsigned int chuckID, const char* name, unsigned int index, void (*callback)(t_CKFLOAT)); 92 | CHUCKDESIGNERSHARED_API bool getNamedGlobalFloatArrayValue(unsigned int chuckID, const char* name, unsigned int index, void (*callback)(const char*, t_CKFLOAT)); 93 | CHUCKDESIGNERSHARED_API bool getGlobalFloatArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, unsigned int index, void (*callback)(t_CKINT, t_CKFLOAT)); 94 | CHUCKDESIGNERSHARED_API bool setGlobalAssociativeFloatArrayValue(unsigned int chuckID, const char* name, char* key, t_CKFLOAT value); 95 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeFloatArrayValue(unsigned int chuckID, const char* name, char* key, void (*callback)(t_CKFLOAT)); 96 | CHUCKDESIGNERSHARED_API bool getNamedGlobalAssociativeFloatArrayValue(unsigned int chuckID, const char* name, char* key, void (*callback)(const char*, t_CKFLOAT)); 97 | CHUCKDESIGNERSHARED_API bool getGlobalAssociativeFloatArrayValueWithID(unsigned int chuckID, t_CKINT callbackID, const char* name, char* key, void (*callback)(t_CKINT, t_CKFLOAT)); 98 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArray_AT(unsigned int chuckID, const char* name, t_CKFLOAT arrayValues[], unsigned int numValues); // internal/audio-thread-friendly global array setter 99 | CHUCKDESIGNERSHARED_API bool setGlobalFloatArrayValue_AT(unsigned int chuckID, const char* name, unsigned int index, t_CKFLOAT value); // internal/audio-thread-friendly 100 | 101 | CHUCKDESIGNERSHARED_API bool initChuckInstance(unsigned int chuckID, unsigned int sampleRate, unsigned int numInChannels, unsigned int numOutChannels, string globalDir); 102 | CHUCKDESIGNERSHARED_API bool clearChuckInstance(unsigned int chuckID); 103 | CHUCKDESIGNERSHARED_API bool clearGlobals(unsigned int chuckID); 104 | CHUCKDESIGNERSHARED_API bool cleanupChuckInstance(unsigned int chuckID, unsigned int opId); 105 | CHUCKDESIGNERSHARED_API bool chuckManualAudioCallback(unsigned int chuckID, float* inBuffer, float* outBuffer, unsigned int numFrames, unsigned int inChannels, unsigned int outChannels); 106 | CHUCKDESIGNERSHARED_API void cleanRegisteredChucks(); 107 | 108 | CHUCKDESIGNERSHARED_API bool setChoutCallback(unsigned int chuckID, void (*callback)(const char*)); 109 | CHUCKDESIGNERSHARED_API bool setCherrCallback(unsigned int chuckID, void (*callback)(const char*)); 110 | CHUCKDESIGNERSHARED_API bool setStdoutCallback(void (*callback)(const char*)); 111 | CHUCKDESIGNERSHARED_API bool setStderrCallback(void (*callback)(const char*)); 112 | 113 | CHUCKDESIGNERSHARED_API bool setLogLevel(unsigned int level); 114 | 115 | CHUCKDESIGNERSHARED_API unsigned int getNextValidID(uint32_t opID); 116 | 117 | CHUCKDESIGNERSHARED_API unsigned int getChucKIDForOpID(uint32_t opID); 118 | 119 | CHUCKDESIGNERSHARED_API bool getInstanceInfo(unsigned int chuckID, int& numChannels, int& numSamples, float& sampleRate); 120 | 121 | CHUCKDESIGNERSHARED_API bool processBlock(unsigned int chuckID, const float** inBuffer, int inBufferNumChannels, int inBufferNumSamples, float* inChucKBuffer, float* outChucKBuffer, float** outBuffer, int numOutSamples, int numOutChannels); 122 | 123 | CHUCKDESIGNERSHARED_API bool getFloat(const char* varName, t_CKFLOAT& val); 124 | CHUCKDESIGNERSHARED_API bool getInt(const char* varStr, t_CKINT& val); 125 | CHUCKDESIGNERSHARED_API bool getString(const char* varStr, std::string& val); 126 | CHUCKDESIGNERSHARED_API bool getFloatArray(const char* varName, t_CKFLOAT** vec, int& numItems); 127 | CHUCKDESIGNERSHARED_API bool getIntArray(const char* varName, t_CKINT** vec, int& numItems); 128 | 129 | CHUCKDESIGNERSHARED_API bool getFloatArrayValue(const char* varName, unsigned int index, t_CKFLOAT& val); 130 | CHUCKDESIGNERSHARED_API bool getIntArrayValue(const char* varName, unsigned int index, t_CKINT& val); 131 | //CHUCKDESIGNERSHARED_API bool getAssociativeFloatArrayValue(const char* varName, char* key, unsigned int index, t_CKFLOAT& val); 132 | //CHUCKDESIGNERSHARED_API bool getAssociativeIntArrayValue(const char* varName, char* key, unsigned int index, t_CKINT& val); 133 | 134 | CHUCKDESIGNERSHARED_API void sharedFloatCallback(const char* varName, t_CKFLOAT val); 135 | CHUCKDESIGNERSHARED_API void sharedIntCallback(const char* varName, t_CKINT val); 136 | CHUCKDESIGNERSHARED_API void sharedStringCallback(const char* varName, const char* val); 137 | 138 | CHUCKDESIGNERSHARED_API void sharedFloatArrayCallback(const char* varName, t_CKFLOAT vals[], t_CKUINT numItems); 139 | CHUCKDESIGNERSHARED_API void sharedIntArrayCallback(const char* varName, t_CKINT vals[], t_CKUINT numItems); 140 | 141 | //CHUCKDESIGNERSHARED_API void sharedAssociativeFloatArrayCallback(const char* varName, t_CKFLOAT val); 142 | //CHUCKDESIGNERSHARED_API void sharedAssociativeIntArrayCallback(const char* varName, t_CKUINT val); 143 | 144 | CHUCKDESIGNERSHARED_API void sharedEventCallback(const char* varName); 145 | CHUCKDESIGNERSHARED_API void sharedEventNonCallback(const char* varName); 146 | 147 | CHUCKDESIGNERSHARED_API void addListenerCHOP(const char* varName, uint32_t opID); 148 | CHUCKDESIGNERSHARED_API void removeListenerCHOP(const char* varName, uint32_t opID); 149 | CHUCKDESIGNERSHARED_API int queryEvent(const char* varName, uint32_t opID); 150 | } // namespace ChucK_For_TouchDesigner 151 | }; // extern "C" 152 | -------------------------------------------------------------------------------- /thirdparty/TouchDesigner/CHOP_CPlusPlusBase.h: -------------------------------------------------------------------------------- 1 | /* Shared Use License: This file is owned by Derivative Inc. (Derivative) 2 | * and can only be used, and/or modified for use, in conjunction with 3 | * Derivative's TouchDesigner software, and only if you are a licensee who has 4 | * accepted Derivative's TouchDesigner license or assignment agreement 5 | * (which also govern the use of this file). You may share or redistribute 6 | * a modified version of this file provided the following conditions are met: 7 | * 8 | * 1. The shared file or redistribution must retain the information set out 9 | * above and this list of conditions. 10 | * 2. Derivative's name (Derivative Inc.) or its trademarks may not be used 11 | * to endorse or promote products derived from this file without specific 12 | * prior written permission from Derivative. 13 | */ 14 | 15 | /* 16 | * Produced by: 17 | * 18 | * Derivative Inc 19 | * 401 Richmond Street West, Unit 386 20 | * Toronto, Ontario 21 | * Canada M5V 3A8 22 | * 416-591-3555 23 | * 24 | * NAME: CHOP_CPlusPlusBase.h 25 | * 26 | * 27 | * Do not edit this file directly! 28 | * Make a subclass of CHOP_CPlusPlusBase instead, and add your own 29 | * data/functions. 30 | 31 | * Derivative Developers:: Make sure the virtual function order 32 | * stays the same, otherwise changes won't be backwards compatible 33 | */ 34 | 35 | #ifndef __CHOP_CPlusPlusBase__ 36 | #define __CHOP_CPlusPlusBase__ 37 | 38 | #include "CPlusPlus_Common.h" 39 | 40 | namespace TD 41 | { 42 | #pragma pack(push, 8) 43 | 44 | class CHOP_CPlusPlusBase; 45 | 46 | // Define for the current API version that this sample code is made for. 47 | // To upgrade to a newer version, replace the files 48 | // CHOP_CPlusPlusBase.h 49 | // CPlusPlus_Common.h 50 | // from the samples folder in a newer TouchDesigner installation. 51 | // You may need to upgrade your plugin code in that case, to match 52 | // the new API requirements 53 | const int CHOPCPlusPlusAPIVersion = 9; 54 | 55 | class CHOP_PluginInfo 56 | { 57 | public: 58 | // Must be set to CHOPCPlusPlusAPIVersion in FillCHOPPluginInfo 59 | int32_t apiVersion = 0; 60 | 61 | int32_t reserved[100]; 62 | 63 | // Information used to describe this plugin as a custom OP. 64 | OP_CustomOPInfo customOPInfo; 65 | 66 | int32_t reserved2[20]; 67 | }; 68 | 69 | class CHOP_GeneralInfo 70 | { 71 | public: 72 | // Set this to true if you want the CHOP to cook every frame, even 73 | // if none of it's inputs/parameters are changing. 74 | // This is generally useful for cases where the node is outputting to 75 | // something external to TouchDesigner, such as a network socket or device. 76 | // It ensures the node cooks every if nothing inside the network is using/viewing 77 | // the output of this node. 78 | // Important: 79 | // If the node may not be viewed/used by other nodes in the file, 80 | // such as a TCP network output node that isn't viewed in perform mode, 81 | // you should set cookOnStart = true in OP_CustomOPInfo. 82 | // That will ensure cooking is kick-started for this node. 83 | // Note that this fix only works for Custom Operators, not 84 | // cases where the .dll is loaded into CPlusPlus CHOP. 85 | // DEFAULT: false 86 | bool cookEveryFrame; 87 | 88 | // Set this to true if you want the CHOP to cook every frame, but only 89 | // if someone asks for it to cook. So if nobody is using the output from 90 | // the CHOP, it won't cook. This is different from 'cookEveryFrame' 91 | // since that will cause it to cook every frame no matter what. 92 | // DEFAULT: false 93 | bool cookEveryFrameIfAsked; 94 | 95 | // Set this to true if you will be outputting a timeslice 96 | // Outputting a timeslice means the number of samples in the CHOP will 97 | // be determined by the number of frames that have elapsed since the last 98 | // time TouchDesigner cooked (it will be more than one in cases where it's 99 | // running slower than the target cook rate), the playbar framerate and 100 | // the sample rate of the CHOP. 101 | // For example if you are outputting the CHOP 120hz sample rate, 102 | // TouchDesigner is running at 60 hz cookrate, and you missed a frame last cook 103 | // then on this cook the number of sampels of the output of this CHOP will 104 | // be 4 samples. I.e (120 / 60) * number of playbar frames to output. 105 | // If this isn't set then you specify the number of sample in the CHOP using 106 | // the getOutputInfo() function 107 | // DEFAULT: false 108 | bool timeslice; 109 | 110 | // If you are returning 'false' from getOutputInfo, this index will 111 | // specify the CHOP input whos attribues you will match 112 | // (channel names, length, sample rate etc.) 113 | // DEFAULT : 0 114 | int32_t inputMatchIndex; 115 | 116 | int32_t reserved[20]; 117 | }; 118 | 119 | class CHOP_OutputInfo 120 | { 121 | public: 122 | // The number of channels you want to output 123 | int32_t numChannels; 124 | 125 | // If you arn't outputting a timeslice, specify the number of samples here 126 | int32_t numSamples; 127 | 128 | // if you arn't outputting a timeslice, specify the start index 129 | // of the channels here. This is the 'Start' you see when you 130 | // middle click on a CHOP 131 | uint32_t startIndex; 132 | 133 | // Specify the sample rate of the channel data 134 | // DEFAULT : whatever the timeline FPS is ($FPS) 135 | float sampleRate; 136 | 137 | void* reserved1; 138 | int32_t reserved[20]; 139 | }; 140 | 141 | class CHOP_Output 142 | { 143 | public: 144 | CHOP_Output(int32_t nc, int32_t l, float s, uint32_t st, 145 | float **cs, const char** ns): 146 | numChannels(nc), 147 | numSamples(l), 148 | sampleRate(s), 149 | startIndex(st), 150 | channels(cs), 151 | names(ns) 152 | { 153 | } 154 | 155 | // Info about what you are expected to output 156 | const int32_t numChannels; 157 | const int32_t numSamples; 158 | const float sampleRate; 159 | const uint32_t startIndex; 160 | 161 | // This is an array of const char* that tells you the channel names 162 | // of the channels you are providing values for. It's 'numChannels' long. 163 | // E.g names[3] is the name of the 4th channel 164 | const char** const names; 165 | 166 | // This is an array of float arrays that is already allocated for you. 167 | // Fill it with the data you want outputted for this CHOP. 168 | // The length of the array is 'numChannels', 169 | // While the length of each of the array entries is 'numSamples'. 170 | // For example channels[1][10] will point to the 11th sample in the 2nd 171 | // channel 172 | float** const channels; 173 | 174 | int32_t reserved[20]; 175 | }; 176 | 177 | /***** FUNCTION CALL ORDER DURING INITIALIZATION ******/ 178 | /* 179 | When the TOP loads the dll the functions will be called in this order 180 | 181 | setupParameters(OP_ParameterManager* m); 182 | 183 | */ 184 | 185 | /***** FUNCTION CALL ORDER DURING A COOK ******/ 186 | /* 187 | 188 | When the CHOP cooks the functions will be called in this order 189 | 190 | getGeneralInfo() 191 | getOutputInfo() 192 | if getOutputInfo() returns true 193 | { 194 | getChannelName() once for each channel needed 195 | } 196 | execute() 197 | getNumInfoCHOPChans() 198 | for the number of chans returned getNumInfoCHOPChans() 199 | { 200 | getInfoCHOPChan() 201 | } 202 | getInfoDATSize() 203 | for the number of rows/cols returned by getInfoDATSize() 204 | { 205 | getInfoDATEntries() 206 | } 207 | getInfoPopupString() 208 | getWarningString() 209 | getErrorString() 210 | */ 211 | 212 | /*** DO NOT EDIT THIS CLASS, MAKE A SUBCLASS OF IT INSTEAD ***/ 213 | class CHOP_CPlusPlusBase 214 | { 215 | protected: 216 | CHOP_CPlusPlusBase() 217 | { 218 | } 219 | 220 | virtual ~CHOP_CPlusPlusBase() 221 | { 222 | } 223 | 224 | public: 225 | 226 | // BEGIN PUBLIC INTERFACE 227 | 228 | // Some general settings can be assigned here (if you override it) 229 | virtual void 230 | getGeneralInfo(CHOP_GeneralInfo*, const OP_Inputs *inputs, void* reserved1) 231 | { 232 | } 233 | 234 | // This function is called so the class can tell the CHOP how many 235 | // channels it wants to output, how many samples etc. 236 | // Return true if you specify the output here. 237 | // Return false if you want the output to be set by matching 238 | // the channel names, numSamples, sample rate etc. of one of your inputs 239 | // The input that is used is chosen by setting the 'inputMatchIndex' 240 | // memeber in CHOP_OutputInfo 241 | // The CHOP_OutputInfo class is pre-filled with what the CHOP would 242 | // output if you return false, so you can just tweak a few settings 243 | // and return true if you want 244 | virtual bool 245 | getOutputInfo(CHOP_OutputInfo*, const OP_Inputs *inputs, void *reserved1) 246 | { 247 | return false; 248 | } 249 | 250 | // This function will be called after getOutputInfo() asking for 251 | // the channel names. It will get called once for each channel name 252 | // you need to specify. If you returned 'false' from getOutputInfo() 253 | // it won't be called. 254 | virtual void 255 | getChannelName(int32_t index, OP_String *name, 256 | const OP_Inputs *inputs, void* reserved1) 257 | { 258 | name->setString("chan1"); 259 | } 260 | 261 | 262 | // In this function you do whatever you want to fill the output channels 263 | // which are already allocated for you in 'outputs' 264 | virtual void execute(CHOP_Output* outputs, 265 | const OP_Inputs* inputs, 266 | void* reserved1) = 0; 267 | 268 | 269 | // Override these methods if you want to output values to the Info CHOP/DAT 270 | // returning 0 means you dont plan to output any Info CHOP channels 271 | virtual int32_t 272 | getNumInfoCHOPChans(void *reserved1) 273 | { 274 | return 0; 275 | } 276 | 277 | // Specify the name and value for Info CHOP channel 'index', 278 | // by assigning something to 'name' and 'value' members of the 279 | // OP_InfoCHOPChan class pointer that is passed in. 280 | virtual void 281 | getInfoCHOPChan(int32_t index, OP_InfoCHOPChan* chan, void* reserved1) 282 | { 283 | } 284 | 285 | 286 | // Return false if you arn't returning data for an Info DAT 287 | // Return true if you are. 288 | // Set the members of the CHOP_InfoDATSize class to specify 289 | // the dimensions of the Info DAT 290 | virtual bool 291 | getInfoDATSize(OP_InfoDATSize* infoSize, void *reserved1) 292 | { 293 | return false; 294 | } 295 | 296 | // You are asked to assign values to the Info DAT 1 row or column at a time 297 | // The 'byColumn' variable in 'getInfoDATSize' is how you specify 298 | // if it is by column or by row. 299 | // 'index' is the row/column index 300 | // 'nEntries' is the number of entries in the row/column 301 | // Strings should be UTF-8 encoded. 302 | virtual void 303 | getInfoDATEntries(int32_t index, int32_t nEntries, 304 | OP_InfoDATEntries* entries, 305 | void *reserved1) 306 | { 307 | } 308 | 309 | // You can use this function to put the node into a warning state 310 | // by calling setSting() on 'warning' with a non empty string. 311 | // Leave 'warning' unchanged to not go into warning state. 312 | virtual void 313 | getWarningString(OP_String *warning, void *reserved1) 314 | { 315 | } 316 | 317 | // You can use this function to put the node into a error state 318 | // by calling setSting() on 'error' with a non empty string. 319 | // Leave 'error' unchanged to not go into error state. 320 | virtual void 321 | getErrorString(OP_String *error, void *reserved1) 322 | { 323 | } 324 | 325 | // Use this function to return some text that will show up in the 326 | // info popup (when you middle click on a node) 327 | // call setString() on info and give it some info if desired. 328 | virtual void 329 | getInfoPopupString(OP_String *info, void *reserved1) 330 | { 331 | } 332 | 333 | 334 | // Override these methods if you want to define specfic parameters 335 | virtual void 336 | setupParameters(OP_ParameterManager* manager, void* reserved1) 337 | { 338 | } 339 | 340 | 341 | // This is called whenever a pulse parameter is pressed 342 | virtual void 343 | pulsePressed(const char* name, void* reserved1) 344 | { 345 | } 346 | 347 | // END PUBLIC INTERFACE 348 | 349 | 350 | private: 351 | 352 | // Reserved for future features 353 | virtual int32_t reservedFunc6() { return 0; } 354 | virtual int32_t reservedFunc7() { return 0; } 355 | virtual int32_t reservedFunc8() { return 0; } 356 | virtual int32_t reservedFunc9() { return 0; } 357 | virtual int32_t reservedFunc10() { return 0; } 358 | virtual int32_t reservedFunc11() { return 0; } 359 | virtual int32_t reservedFunc12() { return 0; } 360 | virtual int32_t reservedFunc13() { return 0; } 361 | virtual int32_t reservedFunc14() { return 0; } 362 | virtual int32_t reservedFunc15() { return 0; } 363 | virtual int32_t reservedFunc16() { return 0; } 364 | virtual int32_t reservedFunc17() { return 0; } 365 | virtual int32_t reservedFunc18() { return 0; } 366 | virtual int32_t reservedFunc19() { return 0; } 367 | virtual int32_t reservedFunc20() { return 0; } 368 | 369 | int32_t reserved[400]; 370 | 371 | }; 372 | 373 | #pragma pack(pop) 374 | 375 | static_assert(offsetof(CHOP_PluginInfo, apiVersion) == 0, "Incorrect Alignment"); 376 | static_assert(offsetof(CHOP_PluginInfo, customOPInfo) == 408, "Incorrect Alignment"); 377 | static_assert(sizeof(CHOP_PluginInfo) == 944, "Incorrect Size"); 378 | 379 | static_assert(offsetof(CHOP_GeneralInfo, cookEveryFrame) == 0, "Incorrect Alignment"); 380 | static_assert(offsetof(CHOP_GeneralInfo, cookEveryFrameIfAsked) == 1, "Incorrect Alignment"); 381 | static_assert(offsetof(CHOP_GeneralInfo, timeslice) == 2, "Incorrect Alignment"); 382 | static_assert(offsetof(CHOP_GeneralInfo, inputMatchIndex) == 4, "Incorrect Alignment"); 383 | static_assert(sizeof(CHOP_GeneralInfo) == 88, "Incorrect Size"); 384 | 385 | static_assert(offsetof(CHOP_OutputInfo, numChannels) == 0, "Incorrect Alignment"); 386 | static_assert(offsetof(CHOP_OutputInfo, numSamples) == 4, "Incorrect Alignment"); 387 | static_assert(offsetof(CHOP_OutputInfo, startIndex) == 8, "Incorrect Alignment"); 388 | static_assert(offsetof(CHOP_OutputInfo, sampleRate) == 12, "Incorrect Alignment"); 389 | static_assert(offsetof(CHOP_OutputInfo, reserved1) == 16, "Incorrect Alignment"); 390 | static_assert(sizeof(CHOP_OutputInfo) == 104, "Incorrect Size"); 391 | 392 | static_assert(offsetof(CHOP_Output, numChannels) == 0, "Incorrect Alignment"); 393 | static_assert(offsetof(CHOP_Output, numSamples) == 4, "Incorrect Alignment"); 394 | static_assert(offsetof(CHOP_Output, sampleRate) == 8, "Incorrect Alignment"); 395 | static_assert(offsetof(CHOP_Output, startIndex) == 12, "Incorrect Alignment"); 396 | static_assert(offsetof(CHOP_Output, names) == 16, "Incorrect Alignment"); 397 | static_assert(offsetof(CHOP_Output, channels) == 24, "Incorrect Alignment"); 398 | static_assert(sizeof(CHOP_Output) == 112, "Incorrect Size"); 399 | #endif 400 | }; // namespace TD 401 | -------------------------------------------------------------------------------- /thirdparty/TouchDesigner/GL_Extensions.h: -------------------------------------------------------------------------------- 1 | // Stub file for simpler CHOP usage than an OpenGLTOP 2 | 3 | #include 4 | --------------------------------------------------------------------------------