diff --git a/README.md b/README.md index 3619088..2a83173 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# AtemOSC v2.5.7 +# AtemOSC v3.0.0 ## Features -This is a macOS application, providing an interface to control an ATEM video switcher via OSC. +This is a macOS application, providing an interface to control an ATEM video switcher via OSC. -![atemOSC](https://github.com/danielbuechele/atemOSC/raw/master/atemOSC.jpg) +atemOSC Screenshot -The current version is built for Mac OS 10.15.1 (as of version 2.5.7). A compiled and runnable version of the atemOSC is included which has been built against Blackmagic SDK 8.1 (as of version 2.5.7). +The current version is built for Mac OS 10.15.1 (since version 2.5.7). A compiled and runnable version of the atemOSC is included which has been built against Blackmagic SDK 8.1 (since version 2.5.7). ## Download the App @@ -32,36 +32,36 @@ AtemOSC is a proxy, listening for commands following the [OSC protocol](http://o ## OSC API - - A full overview of the actual OSC-addresses available for your switcher can be obtained from the help-menu inside the application. - - Unless otherwise specified, send the value 1 along with the OSC address below. Sending any other value may result in the command not being processed. - +A full overview of the actual OSC-addresses available for your switcher can be obtained from the help menu inside the application. ### Program and Preview Selection - - **Black** `/atem/program/0` + - **Black** `/atem/program 0` - - **Cam 1** `/atem/program/1` - - **Cam 2** `/atem/program/2` - - **Cam 3** `/atem/program/3` - - **Cam 4** `/atem/program/4` - - **Cam 5** `/atem/program/5` - - **Cam 6** `/atem/program/6` + - **Cam 1** `/atem/program 1` + - **Cam 2** `/atem/program 2` + - **Cam 3** `/atem/program 3` + - **Cam 4** `/atem/program 4` + - **Cam 5** `/atem/program 5` + - **Cam 6** `/atem/program 6` - and so on... - - **Color Bars** `/atem/program/1000` - - **Color 1** `/atem/program/2001` - - **Color 2** `/atem/program/2002` - - **Media 1** `/atem/program/3010` - - **Media 2** `/atem/program/3020` - - **Key 1 Mask** `/atem/program/4010` - - **DSK 1 Mask**: `/atem/program/5010` - - **DSK 2 Mask**: `/atem/program/5020` - - **Clean Feed 1** `/atem/program/7001` - - **Clean Feed 2** `/atem/program/7002` - - **Auxiliary 1** `/atem/program/8001` + - **Color Bars** `/atem/program 1000` + - **Color 1** `/atem/program 2001` + - **Color 2** `/atem/program 2002` + - **Media 1** `/atem/program 3010` + - **Media 2** `/atem/program 3020` + - **Key 1 Mask** `/atem/program 4010` + - **DSK 1 Mask**: `/atem/program 5010` + - **DSK 2 Mask**: `/atem/program 5020` + - **Clean Feed 1** `/atem/program 7001` + - **Clean Feed 2** `/atem/program 7002` + - **Auxiliary 1** `/atem/program 8001` - and so on... -For preview selection `/atem/preview/$i` can be used. +For preview selection `/atem/preview $i` can be used. + +Also supports sending the input in the address instead of as a value (e.g. `/atem/program/5`) Feedback: Enabled for all values @@ -74,41 +74,42 @@ Note: You can fetch the names of each input by sending the `/atem/send-status` c - **T-bar** `/atem/transition/bar <0-1>` - **Cut** `/atem/transition/cut` - **Auto** `/atem/transition/auto` - - **Fade to Black** `/atem/transition/ftb` - - **Preview Transition** `/atem/transition/preview <0|1>` + - **Fade to Black Toggle** `/atem/transition/ftb` + - **Preview Transition** `/atem/transition/preview ` To set the transition type of the Auto transition: - - **Mix** `/atem/transition/set-type/mix` - - **Dip** `/atem/transition/set-type/dip` - - **Wipe** `/atem/transition/set-type/wipe` - - **Stinger** `/atem/transition/set-type/sting` - - **DVE** `/atem/transition/set-type/dve` - + - **Mix** `/atem/transition/type mix` + - **Dip** `/atem/transition/type dip` + - **Wipe** `/atem/transition/type wipe` + - **Stinger** `/atem/transition/type sting` + - **DVE** `/atem/transition/type dve` + - Also supports sending the type in the address instead of as a string value (e.g. `/atem/transition/type/dve`) + Feedback: None - + ### Auxiliary Source Selection - **Set Aux $i source to $x** `/atem/aux/$i $x` - - Where `$x` is an integer value that is a valid program source, and can be 1-6 depending on the capability of your ATEM switcher. Check the Help Menu for the correct values. - - e.g. `/atem/aux/1 1` to set Aux 1 output to source 1 (Camera 1) + - Where `$x` is an integer value that is a valid program source, and can be 1-6 depending on the capability of your ATEM switcher. Check the Help Menu for the correct values. + - e.g. `/atem/aux/1 1` to set Aux 1 output to source 1 (Camera 1) Feedback: None ### Upstream Keyers - - **Set Tie BKGD** `/atem/usk/0/tie <0|1>` + - **Set Tie BKGD** `/atem/usk/0/tie ` - Send a value of 1 to enable tie, and 0 to disable - **Toggle Tie BKGD** `/atem/usk/0/tie/toggle` - - **Set On-Air Upstream Keyer $i** `/atem/usk/$i/on-air <0|1>` - - Send a value of 1 to cut the USK on-air, and a value of 0 to cut it off-air + - **Set On-Air Upstream Keyer $i** `/atem/usk/$i/on-air ` + - Send a value of true to cut the USK on-air, and a value of false to cut it off-air - **Cut Toggle On-Air Upstream Keyer $i** `/atem/usk/$i/on-air/toggle` - - **Set Tie Upstream Keyer $i** `/atem/usk/$i/tie <0|1>` - - Send a value of 1 to enable tie, and 0 to disable + - **Set Tie Upstream Keyer $i** `/atem/usk/$i/tie ` + - Send a value of true to enable tie, and false to disable - **Toggle Tie Upstream Keyer $i** `/atem/usk/$i/tie/toggle` - - **Set Upstream Keyer $i for Next Scene** `/atem/usk/$i/tie/set-next <0|1>` - - Send a value of 1 to show the USK after next transition, and 0 if you don’t want to show the USK after next transition - - e.g. If USK 1 is on air, `/atem/usk/1/tie/set-next 1` will untie USK 1 so that it remains on, while `/atem/usk/1/tie/set-next 0` will tie USK 1 so that it will go off air after the next transition. + - **Set Upstream Keyer $i for Next Scene** `/atem/usk/$i/tie/set-next ` + - Send a boolean value of true to show the USK after next transition, and false if you don’t want to show the USK after next transition + - e.g. If USK 1 is on air, `/atem/usk/1/tie/set-next true` will untie USK 1 so that it remains on, while `/atem/usk/1/tie/set-next false` will tie USK 1 so that it will go off air after the next transition. - **Set Key type for Upstream Keyer $i** `/atem/usk/$i/type ` - Also supports sending the type in the address instead of as a string value (e.g. `/atem/usk/$i/type/luma`) @@ -137,23 +138,43 @@ Feedback: None - Float value should be between 0.0 (for 0%) and 1.0 (for 100%) - **Set "Narrow Chroma Key Range" Parameter for Upstream Keyer $i** `/atem/usk/$i/chroma/narrow ` +#### USK DVE Parameters + - **Set DVE Border Enabled for Upstream Keyer $i** `/atem/usk/$i/dve/enabled ` + - Other values supported are: `border-width-outer`, `border-width-inner`, `border-softness-outer`, `border-softness-inner`, `border-opacity`, `border-hue`, `border-saturation`, and `border-luma` + Where `$i` can be 1, 2, 3, or 4 depending on the capability of your ATEM switcher Feedback: Enabled for '/atem/usk/$i/on-air', '/atem/usk/$i/tie', '/atem/usk/$i/source/*', '/atem/usk/$i/luma/*', and '/atem/usk/$i/chroma/*' ### Downstream Keyers - - **Set On-Air Downstreamkeyer $i** `/atem/dsk/$i/on-air <0|1>` - - Send a value of 1 to cut the DSK on-air, and a value of 0 to cut it off-air + - **Set On-Air Downstreamkeyer $i** `/atem/dsk/$i/on-air ` + - Send a value of true to cut the DSK on-air, and a value of false to cut it off-air - **Auto Toggle On-Air Downstreamkeyer $i** `/atem/dsk/$i/on-air/auto` - **Cut Toggle On-Air Downstreamkeyer $i** `/atem/dsk/$i/on-air/toggle` - - **Set Tie Downstreamkeyer $i** `/atem/dsk/$i/tie <0|1>` - - Send a value of 1 to enable tie, and 0 to disable + - **Set Tie Downstreamkeyer $i** `/atem/dsk/$i/tie ` + - Send a value of true to enable tie, and false to disable - **Toggle Tie Downstreamkeyer $i** `/atem/dsk/$i/tie/toggle` - - **Set Downstreamkeyer $i for Next Scene** `/atem/dsk/$i/tie/set-next <0|1>` - - Send a value of 1 to show the DSK after next transition, and 0 if you don’t want to show the DSK after next transition - - e.g. If DSK1 is on air, `/atem/dsk/1/tie/set-next 1` will untie DSK1 so that it remains on, while `/atem/dsk/1/tie/set-next 0` will tie DSK1 so that it will go off air after the next transition. - + - **Set Downstreamkeyer $i for Next Scene** `/atem/dsk/$i/tie/set-next ` + - Send a value of true to show the DSK after next transition, and false if you don’t want to show the DSK after next transition + - e.g. If DSK1 is on air, `/atem/dsk/1/tie/set-next true` will untie DSK1 so that it remains on, while `/atem/dsk/1/tie/set-next false` will tie DSK1 so that it will go off air after the next transition. + +#### DSK Source + - **Set Fill Source for Downstreamkeyer $i** `/atem/dsk/$i/source/fill ` + - Int value should be the ID of the input to set as the source (from in-app help menu, under the Sources section) + - **Set Key (cut) Source for Downstreamkeyer $i** `/atem/dsk/$i/source/cut ` + - Int value should be the ID of the input to set as the source (from in-app help menu, under the Sources section) + +#### DSK Parameters + - **Set Clip Parameter for Downstreamkeyer $i** `/atem/dsk/$i/clip ` + - Float value should be between 0.0 (for 0%) and 1.0 (for 100%) + - **Set Gain Parameter for Downstreamkeyer $i** `/atem/dsk/$i/gain ` + - Float value should be between 0.0 (for 0%) and 1.0 (for 100%) + - **Set Pre-multiplied Parameter for Downstreamkeyer $i** `/atem/dsk/$i/pre-multiplied ` + - **Set Invert Parameter for Downstreamkeyer $i** `/atem/dsk/$i/inverse ` + - **Set Rate Parameter for Downstreamkeyer $i** `/atem/dsk/$i/rate ` + - Int value is number of frames, so 30 is 1 second and 60 is 2 seconds (given 30 fps base value) + Where `$i` can be 1, 2, 3, or 4 depending on the capability of your ATEM switcher Feedback: Enabled for '/atem/dsk/$i/on-air' and '/atem/dsk/$i/tie' @@ -177,41 +198,41 @@ Feedback: Enabled for all values ### Media Players - - **Set Media Player $i source to Clip $x** `/atem/mplayer/$i/clip/$x` + - **Set Media Player $i source to Clip $x** `/atem/mplayer/$i/clip $x` - Where `$i` can be 1 or 2, and `$x` can be 1 or 2 depending on the capability of your ATEM switcher - - e.g. `/atem/mplayer/2/clip/1` - - **Set Media Player $i source to Still $x** `/atem/mplayer/$i/still/$x` + - e.g. `/atem/mplayer/2/clip 1` + - **Set Media Player $i source to Still $x** `/atem/mplayer/$i/still $x` - Where `$i` can be 1 or 2, and `$x` can be 1-20 depending on the capability of your ATEM switcher - - e.g. `/atem/mplayer/1/still/5` + - e.g. `/atem/mplayer/1/still 5` Feedback: None ### SuperSource (when available) - - **Toggle SuperSource Box $i enabled** `/atem/supersource/$i/enabled <0|1>` + - **Toggle SuperSource Box $i enabled** `/atem/supersource/$i/enabled ` - Send a value of 1 to enable, and 0 to disable - - **Set SuperSource Box $i source to input $x** `/atem/supersource/$i/source $x` + - **Set SuperSource Box $i source to input $x** `/atem/supersource/$i/source $x` - Where `$x` is a valid program source. Check the Help Menu for the correct values. - - Other options are available. Check the Help Menu in the app for the full list. + - Other options are available. Check the Help Menu in the app for the full list. Feedback: None ### Macros - - Macros should be recorded within the ATEM Control Panel software. - - Macros are stored within the ATEM in a 0-index array + - Macros should be recorded within the ATEM Control Panel software. + - Macros are stored within the ATEM in a 0-index array - This means that to access the first recorded Macro, you should use an index `$i` of `0`, to access the second recorded Macro, you should use an index of `1` etc. - - Get the Maximum Number of Macros: `/atem/macros/max-number` + - Get the Maximum Number of Macros: `/atem/macros/max-number` - Returns an `int` of the maximum number of Macros supported by your ATEM - Access to these Macros should be used via an index of `n-1` - - Stop the currently active Macro (if any): `/atem/macros/stop` - - Get the Name of a Macro: `/atem/macros/$i/name` + - Stop the currently active Macro (if any): `/atem/macros/stop` + - Get the Name of a Macro: `/atem/macros/$i/name` - Returns a `string` with the name, or "" if the Macro is invalid - - Get the Description of a Macro: `/atem/macros/$i/description` + - Get the Description of a Macro: `/atem/macros/$i/description` - Returns a `string` with the description, or "" if the Macro is invalid - - Get whether the Macro at index $i is valid: `/atem/macros/$i/is-valid` + - Get whether the Macro at index $i is valid: `/atem/macros/$i/is-valid` - Returns an `int` of `0|1` to indicate whether the requested Macro is valid - - Run the Macro at index $i: `/atem/macros/$i/run` + - Run the Macro at index $i: `/atem/macros/$i/run` - Returns an `int` of `0|1` to indicate whether the requested Macro was executed. A `0` will be returned if the Macro is invalid, or does not exist Feedback: Enabled for `/atem/macros/max-number`, `/atem/macros/$i/name`, `/atem/macros/$i/description`, and `/atem/macros/$i/is-valid`. Also available On-Request (you can send the command to get the value in a return message) @@ -230,14 +251,30 @@ Feedback: Enabled for `/atem/macros/max-number`, `/atem/macros/$i/name`, `/atem/ - e.g. `/atem/hyperdeck/1/shuttle 0` = stopped - **Jog Clip on HyperDeck $i** `/atem/hyperdeck/$i/jog $x` - Where `$x` is an integer value specifying the number of frames to jump forward or backward in the selected clip + - **Jump to clip time $x on HyperDeck $i** `/atem/hyperdeck/$i/clip-time $x` + - Where `$x` is a string in the format 'hh:mm:ss' (h = hour, m = minute, s = second) + - e.g. `/atem/hyperdeck/1/clip-time 00:05:00` = jump 5 minutes into the clip + - **Jump to timeline time $x on HyperDeck $i** `/atem/hyperdeck/$i/timeline-time $x` Feedback: Enabled for `/atem/hyperdeck/$i/clip`. The state of the HyperDeck is available as a string value at `/atem/hyperdeck/$i/state`, and is sent out automatically when the state changes. State options are `play`, `record`, `shuttle`, `idle`, or `unknown`. ### Other - **Request all feedback available** `/atem/send-status` - - This will query the switcher and send back the status for the program/preview, transition control, keyers, and macros - - e.g. This can be used when a new OSC client device is brought online, so that it gets the current status of the system + - This will query the switcher and send back the status for the program/preview, transition control, keyers, and macros + - e.g. This can be used when a new OSC client device is brought online, so that it gets the current status of the system + +### Type Casting + +For your convenience, atemOSC will cast certain types to the correct type for certain endpoints + + - If you pass an int or float value of 0 to a boolean method, the 0 will be interpreted as false + - If you pass an int or float value of 1 to a boolean method, the 1 will be interpreted as true + - If you pass an int value to an endpoint that requires a float, it will be properly converted (and vice-versa) + +### TouchOSC Support + +Due to the limited capabilities of the TouchOSC client, atemOSC supports an alternative form of passing values. For any method listed above that requires a string or int value, you can pass the string or int as part of the address instead of as a value. For example, you can send `/atem/transition/type/wipe` instead of `/atem/transition/type wipe`, or send `/atem/usk/1/source/fill/3` instead of `/atem/usk/1/source/fill 3`. Additionally, any command sent with one of these alternative addresses and a float value of `0.0` will be ignored, as that represents a button release and commonly causes issues. If you would like to trigger a change on button release instead of button press, simply flip the values in the TouchOSC Editor. ---------- @@ -256,7 +293,7 @@ You are free to open an issue or comment on and existing issue, but the quickest ### Auto and cut commands don’t seem to work, or look buggy, when combining atemOSC with MIDI control #### Problem -A lot of MIDI controls send two signals when a button is pressed, one signal when you press down, and another when you release. If you connect the button the `/atem/transition/auto` or `cut`, atemOSC recieves both events and attempts to send the transition command to the switcher twice. This can cause buggy behavior or just not work at all. +A lot of MIDI controls send two signals when a button is pressed, one signal when you press down, and another when you release. If you connect the button the `/atem/transition/auto` or `cut`, atemOSC recieves both events and attempts to send the transition command to the switcher twice. This can cause buggy behavior or just not work at all. #### Solution Tune your MIDI software to send only one of the two signals, either ok button press (rising edge) or button release (falling edge). See #120 for instructions for OSCulator. @@ -265,6 +302,6 @@ Tune your MIDI software to send only one of the two signals, either ok button pr ## Acknowledgements -- The code is based on the *SwitcherPanel*-Democode (Version 3.5) provided by Blackmagic. -- [VVOSC](http://code.google.com/p/vvopensource/) is used as OSC-framework. -- Program icon based heavily on the ATEM Software Control icon by [Blackmagic Design](http://www.blackmagicdesign.com). + - The code is based on the *SwitcherPanel*-Democode (Version 3.5) provided by Blackmagic. + - [VVOSC](http://code.google.com/p/vvopensource/) is used as OSC-framework. + - Program icon based heavily on the ATEM Software Control icon by [Blackmagic Design](http://www.blackmagicdesign.com). diff --git a/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/Resources/Info.plist b/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/Resources/Info.plist index 3b94c59..1da6496 100644 --- a/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/Resources/Info.plist +++ b/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/Resources/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 19B88 + 19C57 CFBundleDevelopmentRegion English CFBundleExecutable @@ -27,7 +27,7 @@ DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 11C29 + 11C504 DTPlatformVersion GM DTSDKBuild @@ -37,7 +37,7 @@ DTXcode 1130 DTXcodeBuild - 11C29 + 11C504 LSMinimumSystemVersion 10.10 diff --git a/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/VVBasics b/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/VVBasics index ac2e37b..20972b9 100755 Binary files a/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/VVBasics and b/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/VVBasics differ diff --git a/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/_CodeSignature/CodeResources b/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/_CodeSignature/CodeResources index 870090f..84e833f 100644 --- a/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/_CodeSignature/CodeResources +++ b/atemOSC.app/Contents/Frameworks/VVBasics.framework/Versions/A/_CodeSignature/CodeResources @@ -6,7 +6,7 @@ Resources/Info.plist - aBL+oY/babUC+/4PXQDuGCTbmIw= + uxEKDA4xNxNzYQMJwxftRXcOP2M= Resources/LICENSE @@ -19,11 +19,11 @@ hash - aBL+oY/babUC+/4PXQDuGCTbmIw= + uxEKDA4xNxNzYQMJwxftRXcOP2M= hash2 - I3kcVlezeZlWoLli8U0I6OCnnNyQPKeZEthbPS+ES+s= + JtIQ5rzuMY6YYg90jOvb5iAZ4wutfFEj3yExFg+9Q/E= Resources/LICENSE diff --git a/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/Resources/Info.plist b/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/Resources/Info.plist index 59cc03a..2f84748 100644 --- a/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/Resources/Info.plist +++ b/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/Resources/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 19B88 + 19C57 CFBundleDevelopmentRegion English CFBundleExecutable @@ -27,7 +27,7 @@ DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 11C29 + 11C504 DTPlatformVersion GM DTSDKBuild @@ -37,7 +37,7 @@ DTXcode 1130 DTXcodeBuild - 11C29 + 11C504 LSMinimumSystemVersion 10.10 diff --git a/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/VVOSC b/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/VVOSC index aa82879..47db49d 100755 Binary files a/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/VVOSC and b/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/VVOSC differ diff --git a/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/_CodeSignature/CodeResources b/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/_CodeSignature/CodeResources index fcfbce9..6ce12d0 100644 --- a/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/_CodeSignature/CodeResources +++ b/atemOSC.app/Contents/Frameworks/VVOSC.framework/Versions/A/_CodeSignature/CodeResources @@ -6,7 +6,7 @@ Resources/Info.plist - IsW4dK+rcFdi1JzfNodOnrc5F/Y= + mqULvg+wkarUvTMUapHTROcyujM= files2 @@ -15,11 +15,11 @@ hash - IsW4dK+rcFdi1JzfNodOnrc5F/Y= + mqULvg+wkarUvTMUapHTROcyujM= hash2 - RYrLPHhxjjDUYtKfdyUwNSb4fw5I1TVqQ8VgYW6JBNY= + YIoogBUi8DzA62rHenVRfCuKfeALHv16pllebYunLTY= diff --git a/atemOSC.app/Contents/Info.plist b/atemOSC.app/Contents/Info.plist index 52691dc..ba09ca3 100644 --- a/atemOSC.app/Contents/Info.plist +++ b/atemOSC.app/Contents/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 19B88 + 19C57 CFBundleDevelopmentRegion English CFBundleExecutable @@ -20,6 +20,8 @@ atemOSC CFBundlePackageType APPL + CFBundleShortVersionString + 3.0.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -31,7 +33,7 @@ DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 11C29 + 11C504 DTPlatformVersion GM DTSDKBuild @@ -41,7 +43,7 @@ DTXcode 1130 DTXcodeBuild - 11C29 + 11C504 LSMinimumSystemVersion 10.9 NSMainNibFile diff --git a/atemOSC.app/Contents/MacOS/atemOSC b/atemOSC.app/Contents/MacOS/atemOSC index 429db61..e99999e 100755 Binary files a/atemOSC.app/Contents/MacOS/atemOSC and b/atemOSC.app/Contents/MacOS/atemOSC differ diff --git a/atemOSC.app/Contents/Resources/Assets.car b/atemOSC.app/Contents/Resources/Assets.car index 25ca199..59fe4e3 100644 Binary files a/atemOSC.app/Contents/Resources/Assets.car and b/atemOSC.app/Contents/Resources/Assets.car differ diff --git a/atemOSC.app/Contents/Resources/English.lproj/MainMenu.nib b/atemOSC.app/Contents/Resources/English.lproj/MainMenu.nib index 7b8a191..f024610 100644 Binary files a/atemOSC.app/Contents/Resources/English.lproj/MainMenu.nib and b/atemOSC.app/Contents/Resources/English.lproj/MainMenu.nib differ diff --git a/atemOSC.app/Contents/_CodeSignature/CodeResources b/atemOSC.app/Contents/_CodeSignature/CodeResources index cb79d42..a102782 100644 --- a/atemOSC.app/Contents/_CodeSignature/CodeResources +++ b/atemOSC.app/Contents/_CodeSignature/CodeResources @@ -10,7 +10,7 @@ Resources/Assets.car - 9gmHOetLoTsB+b0r87t6MoQuORc= + b6iNnrhvUAfxRlrhCZbpnJgvRho= Resources/English.lproj/InfoPlist.strings @@ -25,7 +25,7 @@ hash - V+dzOHGDjeHhBzzBXteotCCe+gc= + wBuaKUn4VZeFg2mKnDZt4E//u8M= optional @@ -41,7 +41,7 @@ cdhash - n3+ahAsOMfbLU+FvA8mFDklLWvY= + VnUyxGww9kziBAKkzjrSQUs1Nnk= requirement identifier "com.yourcompany.VVBasics" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: Ken Steffey (649CFX9WPE)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ @@ -50,7 +50,7 @@ cdhash - 4rqnrWBG8OshBChmyX5Yx8jesuA= + XFVVMK3iviuw2KR4y+2msv0xias= requirement identifier "com.yourcompany.VVOSC" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: Ken Steffey (649CFX9WPE)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ @@ -70,11 +70,11 @@ hash - 9gmHOetLoTsB+b0r87t6MoQuORc= + b6iNnrhvUAfxRlrhCZbpnJgvRho= hash2 - oxvdPooL+X1LHZZzaZyTDJ/kHpUx4C2RLtRvjbSEMvs= + WlH2b62MgYZ9yW2C0fu9CkGN6RKRzC8m6nISEYMQM3Q= Resources/English.lproj/InfoPlist.strings @@ -94,11 +94,11 @@ hash - V+dzOHGDjeHhBzzBXteotCCe+gc= + wBuaKUn4VZeFg2mKnDZt4E//u8M= hash2 - /zb8JN1gbA8IBmz7W6Z6gq5on6+sgO1ad+gqZMK+I/I= + 2yOOtP93uzCJP0oVdMX9JJmAmC3+1tIBHx2urhDwi0s= optional diff --git a/atemOSC.jpg b/atemOSC.jpg deleted file mode 100644 index e72f6d0..0000000 Binary files a/atemOSC.jpg and /dev/null differ diff --git a/atemOSC/AppDelegate.h b/atemOSC/AppDelegate.h index 9f1e2c4..f72efdb 100644 --- a/atemOSC/AppDelegate.h +++ b/atemOSC/AppDelegate.h @@ -33,6 +33,7 @@ #import #import "FeedbackMonitors.h" +#import "OSCEndpoint.h" @class OSCAddressPanel; @class OSCReceiver; @@ -87,6 +88,8 @@ @property (strong) IBOutlet NSWindow* window; @property (strong) id activity; +@property(nonatomic, retain) NSMutableArray *endpoints; + - (void)connectBMD; - (void)portChanged:(int)inPortValue out:(int)outPortValue ip:(NSString *)outIpStr; - (IBAction)githubPageButtonPressed:(id)sender; diff --git a/atemOSC/AppDelegate.mm b/atemOSC/AppDelegate.mm index 5d0783f..9393e04 100644 --- a/atemOSC/AppDelegate.mm +++ b/atemOSC/AppDelegate.mm @@ -29,6 +29,7 @@ #include #import "OSCAddressPanel.h" #import "SettingsWindow.h" +#import "OSCReceiver.h" @implementation AppDelegate @@ -58,6 +59,7 @@ @implementation AppDelegate @synthesize mSwitcher; @synthesize mHyperdecks; @synthesize mHyperdeckMonitors; +@synthesize endpoints; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { @@ -76,6 +78,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification mMacroPool = NULL; isConnectedToATEM = NO; + endpoints = [[NSMutableArray alloc] init]; mOscReceiver = [[OSCReceiver alloc] initWithDelegate:self]; mSwitcherMonitor = new SwitcherMonitor(self); @@ -111,8 +114,8 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [self switcherDisconnected]; // start with switcher disconnected - // make an osc manager- i'm using a custom in-port to record a bunch of extra conversion for the display, but you can just make a "normal" manager manager = [[OSCManager alloc] init]; + [manager setDelegate:mOscReceiver]; NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; @@ -158,17 +161,20 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification - (void)portChanged:(int)inPortValue out:(int)outPortValue ip:(NSString *)outIpStr { - [manager removeInput:inPort]; - - if (outIpStr != nil) - { - [manager removeOutput:outPort]; + if (inPort == nil) + inPort = [manager createNewInputForPort:inPortValue withLabel:@"atemOSC"]; + else if (inPortValue != [inPort port]) + [inPort setPort:inPortValue]; + + if (outPort == nil) outPort = [manager createNewOutputToAddress:outIpStr atPort:outPortValue withLabel:@"atemOSC"]; + else + { + if (![outIpStr isEqualToString: [outPort addressString]]) + [outPort setAddressString:outIpStr]; + if (outPortValue != [outPort port]) + [outPort setPort:outPortValue]; } - - inPort = [manager createNewInputForPort:inPortValue withLabel:@"atemOSC"]; - - [manager setDelegate:mOscReceiver]; } - (void)applicationWillTerminate:(NSNotification*)aNotification @@ -508,12 +514,16 @@ - (void)switcherDisconnected self.activity = nil; - OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/led/green"]; - [newMsg addFloat:0.0]; - [outPort sendThisMessage:newMsg]; - newMsg = [OSCMessage createWithAddress:@"/atem/led/red"]; - [newMsg addFloat:1.0]; - [outPort sendThisMessage:newMsg]; + if (outPort != nil) + { + OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/led/green"]; + [newMsg addFloat:0.0]; + [outPort sendThisMessage:newMsg]; + newMsg = [OSCMessage createWithAddress:@"/atem/led/red"]; + [newMsg addFloat:1.0]; + [outPort sendThisMessage:newMsg]; + } + [(SettingsWindow *)window showSwitcherDisconnected]; @@ -656,6 +666,7 @@ - (void)logMessage:(NSString *)message if (message) { dispatch_async(dispatch_get_main_queue(), ^{ [self appendMessage:message]; + [(SettingsWindow *)window updateLogLabel:message]; }); NSLog(@"%@", message); } diff --git a/atemOSC/Base.lproj/MainMenu.xib b/atemOSC/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..8bb745b --- /dev/null +++ b/atemOSC/Base.lproj/MainMenu.xib @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atemOSC/English.lproj/MainMenu.xib b/atemOSC/English.lproj/MainMenu.xib index 8bb745b..77160f0 100644 --- a/atemOSC/English.lproj/MainMenu.xib +++ b/atemOSC/English.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -117,18 +117,19 @@ + - - - - + + + + - - + + @@ -136,20 +137,20 @@ - - + + - + - - + + @@ -157,8 +158,8 @@ - - + + @@ -166,8 +167,8 @@ - - + + @@ -175,8 +176,8 @@ - - + + @@ -184,12 +185,12 @@ - - + + - + @@ -197,24 +198,24 @@ - - + + - + - - + + - + @@ -222,8 +223,8 @@ - - + + @@ -231,28 +232,71 @@ - - + + - + + + - + @@ -266,23 +310,23 @@ - + - + - + - + - + @@ -293,7 +337,7 @@ - + @@ -303,11 +347,11 @@ - - + - + - + - + - + @@ -347,18 +391,18 @@ - - + diff --git a/atemOSC/FeedbackMonitors.mm b/atemOSC/FeedbackMonitors.mm index cb77089..0f56df4 100644 --- a/atemOSC/FeedbackMonitors.mm +++ b/atemOSC/FeedbackMonitors.mm @@ -85,6 +85,10 @@ OSCMessage *newMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/program/%lld",it.first]]; if (programId==it.first) {[newMsg addFloat:1.0];} else {[newMsg addFloat:0.0];} [static_cast(appDel).outPort sendThisMessage:newMsg]; + + OSCMessage *newMsg2 = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/program"]]; + [newMsg2 addInt:(int)it.first]; + [static_cast(appDel).outPort sendThisMessage:newMsg2]; } } @@ -98,6 +102,10 @@ OSCMessage *newMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/preview/%lld",it.first]]; if (previewId==it.first) {[newMsg addFloat:1.0];} else {[newMsg addFloat:0.0];} [static_cast(appDel).outPort sendThisMessage:newMsg]; + + OSCMessage *newMsg2 = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/preview"]]; + [newMsg2 addInt:(int)it.first]; + [static_cast(appDel).outPort sendThisMessage:newMsg2]; } } @@ -233,11 +241,6 @@ bool isTied; key->GetTie(&isTied); - // Deprecated - OSCMessage *oldMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/dsk/set-tie/%d",i]]; - [oldMsg addInt: isTied]; - [static_cast(appDel).outPort sendThisMessage:oldMsg]; - OSCMessage *newMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/dsk/%d/tie",i]]; [newMsg addInt: isTied]; [static_cast(appDel).outPort sendThisMessage:newMsg]; @@ -255,11 +258,6 @@ bool isOnAir; key->GetOnAir(&isOnAir); - // Deprecated - OSCMessage *oldMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/dsk/on-air/%d",i]]; - [oldMsg addInt: isOnAir]; - [static_cast(appDel).outPort sendThisMessage:oldMsg]; - OSCMessage *newMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/dsk/%d/on-air",i]]; [newMsg addInt: isOnAir]; [static_cast(appDel).outPort sendThisMessage:newMsg]; @@ -651,11 +649,6 @@ for (int i = 0; i <= ((int) reinterpret_cast(appDel).keyers.size()); i++) { uint32_t requestedTransitionSelection = transitionSelections[i]; - - // Deprecated - OSCMessage *oldMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/nextusk/%d",i]]; - [oldMsg addInt: ((requestedTransitionSelection & currentTransitionSelection) == requestedTransitionSelection)]; - [static_cast(appDel).outPort sendThisMessage:oldMsg]; OSCMessage *newMsg = [OSCMessage createWithAddress:[NSString stringWithFormat:@"/atem/usk/%d/tie",i]]; [newMsg addInt: ((requestedTransitionSelection & currentTransitionSelection) == requestedTransitionSelection)]; diff --git a/atemOSC/OSCAddressPanel.mm b/atemOSC/OSCAddressPanel.mm index 980d118..c3c8635 100644 --- a/atemOSC/OSCAddressPanel.mm +++ b/atemOSC/OSCAddressPanel.mm @@ -8,6 +8,7 @@ #import "OSCAddressPanel.h" #import "BMDSwitcherAPI.h" #import "AppDelegate.h" +#import "OSCEndpoint.h" @implementation OSCAddressPanel @@ -48,44 +49,7 @@ - (void)setupWithDelegate:(AppDelegate *)appDel [self addEntry:@"Set to Wipe" forAddress:@"/atem/transition/set-type/wipe" toString:helpString]; [self addEntry:@"Set to Stinger" forAddress:@"/atem/transition/set-type/sting" toString:helpString]; [self addEntry:@"Set to DVE" forAddress:@"/atem/transition/set-type/dve" toString:helpString]; - - [self addHeader:@"Upstream Keyers" toString:helpString]; - [self addEntry:@"Set Tie BKGD" forAddress:@"/atem/usk/0/tie" toString:helpString]; - [self addEntry:@"Toggle Tie BKGD" forAddress:@"/atem/usk/0/tie/toggle" toString:helpString]; - for (int i = 0; i<[appDel keyers].size();i++) - { - [self addEntry:[NSString stringWithFormat:@"Set USK%d On Air",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/on-air\t<0|1>",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Toggle On Air USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/on-air/toggle",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Tie USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/tie\t<0|1>",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Toggle Tie USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/tie/toggle",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Next-Transition State USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/tie/set-next\t<0|1>",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Fill Source USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/source/fill\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Cut Source USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/source/cut\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Clip Luma Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/luma/clip\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Gain Luma Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/luma/gain\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Pre-Multiplied Luma Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/luma/pre-multiplied\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Inverse Luma Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/luma/inverse\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Enabled DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-enabled/true",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Inner Width DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-width-inner\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Outer Width DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-width-outer\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Inner Softness DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-softness-inner\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Outer Softness DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-softness-outer\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Hue DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-hue\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Saturation DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-saturation\t",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Border Luman DVE Parameter USK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/usk/%d/dve/border-luma\t",i+1] toString:helpString]; - } - - [self addHeader:@"Downstream Keyers" toString:helpString]; - for (int i = 0; i<[appDel dsk].size();i++) - { - [self addEntry:[NSString stringWithFormat:@"Set DSK%d On Air",i+1] forAddress:[NSString stringWithFormat:@"/atem/dsk/%d/on-air\t<0|1>",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Auto-Transistion DSK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/dsk/%d/on-air/auto",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Toggle On Air DSK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/dsk/%d/on-air/toggle",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Tie DSK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/dsk/%d/tie\t<0|1>",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Toggle Tie DSK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/dsk/%d/tie/toggle",i+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Next-Transition State DSK%d",i+1] forAddress:[NSString stringWithFormat:@"/atem/dsk/%d/tie/set-next\t<0|1>",i+1] toString:helpString]; - } - + [self addHeader:@"Sources" toString:helpString]; HRESULT result; @@ -117,6 +81,52 @@ - (void)setupWithDelegate:(AppDelegate *)appDel } inputIterator->Release(); + [self addHeader:@"Upstream Keyers" toString:helpString]; + [self addEntry:@"Set Tie BKGD" forAddress:@"/atem/usk/0/tie" toString:helpString]; + [self addEntry:@"Toggle Tie BKGD" forAddress:@"/atem/usk/0/tie/toggle" toString:helpString]; + for (int i = 0; i<[appDel keyers].size();i++) + { + for (OSCEndpoint* endpoint : [appDel endpoints]) + { + if ([[endpoint addressTemplate] containsString:@"/usk/"]) + { + NSString *label = [[endpoint label] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithInt:(i+1)] stringValue]]; + NSString *address = [[endpoint addressTemplate] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithInt:(i+1)] stringValue]]; + if (endpoint.valueType == OSCValInt) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValBool) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValFloat) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValString) + address = [address stringByAppendingString:@" "]; + [self addEntry:label forAddress:address toString:helpString]; + } + } + } + + [self addHeader:@"Downstream Keyers" toString:helpString]; + for (int i = 0; i<[appDel dsk].size();i++) + { + for (OSCEndpoint* endpoint : [appDel endpoints]) + { + if ([[endpoint addressTemplate] containsString:@"/dsk/"]) + { + NSString *label = [[endpoint label] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithInt:(i+1)] stringValue]]; + NSString *address = [[endpoint addressTemplate] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithInt:(i+1)] stringValue]]; + if (endpoint.valueType == OSCValInt) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValBool) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValFloat) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValString) + address = [address stringByAppendingString:@" "]; + [self addEntry:label forAddress:address toString:helpString]; + } + } + } + [self addHeader:@"Audio Inputs" toString:helpString]; for (auto const& it : [appDel mAudioInputs]) @@ -198,45 +208,76 @@ - (void)setupWithDelegate:(AppDelegate *)appDel for (int i = 1; i <= [appDel mSuperSourceBoxes].size(); i++) { - [self addEntry:[NSString stringWithFormat:@"Set Box %d enabled",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/enabled\t<0|1>",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Input source",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/source\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Position X",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/x\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Position Y",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/y\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Size",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/size\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Cropped Enabled",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/cropped\t<0|1>",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Crop Top",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/crop-top\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Crop Bottom",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/crop-bottom\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Crop Left",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/crop-left\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set Box %d Crop Right",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/crop-right\t",i] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Reset Box %d Crop",i] forAddress:[NSString stringWithFormat:@"/atem/supersource/box/%d/crop-reset\t<1>",i] toString:helpString]; + for (OSCEndpoint* endpoint : [appDel endpoints]) + { + if ([[endpoint addressTemplate] containsString:@"/supersource/"]) + { + NSString *label = [[endpoint label] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithInt:i] stringValue]]; + NSString *address = [[endpoint addressTemplate] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithInt:i] stringValue]]; + if (endpoint.valueType == OSCValInt) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValBool) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValFloat) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValString) + address = [address stringByAppendingString:@" "]; + [self addEntry:label forAddress:address toString:helpString]; + } + } } } [self addHeader:@"Macros" toString:helpString]; - [self addEntry:@"Get the Maximum Number of Macros" forAddress:@"/atem/macros/max-number" toString:helpString]; - [self addEntry:@"Stop the currently active Macro (if any)" forAddress:@"/atem/macros/stop" toString:helpString]; - [self addEntry:@"Get the Name of a Macro" forAddress:@"/atem/macros//name" toString:helpString]; - [self addEntry:@"Get the Description of a Macro" forAddress:@"/atem/macros//description" toString:helpString]; - [self addEntry:@"Get whether the Macro at is valid" forAddress:@"/atem/macros//is-valid" toString:helpString]; - [self addEntry:@"Run the Macro at " forAddress:@"/atem/macros//run" toString:helpString]; + for (OSCEndpoint* endpoint : [appDel endpoints]) + { + if ([[endpoint addressTemplate] containsString:@"/macros/"]) + { + NSString *address = [endpoint addressTemplate]; + if (endpoint.valueType == OSCValInt) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValBool) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValFloat) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValString) + address = [address stringByAppendingString:@" "]; + [self addEntry:[endpoint label] forAddress:address toString:helpString]; + } + } - [self addHeader:@"HyperDecks" toString:helpString]; for (auto const& it : [appDel mHyperdecks]) { BMDSwitcherHyperDeckConnectionStatus status; it.second->GetConnectionStatus(&status); if (status == bmdSwitcherHyperDeckConnectionStatusConnected) { - [self addEntry:[NSString stringWithFormat:@"HyperDeck %lld Play", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/play", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"HyperDeck %lld Stop", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/stop", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"HyperDeck %lld Record", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/record", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"HyperDeck %lld Shuttle", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/shuttle\t", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"HyperDeck %lld Jog", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/jog\t", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set HyperDeck %lld Current Clip Number", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/clip\t", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set HyperDeck %lld Current Clip Time", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/clip-time\t", it.first+1] toString:helpString]; - [self addEntry:[NSString stringWithFormat:@"Set HyperDeck %lld Current Timeline Time", it.first+1] forAddress:[NSString stringWithFormat:@"/atem/hyperdeck/%lld/timeline-time\t", it.first+1] toString:helpString]; + if (it.first == 0) + [self addHeader:@"HyperDecks" toString:helpString]; + + for (OSCEndpoint* endpoint : [appDel endpoints]) + { + if ([[endpoint addressTemplate] containsString:@"/hyperdeck/"]) + { + NSString *label = [[endpoint label] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithLongLong:it.first+1] stringValue]]; + NSString *address = [[endpoint addressTemplate] stringByReplacingOccurrencesOfString:@"" withString:[[NSNumber numberWithLongLong:it.first+1] stringValue]]; + if (endpoint.valueType == OSCValInt) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValBool) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValFloat) + address = [address stringByAppendingString:@" "]; + else if (endpoint.valueType == OSCValString) + address = [address stringByAppendingString:@" "]; + [self addEntry:label forAddress:address toString:helpString]; + } + } } } + + [helpString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nNote: Additional addresses are available that provide backward-compatibility with TouchOSC. See the Readme on Github for details.\n\n"]]; + + [helpString appendAttributedString:[[NSAttributedString alloc] initWithString:@"We add support for addresses on an as-needed basis. If you are in need of an additional address, open an issue on Github letting us know what it is.\n"]]; [helpString addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0,helpString.length)]; [[helpTextView textStorage] setAttributedString:helpString]; diff --git a/atemOSC/OSCEndpoint.h b/atemOSC/OSCEndpoint.h new file mode 100644 index 0000000..5421360 --- /dev/null +++ b/atemOSC/OSCEndpoint.h @@ -0,0 +1,21 @@ +// +// OSCEndpoint.h +// AtemOSC +// +// Created by Peter Steffey on 1/11/20. +// + +#import +#import "VVOSC/VVOSC.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OSCEndpoint : NSObject + @property(nonatomic) NSString *addressTemplate; + @property(nonatomic) NSString *helpText; + @property(nonatomic) NSString *label; + @property(nonatomic) OSCValueType valueType; + @property(nonatomic, copy) void (^handler)(NSDictionary *, OSCValue *); +@end + +NS_ASSUME_NONNULL_END diff --git a/atemOSC/OSCEndpoint.m b/atemOSC/OSCEndpoint.m new file mode 100644 index 0000000..95bda8e --- /dev/null +++ b/atemOSC/OSCEndpoint.m @@ -0,0 +1,12 @@ +// +// OSCEndpoint.m +// AtemOSC +// +// Created by Peter Steffey on 1/11/20. +// + +#import "OSCEndpoint.h" + +@implementation OSCEndpoint + +@end diff --git a/atemOSC/OSCReceiver.h b/atemOSC/OSCReceiver.h index 7bc7f16..02e5c58 100644 --- a/atemOSC/OSCReceiver.h +++ b/atemOSC/OSCReceiver.h @@ -2,6 +2,7 @@ #define OSCReveiver_h #import "VVOSC/VVOSC.h" +#import "OSCEndpoint.h" @class AppDelegate; @@ -10,9 +11,14 @@ AppDelegate *appDel; } +@property(nonatomic, retain) NSMutableDictionary *endpointMap; +@property(nonatomic, retain) NSMutableDictionary *validators; + - (instancetype) initWithDelegate:(AppDelegate *)delegate; - (void) receivedOSCMessage:(OSCMessage *)m; @end + + #endif /* OSCReveiver_h */ diff --git a/atemOSC/OSCReceiver.mm b/atemOSC/OSCReceiver.mm index 958cff0..f1f3592 100644 --- a/atemOSC/OSCReceiver.mm +++ b/atemOSC/OSCReceiver.mm @@ -4,1063 +4,857 @@ @implementation OSCReceiver +@synthesize endpointMap; +@synthesize validators; + - (instancetype) initWithDelegate:(AppDelegate *) delegate { self = [super init]; appDel = delegate; - return self; -} + + endpointMap = [[NSMutableDictionary alloc] init]; + validators = [[NSMutableDictionary alloc] init]; + + NSLog(@"Setting up validators"); -- (void) receivedOSCMessage:(OSCMessage *)m -{ - [appDel logMessage:[NSString stringWithFormat:@"Received OSC message: %@\tValue: %@", [m address], [m value]]]; - if ([appDel isConnectedToATEM]) { //Do nothing if not connected - NSArray *address = [[m address] componentsSeparatedByString:@"/"]; + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (key > 0 && key <= [appDel dsk].size()) + return true; + [appDel logMessage:[NSString stringWithFormat:@"DSK %d is not available on your switcher, valid DSK values are 1 - %lu", key, [appDel dsk].size()]]; + return false; + } copy] forKey:@"/atem/dsk"]; + + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + NSString *address = [d objectForKey:@"address"]; - if ([[address objectAtIndex:1] isEqualToString:@"atem"]) - { - if ([[address objectAtIndex:2] isEqualToString:@"send-status"]) - [appDel sendStatus]; - - else if ([[address objectAtIndex:2] isEqualToString:@"preview"] || [[address objectAtIndex:2] isEqualToString:@"program"]) - activateChannel([[address objectAtIndex:3] intValue], [[address objectAtIndex:2] isEqualToString:@"program"]); - - else if ([[address objectAtIndex:2] isEqualToString:@"transition"]) - { - if ([[address objectAtIndex:3] isEqualToString:@"bar"]) - { - if ([appDel mMixEffectBlockMonitor]->mMoveSliderDownwards) - [appDel mMixEffectBlock]->SetTransitionPosition([m calculateFloatValue]); - else - [appDel mMixEffectBlock]->SetTransitionPosition(1.0-[m calculateFloatValue]); - } - - else if ([[address objectAtIndex:3] isEqualToString:@"cut"]) - [appDel mMixEffectBlock]->PerformCut(); - - else if ([[address objectAtIndex:3] isEqualToString:@"auto"]) - [appDel mMixEffectBlock]->PerformAutoTransition(); - - else if ([[address objectAtIndex:3] isEqualToString:@"ftb"]) - [appDel mMixEffectBlock]->PerformFadeToBlack(); - - else if ([[address objectAtIndex:3] isEqualToString:@"preview"]) - [appDel mMixEffectBlock]->SetPreviewTransition((int)[m calculateFloatValue]); - - else if ([[address objectAtIndex:3] isEqualToString:@"set-type"]) - { - - HRESULT result; - NSString *style = [address objectAtIndex:4]; - REFIID transitionStyleID = IID_IBMDSwitcherTransitionParameters; - IBMDSwitcherTransitionParameters* mTransitionStyleParameters=NULL; - result = [appDel mMixEffectBlock]->QueryInterface(transitionStyleID, (void**)&mTransitionStyleParameters); - if (SUCCEEDED(result)) - { - if ([style isEqualToString:@"mix"]) - mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleMix); - - else if ([style isEqualToString:@"dip"]) - mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleDip); - - else if ([style isEqualToString:@"wipe"]) - mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleWipe); - - else if ([style isEqualToString:@"sting"]) - mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleStinger); - - else if ([style isEqualToString:@"dve"]) - mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleDVE); - - else - [appDel logMessage:@"You must specify a transition type of 'mix', 'dip', 'wipe', 'sting', or 'dve'"]; - } - } - - else - [appDel logMessage:@"You must specify a transition action of 'bar', 'cut', 'auto', 'ftb', or 'set-type"]; - } - - else if ([[address objectAtIndex:2] isEqualToString:@"usk"] && [address count] > 4) - { - if (stringIsNumber([address objectAtIndex:3])) - { - int t = [[address objectAtIndex:3] intValue]; - - if ([[address objectAtIndex:4] isEqualToString:@"tie"]) - { - // Toggle tie - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"toggle"]) - { - uint32_t currentTransitionSelection; - [appDel switcherTransitionParameters]->GetNextTransitionSelection(¤tTransitionSelection); - - uint32_t transitionSelections[5] = { bmdSwitcherTransitionSelectionBackground, bmdSwitcherTransitionSelectionKey1, bmdSwitcherTransitionSelectionKey2, bmdSwitcherTransitionSelectionKey3, bmdSwitcherTransitionSelectionKey4 }; - uint32_t requestedTransitionSelection = transitionSelections[t]; - - [self changeTransitionSelection:t select:!((requestedTransitionSelection & currentTransitionSelection) == requestedTransitionSelection)]; - } - - // Set for state after next transition - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"set-next"]) - { - bool value = [[m value] floatValue] != 0.0; - - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - bool isOnAir; - key->GetOnAir(&isOnAir); - - [self changeTransitionSelection:t select:(value != isOnAir)]; - } - } - - // Set tie - else if ([address count] == 5) - { - bool value = [[m value] floatValue] != 0.0; - [self changeTransitionSelection:t select:value]; - } - - else - [appDel logMessage:@"You must specify a usk tie command of 'toggle' or 'set-next', or send a value to set the tie on or off"]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"on-air"]) - { - // Cut toggle on-air - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"toggle"]) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - bool onAir; - key->GetOnAir(&onAir); - key->SetOnAir(!onAir); - } - } - - // Force set on-air - else if ([address count] == 5) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - bool value = [[m value] floatValue] != 0.0; - key->SetOnAir(value); - } - } - - else - [appDel logMessage:@"You must specify a usk on-air command of 'toggle' or send a value to cut the usk on or off air"]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"source"]) - { - // Get/Set Fill Source - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"fill"]) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - if ([m valueCount] > 0) - { - BMDSwitcherInputId inputId = [[m value] intValue]; - key->SetInputFill(inputId); - } - } - } - - // Get/Set Key (cut) Source - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"cut"]) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - if ([m valueCount] > 0) - { - BMDSwitcherInputId inputId = [[m value] intValue]; - key->SetInputCut(inputId); - } - } - } - - else - [appDel logMessage:@"You must specify a usk source command of 'fill' or 'cut'"]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"type"]) - { - if (([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"luma"]) || [[[m value] stringValue] isEqualToString: @"luma"]) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - key->SetType(bmdSwitcherKeyTypeLuma); - } - } - else if (([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"chroma"]) || ([m valueCount] > 0 && [[[m value] stringValue] isEqualToString: @"chroma"])) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - key->SetType(bmdSwitcherKeyTypeChroma); - } - } - else if (([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"pattern"]) || ([m valueCount] > 0 && [[[m value] stringValue] isEqualToString: @"pattern"])) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - key->SetType(bmdSwitcherKeyTypePattern); - } - } - else if (([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"dve"]) || ([m valueCount] > 0 && [[[m value] stringValue] isEqualToString: @"dve"])) - { - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - key->SetType(bmdSwitcherKeyTypeDVE); - } - } - } - - else if ([[address objectAtIndex:4] isEqualToString:@"luma"]) - { - // Get/Set PreMultiplied - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"pre-multiplied"]) - { - if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:t]) - { - if ([m valueCount] > 0) - { - lumaParams->SetPreMultiplied([[m value] boolValue]); - } - } - } - - // Get/Set Clip - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"clip"]) - { - if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:t]) - { - if ([m valueCount] > 0) - { - lumaParams->SetClip([[m value] floatValue]); - } - } - } - - // Get/Set Gain - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"gain"]) - { - if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:t]) - { - if ([m valueCount] > 0) - { - lumaParams->SetGain([[m value] floatValue]); - } - } - } - - // Get/Set Inverse - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"inverse"]) - { - if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:t]) - { - if ([m valueCount] > 0) - { - lumaParams->SetInverse([[m value] boolValue]); - } - } - } - - else - [appDel logMessage:@"You must specify a usk luma command of 'pre-multiplied', 'clip', 'gain', or 'inverse'"]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"chroma"]) - { - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"hue"]) - { - if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:t]) - { - if ([m valueCount] > 0) - { - chromaParams->SetHue([[m value] floatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"gain"]) - { - if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:t]) - { - if ([m valueCount] > 0) - { - chromaParams->SetGain([[m value] floatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"y-suppress"]) - { - if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:t]) - { - if ([m valueCount] > 0) - { - chromaParams->SetYSuppress([[m value] floatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"lift"]) - { - if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:t]) - { - if ([m valueCount] > 0) - { - chromaParams->SetLift([[m value] floatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"narrow"]) - { - if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:t]) - { - if ([m valueCount] > 0) - { - chromaParams->SetNarrow([[m value] boolValue]); - } - } - } - - else - [appDel logMessage:@"You must specify a usk chroma command of 'hue', 'gain', 'y-suppress', 'lift', or 'narrow'"]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"dve"]) - { - if ([address count] == 7 && [[address objectAtIndex:5] isEqualToString:@"enabled"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderHue([[address objectAtIndex:6] boolValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-width-inner"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderWidthIn([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-width-outer"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderWidthOut([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-softness-outer"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderSoftnessOut([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-softness-outer"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderSoftnessOut([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-opacity"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderOpacity([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-hue"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderHue([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-saturation"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderSaturation([m calculateFloatValue]); - } - } - } - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"border-luma"]) - { - if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:t]) - { - if ([m valueCount] > 0) - { - dveParams->SetBorderLuma([m calculateFloatValue]); - } - } - } - - else - [appDel logMessage:@"You must specify a usk dve command of 'border-width-inner', 'border-width-outer', 'border-softness-inner', 'border-softness-outer', 'border-opacity', 'border-hue', 'border-saturation', or 'border-luma'"]; - } - - else - [appDel logMessage:@"You must specify a usk command of 'tie', 'on-air', 'type', 'source', 'chroma', or 'luma'"]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"You must specify a usk between 0 and %lu", [appDel keyers].size()]]; - } - - // Deprecated - else if ([[address objectAtIndex:2] isEqualToString:@"set-nextusk"]) - { - int t = [[address objectAtIndex:3] intValue]; - bool value = [m calculateFloatValue] != 0.0; - - if (IBMDSwitcherKey* key = [self getUSK:t]) - { - bool isOnAir; - key->GetOnAir(&isOnAir); - - [self changeTransitionSelection:t select:(value != isOnAir)]; - } - } - - // Deprecated - else if ([[address objectAtIndex:2] isEqualToString:@"nextusk"]) - { - int t = [[address objectAtIndex:3] intValue]; - bool value = [m calculateFloatValue] != 0.0; - [self changeTransitionSelection:t select:value]; - } - - // Deprecated - else if ([[address objectAtIndex:2] isEqualToString:@"usk"]) - { - IBMDSwitcherKey* key = [self getUSK:[[address objectAtIndex:3] intValue]]; - if (key && [m calculateFloatValue] != 0.0) - { - bool onAir; - key->GetOnAir(&onAir); - key->SetOnAir(!onAir); - } - } - - else if ([[address objectAtIndex:2] isEqualToString:@"dsk"]) - { - // Deprecated - if ([[address objectAtIndex:3] isEqualToString:@"set-tie"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:[[address objectAtIndex:4] intValue]]) - { - bool value = [m calculateFloatValue] != 0.0; - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetTie(value); - } - } - - // Deprecated - else if ([[address objectAtIndex:3] isEqualToString:@"tie"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:[[address objectAtIndex:4] intValue]]) - { - bool isTied; - key->GetTie(&isTied); - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetTie(!isTied); - } - } - - // Deprecated - else if ([[address objectAtIndex:3] isEqualToString:@"toggle"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:[[address objectAtIndex:4] intValue]]) - { - bool isLive; - key->GetOnAir(&isLive); - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetOnAir(!isLive); - } - } - - // Deprecated - else if ([[address objectAtIndex:3] isEqualToString:@"on-air"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:[[address objectAtIndex:4] intValue]]) - { - bool value = [m calculateFloatValue] != 0.0; - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetOnAir(value); - } - } - - // Deprecated - else if ([[address objectAtIndex:3] isEqualToString:@"set-next"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:[[address objectAtIndex:4] intValue]]) - { - bool value = [m calculateFloatValue] != 0.0; - bool isTransitioning, isOnAir; - key->IsTransitioning(&isTransitioning); - key->GetOnAir(&isOnAir); - if (!isTransitioning) key->SetTie(value != isOnAir); - } - } - - // Deprecated - else if ([address count] == 4 && stringIsNumber([address objectAtIndex:3])) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:[[address objectAtIndex:3] intValue]]) - { - bool isTransitioning; - key->IsAutoTransitioning(&isTransitioning); - if (!isTransitioning) key->PerformAutoTransition(); - } - } - - else if ([address count] > 4 && stringIsNumber([address objectAtIndex:3])) - { - int t = [[address objectAtIndex:3] intValue]; - - if ([[address objectAtIndex:4] isEqualToString:@"tie"]) - { - // Toggle tie - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"toggle"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:t]) - { - bool isTied; - key->GetTie(&isTied); - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetTie(!isTied); - } - } - - // Set for state after next transition - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"set-next"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:t]) - { - bool value = [m calculateFloatValue] != 0.0; - bool isTransitioning, isOnAir; - key->IsTransitioning(&isTransitioning); - key->GetOnAir(&isOnAir); - if (!isTransitioning) key->SetTie(value != isOnAir); - } - } - - // Set tie - else if ([address count] == 5) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:t]) - { - bool value = [m calculateFloatValue] != 0.0; - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetTie(value); - } - } - - else - [appDel logMessage:@"You must specify a dsk tie command of 'toggle' or 'set-next', or send a value to set the tie on or off"]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"on-air"]) - { - // Cut toggle on-air - if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"toggle"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:t]) - { - bool isLive; - key->GetOnAir(&isLive); - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetOnAir(!isLive); - } - } - - // Auto on-air - else if ([address count] == 6 && [[address objectAtIndex:5] isEqualToString:@"auto"]) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:t]) - { - bool isTransitioning; - key->IsAutoTransitioning(&isTransitioning); - if (!isTransitioning) key->PerformAutoTransition(); - } - } - - // Set on-air - else if ([address count] == 5) - { - if (IBMDSwitcherDownstreamKey* key = [self getDSK:t]) - { - bool value = [m calculateFloatValue] != 0.0; - bool isTransitioning; - key->IsTransitioning(&isTransitioning); - if (!isTransitioning) key->SetOnAir(value); - } - } - - else - [appDel logMessage:@"You must specify a dsk on-air command of 'toggle' or 'auto', or send a value to cut the dsk on or off air"]; - } - - else - [appDel logMessage:@"You must specify a dsk command of 'tie' or 'on-air'"]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"You must specify a dsk between 1 and %lu", [appDel dsk].size()]]; - } - - else if ([[address objectAtIndex:2] isEqualToString:@"mplayer"]) - { - int mplayer = [[address objectAtIndex:3] intValue]; - NSString *type = [address objectAtIndex:4]; - int requestedValue = [[address objectAtIndex:5] intValue]; - BMDSwitcherMediaPlayerSourceType sourceType; - - // check we have the media pool - if (![appDel mMediaPool]) - { - [appDel logMessage:@"No media pool\n"]; - return; - } - - if ([appDel mMediaPlayers].size() < mplayer || mplayer < 0) - { - [appDel logMessage:[NSString stringWithFormat:@"No media player %d", mplayer]]; - return; - } - - if ([type isEqualToString:@"clip"]) - { - sourceType = bmdSwitcherMediaPlayerSourceTypeClip; - } - else if ([type isEqualToString:@"still"]) - { - sourceType = bmdSwitcherMediaPlayerSourceTypeStill; - } - else - { - [appDel logMessage:@"You must specify the Media type 'clip' or 'still'"]; - return; - } - // set media player source - HRESULT result; - result = [appDel mMediaPlayers][mplayer-1]->SetSource(sourceType, requestedValue-1); - if (FAILED(result)) - { - [appDel logMessage:[NSString stringWithFormat:@"Could not set media player %d source\n", mplayer]]; - return; - } - } - - else if ([[address objectAtIndex:2] isEqualToString:@"supersource"]) - { - [self handleSuperSource:m address:address]; - } - - else if ([[address objectAtIndex:2] isEqualToString:@"macros"]) - { - [self handleMacros:m address:address]; - } - - else if ([[address objectAtIndex:2] isEqualToString:@"aux"]) - { - int auxToChange = [[address objectAtIndex:3] intValue]; - int source = [m calculateFloatValue]; - [self handleAuxSource:auxToChange channel:source]; - } - - else if ([[address objectAtIndex:2] isEqualToString:@"audio"] && [address count] > 3) - { - if ([[address objectAtIndex:3] isEqualToString:@"input"]) - { - if (stringIsNumber([address objectAtIndex:4])) - { - BMDSwitcherAudioInputId inputNumber = [[address objectAtIndex:4] intValue]; - if ([appDel mAudioInputs].count(inputNumber) > 0) - { - if ([[address objectAtIndex:5] isEqualToString:@"gain"]) - [appDel mAudioInputs][inputNumber]->SetGain([m calculateFloatValue]); - - else if ([[address objectAtIndex:5] isEqualToString:@"balance"]) - [appDel mAudioInputs][inputNumber]->SetBalance([m calculateFloatValue]); - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid option '%@'. You must specify an audio input option of 'gain' or 'balance'", [address objectAtIndex:5]]]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid input %lld. Please choose a valid audio input number from the list in Help > OSC addresses.", inputNumber]]; - } + // Normal USK + if (key > 0 && key <= [appDel keyers].size()) + return true; + + // Background + if (key == 0 && [address containsString:@"tie"]) + return true; + + [appDel logMessage:[NSString stringWithFormat:@"USK %d is not available on your switcher, valid USK values are 1 - %lu", key, [appDel keyers].size()]]; + return false; + } copy] forKey:@"/atem/usk"]; + + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int number = [[d objectForKey:@""] intValue]; + if (number > 0 && [appDel mHyperdecks].count(number-1) > 0) + return true; + [appDel logMessage:[NSString stringWithFormat:@"Hyperdeck %d is not available on your switcher, valid Hyperdecks are 1 - %lu", number, [appDel mHyperdecks].size()]]; + return false; + } copy] forKey:@"/atem/hyperdeck"]; + + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int number = [[d objectForKey:@""] intValue]; + if (number > 0 && [appDel mAudioInputs].count(number) > 0) + return true; + [appDel logMessage:[NSString stringWithFormat:@"Invalid input %d. Please choose a valid audio input number from the list in Help > OSC addresses.", number]]; + return false; + } copy] forKey:@"/atem/audio/input"]; + + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int mplayer = [[d objectForKey:@""] intValue]; - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid input %@. The address following input/ must be a number", [address objectAtIndex:4]]]; - } + if (![appDel mMediaPool]) + { + [appDel logMessage:@"No media pool\n"]; + return false; + } + + if ([appDel mMediaPlayers].size() < mplayer || mplayer < 0) + { + [appDel logMessage:[NSString stringWithFormat:@"No media player %d", mplayer]]; + return false; + } + return true; + } copy] forKey:@"/atem/mplayer"]; + + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; - else if ([[address objectAtIndex:3] isEqualToString:@"output"]) - { - if ([[address objectAtIndex:4] isEqualToString:@"gain"]) - [appDel mAudioMixer]->SetProgramOutGain([m calculateFloatValue]); - - else if ([[address objectAtIndex:4] isEqualToString:@"balance"]) - [appDel mAudioMixer]->SetProgramOutBalance([m calculateFloatValue]); - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid option '%@'. You must specify an audio output option of 'gain' or 'balance'", [address objectAtIndex:4]]]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid command '%@'. You must specify an audio command of 'input' or 'output'", [address objectAtIndex:3]]]; - } - - else if ([[address objectAtIndex:2] isEqualToString:@"hyperdeck"]) - { - if (stringIsNumber([address objectAtIndex:3])) - { - BMDSwitcherHyperDeckId hyperdeckNumber = [[address objectAtIndex:3] intValue]; - if ([appDel mHyperdecks].count(hyperdeckNumber-1) > 0) - { - if ([[address objectAtIndex:4] isEqualToString:@"play"]) - [appDel mHyperdecks][hyperdeckNumber-1]->Play(); - - else if ([[address objectAtIndex:4] isEqualToString:@"stop"]) - [appDel mHyperdecks][hyperdeckNumber-1]->Stop(); - - else if ([[address objectAtIndex:4] isEqualToString:@"record"]) - [appDel mHyperdecks][hyperdeckNumber-1]->Record(); - - else if ([[address objectAtIndex:4] isEqualToString:@"shuttle"]) - [appDel mHyperdecks][hyperdeckNumber-1]->Shuttle([[m value] intValue]); - - else if ([[address objectAtIndex:4] isEqualToString:@"jog"]) - [appDel mHyperdecks][hyperdeckNumber-1]->Jog([[m value] intValue]); - - else if ([[address objectAtIndex:4] isEqualToString:@"clip"]) - [appDel mHyperdecks][hyperdeckNumber-1]->SetCurrentClip([[m value] intValue]-1); - - else if ([[address objectAtIndex:4] isEqualToString:@"clip-time"]) - [self setHyperDeckTime:hyperdeckNumber-1 time:[[m value] stringValue] clip:YES]; - - else if ([[address objectAtIndex:4] isEqualToString:@"timeline-time"]) - [self setHyperDeckTime:hyperdeckNumber-1 time:[[m value] stringValue] clip:NO]; - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid option '%@'. You must specify a hyperdeck option of 'play', 'stop', 'record', 'shuttle', or 'jog'", [address objectAtIndex:4]]]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid HyperDeck identifier %lld. Please choose a valid HyperDeck identifier from the list in Help > OSC addresses.", hyperdeckNumber]]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"Invalid input %@. The address following hyperdeck/ must be a number", [address objectAtIndex:3]]]; - } - - else - [appDel logMessage:[NSString stringWithFormat:@"Cannot handle command: %@\nYou can find a list of valid commands in the help menu", [m address]]]; + if (![appDel mSuperSource]) + { + [appDel logMessage:@"No super source"]; + return false; + } + + if ([appDel mSuperSourceBoxes].size() < key) + { + [appDel logMessage:[NSString stringWithFormat:@"No super source box %d", key]]; + return false; } + + return true; + } copy] forKey:@"/atem/supersource"]; + + [validators setObject:[^bool(NSDictionary *d, OSCValue *v) { + int auxToChange = [[d objectForKey:@""] intValue]; + if (auxToChange > 0 && auxToChange-1 < [appDel mSwitcherInputAuxList].size()) + return true; + [appDel logMessage:[NSString stringWithFormat:@"Aux number %d not available on your switcher", auxToChange]]; + return false; + } copy] forKey:@"/atem/aux"]; + + + + NSLog(@"Setting up endpoints"); + + [self addEndpoint:@"/atem/send-status" handler:^void(NSDictionary *d, OSCValue *v) { + [appDel sendStatus]; + }]; + + [self addEndpoint:@"/atem/preview" valueType: OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + activateChannel([v intValue], false); + }]; + + [self addEndpoint:@"/atem/program" valueType: OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + activateChannel([v intValue], true); + }]; + + [self addEndpoint:@"/atem/transition/bar" valueType: OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + if ([appDel mMixEffectBlockMonitor]->mMoveSliderDownwards) + [appDel mMixEffectBlock]->SetTransitionPosition([v floatValue]); else - [appDel logMessage:[NSString stringWithFormat:@"Cannot handle command: %@\nYou can find a list of valid commands in the help menu", [m address]]]; - } - else - [appDel logMessage:[NSString stringWithFormat:@"Cannot process command %@ because no switcher connected", [m address]]]; -} + [appDel mMixEffectBlock]->SetTransitionPosition(1.0-[v floatValue]); + }]; + + [self addEndpoint:@"/atem/transition/cut" handler:^void(NSDictionary *d, OSCValue *v) { + [appDel mMixEffectBlock]->PerformCut(); + }]; + + [self addEndpoint:@"/atem/transition/auto" handler:^void(NSDictionary *d, OSCValue *v) { + [appDel mMixEffectBlock]->PerformAutoTransition(); + }]; + + [self addEndpoint:@"/atem/transition/ftb" handler:^void(NSDictionary *d, OSCValue *v) { + [appDel mMixEffectBlock]->PerformFadeToBlack(); + }]; + + [self addEndpoint:@"/atem/transition/preview" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + [appDel mMixEffectBlock]->SetPreviewTransition([v boolValue]); + }]; + + [self addEndpoint:@"/atem/transition/type" valueType:OSCValString handler:^void(NSDictionary *d, OSCValue *v) { + REFIID transitionStyleID = IID_IBMDSwitcherTransitionParameters; + IBMDSwitcherTransitionParameters* mTransitionStyleParameters=NULL; + [appDel mMixEffectBlock]->QueryInterface(transitionStyleID, (void**)&mTransitionStyleParameters); + + if ([[v stringValue] isEqualToString:@"mix"]) + mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleMix); + else if ([[v stringValue] isEqualToString:@"dip"]) + mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleDip); + else if ([[v stringValue] isEqualToString:@"wipe"]) + mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleWipe); + else if ([[v stringValue] isEqualToString:@"sting"]) + mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleStinger); + else if ([[v stringValue] isEqualToString:@"dve"]) + mTransitionStyleParameters->SetNextTransitionStyle(bmdSwitcherTransitionStyleDVE); + }]; + + [self addEndpoint:@"/atem/usk//tie" label:@"Set USK Tie" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [self changeTransitionSelection:key select:[v boolValue]]; + }]; + + [self addEndpoint:@"/atem/usk//tie/toggle" label: @"Toggle USK Tie" handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + uint32_t currentTransitionSelection; + [appDel switcherTransitionParameters]->GetNextTransitionSelection(¤tTransitionSelection); + + uint32_t transitionSelections[5] = { bmdSwitcherTransitionSelectionBackground, bmdSwitcherTransitionSelectionKey1, bmdSwitcherTransitionSelectionKey2, bmdSwitcherTransitionSelectionKey3, bmdSwitcherTransitionSelectionKey4 }; + uint32_t requestedTransitionSelection = transitionSelections[key]; + + [self changeTransitionSelection:key select:!((requestedTransitionSelection & currentTransitionSelection) == requestedTransitionSelection)]; + }]; + + [self addEndpoint:@"/atem/usk//tie/set-next" label:@"Set Next-Transition State for USK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isOnAir; + [appDel keyers][key-1]->GetOnAir(&isOnAir); + [self changeTransitionSelection:key select:([v boolValue] != isOnAir)]; + }]; + + [self addEndpoint:@"/atem/usk//on-air" label:@"Set USK On Air" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel keyers][key-1]->SetOnAir([v boolValue]); + }]; + + [self addEndpoint:@"/atem/usk//on-air/toggle" label:@"Toggle USK On Air" handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool onAir; + [appDel keyers][key-1]->GetOnAir(&onAir); + [appDel keyers][key-1]->SetOnAir(!onAir); + }]; + + [self addEndpoint:@"/atem/usk//source/fill" label:@"Set Fill Source for USK" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel keyers][key-1]->SetInputFill([v intValue]); + }]; + + [self addEndpoint:@"/atem/usk//source/cut" label:@"Set Cut Source for USK" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel keyers][key-1]->SetInputCut([v intValue]); + }]; + + [self addEndpoint:@"/atem/usk//type" label:@"Set USK Type" valueType:OSCValString handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if ([[v stringValue] isEqualToString:@"luma"]) + [appDel keyers][key-1]->SetType(bmdSwitcherKeyTypeLuma); + else if ([[v stringValue] isEqualToString:@"chroma"]) + [appDel keyers][key-1]->SetType(bmdSwitcherKeyTypeChroma); + else if ([[v stringValue] isEqualToString:@"pattern"]) + [appDel keyers][key-1]->SetType(bmdSwitcherKeyTypePattern); + else if ([[v stringValue] isEqualToString:@"dve"]) + [appDel keyers][key-1]->SetType(bmdSwitcherKeyTypeDVE); + }]; + + [self addEndpoint:@"/atem/usk//luma/pre-multiplied" label:@"Set Pre-Multiplied Luma Parameter for USK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:key]) + lumaParams->SetPreMultiplied([v boolValue]); + }]; + + [self addEndpoint:@"/atem/usk//luma/clip" label:@"Set Clip Luma Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:key]) + lumaParams->SetClip([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//luma/gain" label:@"Set Gain Luma Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:key]) + lumaParams->SetGain([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//luma/inverse" label:@"Set Inverse Luma Parameter for USK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyLumaParameters* lumaParams = [self getUSKLumaParams:key]) + lumaParams->SetInverse([v boolValue]); + }]; + + [self addEndpoint:@"/atem/usk//chroma/hue" label:@"Set Hue Chroma Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:key]) + chromaParams->SetHue([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//chroma/gain" label:@"Set Gain Chroma Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:key]) + chromaParams->SetGain([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//chroma/y-suppress" label:@"Set Y-Suppress Chroma Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:key]) + chromaParams->SetYSuppress([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//chroma/lift" label:@"Set Lift Chroma Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:key]) + chromaParams->SetLift([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//chroma/narrow" label:@"Set Narrow Chroma Parameter for USK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyChromaParameters* chromaParams = [self getUSKChromaParams:key]) + chromaParams->SetNarrow([v boolValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/enabled" label:@"Set Border Enabled DVE Parameter for USK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderEnabled([v boolValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-width-inner" label:@"Set Border Inner Width DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderWidthIn([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-width-outer" label:@"Set Border Outer Width DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderWidthOut([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-softness-inner" label:@"Set Border Inner Softness DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderSoftnessIn([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-softness-outer" label:@"Set Border Outer Softness DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderSoftnessOut([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-opacity" label:@"Set Border Opacity DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderOpacity([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-hue" label:@"Set Border Hue DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderHue([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-saturation" label:@"Set Border Saturation DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderSaturation([v floatValue]); + }]; + + [self addEndpoint:@"/atem/usk//dve/border-luma" label:@"Set Border Luma DVE Parameter for USK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + if (IBMDSwitcherKeyDVEParameters* dveParams = [self getUSKDVEParams:key]) + dveParams->SetBorderLuma([v floatValue]); + }]; + + [self addEndpoint:@"/atem/dsk//tie" label:@"Set DSK Tie" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isTransitioning; + [appDel dsk][key-1]->IsTransitioning(&isTransitioning); + if (!isTransitioning) [appDel dsk][key-1]->SetTie([v boolValue]); + }]; + + [self addEndpoint:@"/atem/dsk//tie/toggle" label:@"Toggle DSK Tie" handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isTied, isTransitioning; + [appDel dsk][key-1]->GetTie(&isTied); + [appDel dsk][key-1]->IsTransitioning(&isTransitioning); + if (!isTransitioning) [appDel dsk][key-1]->SetTie(!isTied); + }]; + + [self addEndpoint:@"/atem/dsk//tie/set-next" label:@"Set Next-Transition State for DSK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isTransitioning, isOnAir; + [appDel dsk][key-1]->IsTransitioning(&isTransitioning); + [appDel dsk][key-1]->GetOnAir(&isOnAir); + if (!isTransitioning) [appDel dsk][key-1]->SetTie([v boolValue] != isOnAir); + }]; + + [self addEndpoint:@"/atem/dsk//on-air" label:@"Set DSK On Air" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isTransitioning; + [appDel dsk][key-1]->IsTransitioning(&isTransitioning); + if (!isTransitioning) [appDel dsk][key-1]->SetOnAir([v boolValue]); + }]; + + [self addEndpoint:@"/atem/dsk//on-air/toggle" label:@"Toggle DSK On Air" handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isLive, isTransitioning; + [appDel dsk][key-1]->GetOnAir(&isLive); + [appDel dsk][key-1]->IsTransitioning(&isTransitioning); + if (!isTransitioning) [appDel dsk][key-1]->SetOnAir(!isLive); + }]; + + [self addEndpoint:@"/atem/dsk//on-air/auto" label:@"Auto-Transistion DSK" handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + bool isTransitioning; + [appDel dsk][key-1]->IsAutoTransitioning(&isTransitioning); + if (!isTransitioning) [appDel dsk][key-1]->PerformAutoTransition(); + }]; + + [self addEndpoint:@"/atem/dsk//source/fill" label:@"Set Fill Source for DSK" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetInputFill([v intValue]); + }]; + + [self addEndpoint:@"/atem/dsk//source/cut" label:@"Set Cut Source for DSK" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetInputCut([v intValue]); + }]; + + [self addEndpoint:@"/atem/dsk//clip" label:@"Set Clip Level for DSK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetClip([v floatValue]); + }]; + + [self addEndpoint:@"/atem/dsk//gain" label:@"Set Gain Level for DSK" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetGain([v floatValue]); + }]; + + [self addEndpoint:@"/atem/dsk//rate" label:@"Set Rate for DSK" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetRate([v intValue]); + }]; + + [self addEndpoint:@"/atem/dsk//inverse" label:@"Set Inverse Parameter for DSK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetInverse([v boolValue]); + }]; + + [self addEndpoint:@"/atem/dsk//pre-multiplied" label:@"Set Pre-multiplied Parameter for DSK" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel dsk][key-1]->SetPreMultiplied([v boolValue]); + }]; + + [self addEndpoint:@"/atem/mplayer//clip" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int mplayer = [[d objectForKey:@""] intValue]; + [appDel mMediaPlayers][mplayer-1]->SetSource(bmdSwitcherMediaPlayerSourceTypeClip, [v intValue]-1); + }]; + + [self addEndpoint:@"/atem/mplayer//still" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int mplayer = [[d objectForKey:@""] intValue]; + [appDel mMediaPlayers][mplayer-1]->SetSource(bmdSwitcherMediaPlayerSourceTypeStill, [v intValue]-1); + }]; + + [self addEndpoint:@"/atem/supersource/box//enabled" label:@"Set Box enabled" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetEnabled([v boolValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//source" label:@"Set Box Input Source" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetInputSource([v intValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//x" label:@"Set Box X Position" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetPositionX([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//y" label:@"Set Box Y Position" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetPositionY([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//size" label:@"Set Box Size" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetSize([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//cropped" label:@"Set Box Crop Enabled" valueType:OSCValBool handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetCropped([v boolValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//crop-top" label:@"Set Box Crop Top Amount" valueType: OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetCropTop([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//crop-bottom" label:@"Set Box Crop Bottom Amount" valueType: OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetCropBottom([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//crop-left" label:@"Set Box Crop Left Amount" valueType: OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetCropLeft([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//crop-right" label:@"Set Box Crop Right Amount" valueType: OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->SetCropRight([v floatValue]); + }]; + + [self addEndpoint:@"/atem/supersource/box//crop-reset" label:@"Reset box crop" handler:^void(NSDictionary *d, OSCValue *v) { + int key = [[d objectForKey:@""] intValue]; + [appDel mSuperSourceBoxes][key-1]->ResetCrop(); + }]; + + [self addEndpoint:@"/atem/macros/stop" label:@"Stop the currently active Macro (if any)" handler:^void(NSDictionary *d, OSCValue *v) { + stopRunningMacro(); + }]; + + [self addEndpoint:@"/atem/macros/max-number" label:@"Get the Maximum Number of Macros" handler:^void(NSDictionary *d, OSCValue *v) { + uint32_t value = getMaxNumberOfMacros(); + OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/macros/max-number"]; + [newMsg addInt:(int)value]; + [[appDel outPort] sendThisMessage:newMsg]; + }]; + + [self addEndpoint:@"/atem/macros//run" label:@"Run the Macro at " handler:^void(NSDictionary *d, OSCValue *v) { + int macroIndex = [[d objectForKey:@""] intValue]; + int value = runMacroAtIndex(macroIndex); // Try to run the valid Macro + OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/macros//run"]; + [newMsg addInt:(int)value]; + [[appDel outPort] sendThisMessage:newMsg]; + }]; + + [self addEndpoint:@"/atem/macros//name" label:@"Get the Name of a Macro" handler:^void(NSDictionary *d, OSCValue *v) { + int macroIndex = [[d objectForKey:@""] intValue]; + NSString *value = getNameOfMacro(macroIndex); + OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/macros//name"]; + [newMsg addString:(NSString *)value]; + [[appDel outPort] sendThisMessage:newMsg]; + }]; + + [self addEndpoint:@"/atem/macros//description" label:@"Get the Description of a Macro" handler:^void(NSDictionary *d, OSCValue *v) { + int macroIndex = [[d objectForKey:@""] intValue]; + NSString *value = getDescriptionOfMacro(macroIndex); + OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/macros//description"]; + [newMsg addString:(NSString *)value]; + [[appDel outPort] sendThisMessage:newMsg]; + }]; + + [self addEndpoint:@"/atem/macros//is-valid" label:@"Get whether the Macro at is valid" handler:^void(NSDictionary *d, OSCValue *v) { + int macroIndex = [[d objectForKey:@""] intValue]; + int value = isMacroValid(macroIndex) ? 1 : 0; + OSCMessage *newMsg = [OSCMessage createWithAddress:@"/atem/macros//is-valid"]; + [newMsg addInt:(int)value]; + [[appDel outPort] sendThisMessage:newMsg]; + }]; + + [self addEndpoint:@"/atem/aux/" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + int auxToChange = [[d objectForKey:@""] intValue]; + BMDSwitcherInputId inputId = [v intValue]; + [appDel mSwitcherInputAuxList][auxToChange-1]->SetInputSource(inputId); + }]; + + [self addEndpoint:@"/atem/audio/input//gain" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherAudioInputId inputNumber = [[d objectForKey:@""] intValue]; + [appDel mAudioInputs][inputNumber]->SetGain([v floatValue]); + }]; + + [self addEndpoint:@"/atem/audio/input//balance" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherAudioInputId inputNumber = [[d objectForKey:@""] intValue]; + [appDel mAudioInputs][inputNumber]->SetBalance([v floatValue]); + }]; + + [self addEndpoint:@"/atem/audio/output/gain" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + [appDel mAudioMixer]->SetProgramOutGain([v floatValue]); + }]; + + [self addEndpoint:@"/atem/audio/output/balance" valueType:OSCValFloat handler:^void(NSDictionary *d, OSCValue *v) { + [appDel mAudioMixer]->SetProgramOutBalance([v floatValue]); + }]; + + [self addEndpoint:@"/atem/hyperdeck//play" label:@"HyperDeck Play" handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [appDel mHyperdecks][hyperdeckNumber-1]->Play(); + }]; + + [self addEndpoint:@"/atem/hyperdeck//stop" label:@"HyperDeck Stop" handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [appDel mHyperdecks][hyperdeckNumber-1]->Stop(); + }]; + + [self addEndpoint:@"/atem/hyperdeck//record" label:@"HyperDeck Record" handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [appDel mHyperdecks][hyperdeckNumber-1]->Record(); + }]; + + [self addEndpoint:@"/atem/hyperdeck//shuttle" label:@"HyperDeck Shuttle" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [appDel mHyperdecks][hyperdeckNumber-1]->Shuttle([v intValue]); + }]; + + [self addEndpoint:@"/atem/hyperdeck//jog" label:@"HyperDeck Jog" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [appDel mHyperdecks][hyperdeckNumber-1]->Jog([v intValue]); + }]; + + [self addEndpoint:@"/atem/hyperdeck//clip" label:@"HyperDeck Select Clip" valueType:OSCValInt handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [appDel mHyperdecks][hyperdeckNumber-1]->SetCurrentClip([v intValue]-1); + }]; + + [self addEndpoint:@"/atem/hyperdeck//clip-time" label:@"HyperDeck Set Clip Time" valueType:OSCValString handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [self setHyperDeckTime:hyperdeckNumber-1 time:[v stringValue] clip:YES]; + }]; + + [self addEndpoint:@"/atem/hyperdeck//timeline-time" label:@"HyperDeck Set Timeline Time" valueType:OSCValString handler:^void(NSDictionary *d, OSCValue *v) { + BMDSwitcherHyperDeckId hyperdeckNumber = [[d objectForKey:@""] intValue]; + [self setHyperDeckTime:hyperdeckNumber-1 time:[v stringValue] clip:NO]; + }]; + + // Recursively build the tree from the list + // This allows for O(1) calls to find the handler for the address instead of O(n) + for (OSCEndpoint *endpoint in [appDel endpoints]) + [self buildMapLevel:endpointMap endpoint:endpoint index:0]; -- (IBMDSwitcherDownstreamKey *) getDSK:(int)t -{ - if (t<=[appDel dsk].size()) - { - return [appDel dsk][t-1]; - } - return nullptr; + // Turn on for debugging + //[self printLevel:endpointMap index:0]; + + return self; } -- (IBMDSwitcherKey *) getUSK:(int)t +// For debugging purposes +- (void) printLevel:(NSMutableDictionary *)level index:(int)index { - if (t<=[appDel keyers].size()) + if ([level isKindOfClass:[OSCEndpoint class]]) + return; + + for (NSString *key in [level allKeys]) { - return [appDel keyers][t-1]; + NSLog(@"%@%@", [@"" stringByPaddingToLength:index*2 withString: @" " startingAtIndex:0], key); + if ([level objectForKey:key] != nil) + [self printLevel:[level objectForKey:key] index:index+1]; } - return nullptr; } -- (IBMDSwitcherKeyLumaParameters *) getUSKLumaParams:(int)t +// Recursive function to build the endpoint tree from the endpoint list +// Tree is just nested dictionaries that correspond to each component in the OSC address +- (void) buildMapLevel:(NSMutableDictionary *)level endpoint:(OSCEndpoint *)endpoint index:(int)index { - if (t<=[appDel keyers].size()) - { - IBMDSwitcherKey* key = [appDel keyers][t-1]; - IBMDSwitcherKeyLumaParameters* lumaParams; - key->QueryInterface(IID_IBMDSwitcherKeyLumaParameters, (void**)&lumaParams); - return lumaParams; - } - return nullptr; + NSMutableArray *addressComponents = [NSMutableArray arrayWithArray:[[endpoint addressTemplate] componentsSeparatedByString:@"/"]]; + [addressComponents removeObjectAtIndex:0]; // Remove empty string + NSString *key = [addressComponents objectAtIndex:index]; + + // Create new dictionaries as needed + if ([level objectForKey:key] == nil) + [level setObject:[[NSMutableDictionary alloc] init] forKey:key]; + + if (index == [addressComponents count] - 1) + [[level objectForKey:key] setObject:endpoint forKey:@"handler"]; + else + [self buildMapLevel:[level objectForKey:key] endpoint:endpoint index:index+1]; } -- (IBMDSwitcherKeyChromaParameters *) getUSKChromaParams:(int)t +// Helper function for cleaner syntax when add endpoints (see examples above) +- (void) addEndpoint:(NSString *)addressTemplate label:(NSString*)label valueType:(OSCValueType)valueType handler:(void (^)(NSDictionary *, OSCValue *))handler { - if (t<=[appDel keyers].size()) - { - IBMDSwitcherKey* key = [appDel keyers][t-1]; - IBMDSwitcherKeyChromaParameters* chromaParams; - key->QueryInterface(IID_IBMDSwitcherKeyChromaParameters, (void**)&chromaParams); - return chromaParams; - } - return nullptr; + OSCEndpoint *endpoint = [[OSCEndpoint alloc] init]; + endpoint.addressTemplate = addressTemplate; + endpoint.handler = handler; + endpoint.valueType = valueType; + endpoint.label = label; + [[appDel endpoints] addObject: endpoint]; } - -- (IBMDSwitcherKeyDVEParameters *) getUSKDVEParams:(int)t +- (void) addEndpoint:(NSString *)addressTemplate valueType:(OSCValueType)valueType handler:(void (^)(NSDictionary *, OSCValue *))handler { - if (t<=[appDel keyers].size()) - { - IBMDSwitcherKey* key = [appDel keyers][t-1]; - IBMDSwitcherKeyDVEParameters* dveParams; - key->QueryInterface(IID_IBMDSwitcherKeyDVEParameters, (void**)&dveParams); - return dveParams; - } - return nullptr; + [self addEndpoint:addressTemplate label:nil valueType:valueType handler:handler]; } -- (void) changeTransitionSelection:(int)t select:(bool) select +// Makes valueType an optional parameter +- (void) addEndpoint:(NSString *)addressTemplate handler:(void (^)(NSDictionary *, OSCValue *))handler { - uint32_t currentTransitionSelection; - [appDel switcherTransitionParameters]->GetNextTransitionSelection(¤tTransitionSelection); - - uint32_t transitionSelections[5] = { bmdSwitcherTransitionSelectionBackground, bmdSwitcherTransitionSelectionKey1, bmdSwitcherTransitionSelectionKey2, bmdSwitcherTransitionSelectionKey3, bmdSwitcherTransitionSelectionKey4 }; - uint32_t requestedTransitionSelection = transitionSelections[t]; - - if (select) - { - [appDel switcherTransitionParameters]->SetNextTransitionSelection(currentTransitionSelection | requestedTransitionSelection); - } - else - { - // If we are attempting to deselect the only bit set, then default to setting TransitionSelectionBackground - if ((currentTransitionSelection & ~requestedTransitionSelection) == 0) - [appDel switcherTransitionParameters]->SetNextTransitionSelection(bmdSwitcherTransitionSelectionBackground); - else - [appDel switcherTransitionParameters]->SetNextTransitionSelection(currentTransitionSelection & ~requestedTransitionSelection); - } + [self addEndpoint:addressTemplate label:nil valueType:OSCValNil handler:handler]; } - -- (void) handleAuxSource:(int)auxToChange channel:(int)channel +- (void) addEndpoint:(NSString *)addressTemplate label:(NSString*)label handler:(void (^)(NSDictionary *, OSCValue *))handler { - BMDSwitcherInputId inputId = channel; - if (auxToChange-1 < [appDel mSwitcherInputAuxList].size()) - [appDel mSwitcherInputAuxList][auxToChange-1]->SetInputSource(inputId); - else - [appDel logMessage:[NSString stringWithFormat:@"Aux number %d not available on your switcher", channel]]; + [self addEndpoint:addressTemplate label:label valueType:OSCValNil handler:handler]; } -- (void) handleMacros:(OSCMessage *)m address:(NSArray*)address +- (OSCEndpoint *) findEndpointForAddress:(NSString *)address whileUpdatingArgs:(NSMutableDictionary *)args andSettingFinalLevel:(NSMutableDictionary *)finalLevel { - if (![appDel mMacroPool] || ![appDel mMacroControl]) - { - // No Macro support - OSCMessage *newMsg = [OSCMessage createWithAddress:[m address]]; - [newMsg addInt:(int)0]; - [[appDel outPort] sendThisMessage:newMsg]; - return; - } - if ([[address objectAtIndex:3] isEqualToString:@"get-max-number"] || [[address objectAtIndex:3] isEqualToString:@"max-number"]) + NSMutableArray *addressComponents = [NSMutableArray arrayWithArray:[address componentsSeparatedByString:@"/"]]; + [addressComponents removeObjectAtIndex:0]; // remove empty string + + NSMutableDictionary *currentLevel = endpointMap; + + // Go down tree of possible endpoints until one is found that matches + // Add paramaterized values as we go (ones that look like ) + for (int i = 0; i < [addressComponents count]; i++) { - uint32_t value = getMaxNumberOfMacros(); + NSString *method = addressComponents[i]; - OSCMessage *newMsg = [OSCMessage createWithAddress:[m address]]; - [newMsg addInt:(int)value]; - [[appDel outPort] sendThisMessage:newMsg]; - } - else if ([[address objectAtIndex:3] isEqualToString:@"stop"]) - { - stopRunningMacro(); - } - else - { - if (stringIsNumber([address objectAtIndex:3])) + // Match directly if possible + if ([currentLevel objectForKey:method] != nil) + currentLevel = [currentLevel objectForKey:method]; + // Otherwise, this is a variable or invalid method + else { - int macroIndex = [[address objectAtIndex:3] intValue]; - if ([[address objectAtIndex:4] isEqualToString:@"name"]) + // In case this is a variable in the address string, look for address templates that accept variables at this location + NSString *paramPlaceholder = nil; + for (NSString *option in [currentLevel allKeys]) { - NSString *value = getNameOfMacro(macroIndex); - OSCMessage *newMsg = [OSCMessage createWithAddress:[m address]]; - [newMsg addString:(NSString *)value]; - [[appDel outPort] sendThisMessage:newMsg]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"description"]) - { - NSString *value = getDescriptionOfMacro(macroIndex); - OSCMessage *newMsg = [OSCMessage createWithAddress:[m address]]; - [newMsg addString:(NSString *)value]; - [[appDel outPort] sendThisMessage:newMsg]; - } - - else if ([[address objectAtIndex:4] isEqualToString:@"is-valid"]) - { - int value = 0; - if (isMacroValid(macroIndex)) + if ([option hasPrefix:@"<"]) { - value = 1; + paramPlaceholder = option; + + // Set args to pass this to the handler function + if (stringIsNumber(method)) + [args setObject:[NSNumber numberWithInt:[method intValue]] forKey:option]; + else + [args setObject:method forKey:option]; } - OSCMessage *newMsg = [OSCMessage createWithAddress:[m address]]; - [newMsg addInt:(int)value]; - [[appDel outPort] sendThisMessage:newMsg]; } - else if ([[address objectAtIndex:4] isEqualToString:@"run"]) + // If found, set the level and continue + if ([currentLevel objectForKey:paramPlaceholder] != nil) + currentLevel = [currentLevel objectForKey:paramPlaceholder]; + // If we didn't find any available endpoints that accept, leave the loop and error out + else { - int value = 0; - if (isMacroValid(macroIndex)) - { - // Try to run the valid Macro - value = runMacroAtIndex(macroIndex); - } - OSCMessage *newMsg = [OSCMessage createWithAddress:[m address]]; - [newMsg addInt:(int)value]; - [[appDel outPort] sendThisMessage:newMsg]; + [finalLevel addEntriesFromDictionary:currentLevel]; + return nil; } - - else - [appDel logMessage:[NSString stringWithFormat:@"You must specify a macro command of 'run', 'name', 'description', or 'is-valid' for the macro at index %d", macroIndex]]; } - else - [appDel logMessage:@"You must specify a macro command of 'max-number', 'stop', or send the macro number you want to control as an integer"]; } + + [finalLevel addEntriesFromDictionary:currentLevel]; + if ([currentLevel objectForKey:@"handler"] != nil) + return [currentLevel objectForKey:@"handler"]; + + return nil; } -- (void) handleSuperSource:(OSCMessage *)m address:(NSArray*)address +// Starting at any point in the tree, recursively returns all child endpoints +// This is useful for showing contextual help menus when they type invalue commands +- (NSArray *) getEndpointsForNode:(NSMutableDictionary *)node { - if ([[address objectAtIndex:3] isEqualToString:@"box"]) + if (node == nil) + return [[NSArray alloc] init]; + + NSMutableArray *endpoints = [[NSMutableArray alloc] init]; + for (NSMutableDictionary *object in [node allValues]) { - [self handleSuperSourceBox:m address:address]; + if ([object isKindOfClass:[OSCEndpoint class]]) + [endpoints addObject:object]; + else + [endpoints addObjectsFromArray:[self getEndpointsForNode:object]]; } - else - [appDel logMessage:@"You must specify a super-source command of 'box'"]; + return endpoints; } -- (void) handleSuperSourceBox:(OSCMessage *)m address:(NSArray*)address +- (void) receivedOSCMessage:(OSCMessage *)m { - int box = [[address objectAtIndex:4] intValue]; - - // check we have the super source - if (![appDel mSuperSource]) - { - [appDel logMessage:@"No super source"]; - return; - } - - if ([appDel mSuperSourceBoxes].size() < box) - { - [appDel logMessage:[NSString stringWithFormat:@"No super source box %d", box]]; - return; - } - - // convert to value required for arrays - box--; - - if ([[address objectAtIndex:5] isEqualToString:@"enabled"]) - { - bool value = [m calculateFloatValue] != 0.0; - [appDel mSuperSourceBoxes][box]->SetEnabled(value); - } - - else if ([[address objectAtIndex:5] isEqualToString:@"source"]) - { - int value = [[m value] intValue]; - BMDSwitcherInputId InputId = value; - [appDel mSuperSourceBoxes][box]->SetInputSource(InputId); - } - - else if ([[address objectAtIndex:5] isEqualToString:@"x"]) - { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetPositionX(value); - } + [appDel logMessage:[NSString stringWithFormat:@"Received OSC message: %@\tValue: %@", [m address], [m value]]]; - else if ([[address objectAtIndex:5] isEqualToString:@"y"]) - { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetPositionY(value); - } + if (![appDel isConnectedToATEM]) + return [appDel logMessage:[NSString stringWithFormat:@"Cannot process command %@ because no switcher connected", [m address]]]; - else if ([[address objectAtIndex:5] isEqualToString:@"size"]) - { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetSize(value); - } + NSMutableDictionary *args = [[NSMutableDictionary alloc] init]; + [args setValue:[m address] forKey:@"address"]; + + NSMutableDictionary *finalLevel = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *finalLevelSecondTry = [[NSMutableDictionary alloc] init]; + OSCValue *value = [m value]; + + // First check normal address and values + OSCEndpoint *endpoint = [self findEndpointForAddress:[m address] whileUpdatingArgs:args andSettingFinalLevel:finalLevel]; - else if ([[address objectAtIndex:5] isEqualToString:@"cropped"]) + // Then try passing the last component of the address as the value and see if that matches any better + // This would be common with TouchOSC, for example passing /transition/type/mix 1.0 instead of /transition/type mix + if (endpoint == nil) { - bool value = [m calculateFloatValue] != 0.0; - [appDel mSuperSourceBoxes][box]->SetCropped(value); + // In TouchOSC specifically, it will send a float value of 1.0 on button press and 0.0 on button release + // This ignores the button release message to prevent duplicate calls + if ([[m value] type] == OSCValFloat && [[m value] floatValue] == 0.0) + return; + + NSMutableArray *componentArray = [[NSMutableArray alloc] initWithArray:[[m address] componentsSeparatedByString:@"/"]]; + if (stringIsNumber([componentArray lastObject])) + value = [OSCValue createWithInt:[[componentArray lastObject] intValue]]; + else + value = [OSCValue createWithString:[componentArray lastObject]]; + [componentArray removeLastObject]; + NSString *modifiedAddress = [componentArray componentsJoinedByString:@"/"]; + [args removeAllObjects]; + endpoint = [self findEndpointForAddress:modifiedAddress whileUpdatingArgs:args andSettingFinalLevel:finalLevelSecondTry]; } - else if ([[address objectAtIndex:5] isEqualToString:@"crop-top"]) + if (endpoint != nil) { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetCropTop(value); + NSLog(@"Found OSCEndpoint for address: %@", [m address]); + + if (value == nil && endpoint.valueType != OSCValNil) + return [appDel logMessage:[NSString stringWithFormat:@"Value required for %@, but no value given", [m address]]]; + + OSCValueType neededType = endpoint.valueType; + OSCValueType actualType = [value type]; + if (actualType != neededType) + { + // Transform value if needed to match desired type + // This is needed for compatibility with TouchOSC mostly, which can only send floats that need to be converted to ints and bools + if (neededType == OSCValInt && actualType == OSCValFloat) + value = [OSCValue createWithInt:(int)[[m value] floatValue]]; + else if (neededType == OSCValFloat && actualType == OSCValInt) + value = [OSCValue createWithFloat:(float)[[m value] intValue]]; + else if (neededType == OSCValBool && actualType == OSCValFloat) + value = [OSCValue createWithBool:[[m value] floatValue] == 1.0]; + else if (neededType == OSCValBool && actualType == OSCValInt) + value = [OSCValue createWithBool:[[m value] intValue] == 1]; + else if (neededType == OSCValNil) + [appDel logMessage:[NSString stringWithFormat:@"Unecessary value passed for %@, but running regardless", [m address]]]; + else + return [appDel logMessage:[NSString stringWithFormat:@"Incorrect value type for %@", [m address]]]; + } + + // Pass validation if needed (relatively small number of validators should ensure this is performant) + // Validation functions will print their own error messages, so we can just exit directly if they fail + for (NSString *validatorKey in [validators allKeys]) + if ([[endpoint addressTemplate] hasPrefix:validatorKey] && ![validators objectForKey:validatorKey](args, value)) + return; + + // Call handler found for address with paramaterized arguments and value of matching type + endpoint.handler(args, value); } - else if ([[address objectAtIndex:5] isEqualToString:@"crop-bottom"]) + else { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetCropBottom(value); + // Given the last element we could match, show possible future elements that they might have meant + if ([[finalLevel allKeys] count] > 0) + { + NSArray *possibleEndpoints = [self getEndpointsForNode:finalLevel]; + if ([possibleEndpoints count] < 15) + { + NSMutableArray *possibleAddresses = [[NSMutableArray alloc] init]; + for (OSCEndpoint *endpoint in possibleEndpoints) + [possibleAddresses addObject:[endpoint addressTemplate]]; + + return [appDel logMessage:[NSString stringWithFormat:@"OSC endpoint not implemented for %@, maybe you meant to call one of these methods: %@", [m address], [possibleAddresses componentsJoinedByString:@", "]]]; + } + } + + [appDel logMessage:[NSString stringWithFormat:@"OSC endpoint not implemented for %@, refer to the help menu for a list of available addresses", [m address]]]; } +} + +- (IBMDSwitcherKeyLumaParameters *) getUSKLumaParams:(int)t +{ + IBMDSwitcherKey* key = [appDel keyers][t-1]; + IBMDSwitcherKeyLumaParameters* lumaParams; + key->QueryInterface(IID_IBMDSwitcherKeyLumaParameters, (void**)&lumaParams); + return lumaParams; +} + +- (IBMDSwitcherKeyChromaParameters *) getUSKChromaParams:(int)t +{ + IBMDSwitcherKey* key = [appDel keyers][t-1]; + IBMDSwitcherKeyChromaParameters* chromaParams; + key->QueryInterface(IID_IBMDSwitcherKeyChromaParameters, (void**)&chromaParams); + return chromaParams; +} + +- (IBMDSwitcherKeyDVEParameters *) getUSKDVEParams:(int)t +{ + IBMDSwitcherKey* key = [appDel keyers][t-1]; + IBMDSwitcherKeyDVEParameters* dveParams; + key->QueryInterface(IID_IBMDSwitcherKeyDVEParameters, (void**)&dveParams); + return dveParams; +} + +- (void) changeTransitionSelection:(int)t select:(bool) select +{ + uint32_t currentTransitionSelection; + [appDel switcherTransitionParameters]->GetNextTransitionSelection(¤tTransitionSelection); - else if ([[address objectAtIndex:5] isEqualToString:@"crop-left"]) - { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetCropLeft(value); - } + uint32_t transitionSelections[5] = { bmdSwitcherTransitionSelectionBackground, bmdSwitcherTransitionSelectionKey1, bmdSwitcherTransitionSelectionKey2, bmdSwitcherTransitionSelectionKey3, bmdSwitcherTransitionSelectionKey4 }; + uint32_t requestedTransitionSelection = transitionSelections[t]; - else if ([[address objectAtIndex:5] isEqualToString:@"crop-right"]) + if (select) { - float value = [m calculateFloatValue]; - [appDel mSuperSourceBoxes][box]->SetCropRight(value); + [appDel switcherTransitionParameters]->SetNextTransitionSelection(currentTransitionSelection | requestedTransitionSelection); } - - else if ([[address objectAtIndex:5] isEqualToString:@"crop-reset"]) + else { - [appDel mSuperSourceBoxes][box]->ResetCrop(); + // If we are attempting to deselect the only bit set, then default to setting TransitionSelectionBackground + if ((currentTransitionSelection & ~requestedTransitionSelection) == 0) + [appDel switcherTransitionParameters]->SetNextTransitionSelection(bmdSwitcherTransitionSelectionBackground); + else + [appDel switcherTransitionParameters]->SetNextTransitionSelection(currentTransitionSelection & ~requestedTransitionSelection); } - - else - [appDel logMessage:@"You must specify a super-source box command of 'enabled', 'source', 'x', 'y', 'size', 'cropped', 'crop-top', 'crop-bottom', 'crop-left', 'crop-right', or 'crop-reset'"]; } -- (void) setHyperDeckTime:(int)hyperdeckId time:(NSString *)timeString clip:(BOOL)clipTime +- (void) setHyperDeckTime:(long long)hyperdeckId time:(NSString *)timeString clip:(BOOL)clipTime { NSArray *timeComponents = [timeString componentsSeparatedByString:@":"]; uint16_t hour = 0; diff --git a/atemOSC/SettingsWindow.h b/atemOSC/SettingsWindow.h index bd0ebd6..85d6c4e 100644 --- a/atemOSC/SettingsWindow.h +++ b/atemOSC/SettingsWindow.h @@ -20,13 +20,19 @@ IBOutlet NSTextField* mAddressTextField; IBOutlet NSTextField* mSwitcherNameLabel; + IBOutlet NSTextField* mLogLabel; AppDelegate* appDel; + IBOutlet NSMenuItem* logMenuOption; + IBOutlet NSMenuItem *addressesMenuOption; } - (void)loadSettingsFromPreferences; - (void)showSwitcherConnected:(NSString *)switcherName; - (void)showSwitcherDisconnected; - (NSString *)switcherAddress; +- (void)updateLogLabel:(NSString *)message; +- (IBAction)viewLogButtonPressed:(id)sender; +- (IBAction)viewAddressesButtonPressed:(id)sender; @end diff --git a/atemOSC/SettingsWindow.mm b/atemOSC/SettingsWindow.mm index 6a63c39..8c4b54f 100644 --- a/atemOSC/SettingsWindow.mm +++ b/atemOSC/SettingsWindow.mm @@ -110,6 +110,7 @@ - (void)controlTextDidEndEditing:(NSNotification *)aNotification } } + // Only update if the input is valid and actually changed if (validInput) [appDel portChanged:[mIncomingPortTextField intValue] out:[mOutgoingPortTextField intValue] ip:[mOscDeviceTextField stringValue]]; @@ -145,4 +146,20 @@ - (NSString *)switcherAddress return [mAddressTextField stringValue]; } +- (void)updateLogLabel:(NSString *)message +{ + [mLogLabel setStringValue:message]; +} + +- (IBAction)viewLogButtonPressed:(id)sender +{ + NSInteger index = [logMenuOption.menu indexOfItem:logMenuOption]; + [logMenuOption.menu performActionForItemAtIndex:index]; +} + +- (IBAction)viewAddressesButtonPressed:(id)sender { + NSInteger index = [addressesMenuOption.menu indexOfItem:addressesMenuOption]; + [addressesMenuOption.menu performActionForItemAtIndex:index]; +} + @end diff --git a/atemOSC/Utilities.h b/atemOSC/Utilities.h index 0ac88ee..9fa27bf 100644 --- a/atemOSC/Utilities.h +++ b/atemOSC/Utilities.h @@ -19,5 +19,6 @@ extern NSString* getNameOfMacro(uint32_t index); extern NSString* getDescriptionOfMacro(uint32_t index); extern void activateChannel(int channel, bool program); extern bool stringIsNumber(NSString * str); +extern NSArray *mapObjectsUsingBlock(NSArray *array, id (^block)(id obj, NSUInteger idx)); #endif /* Utilities_hpp */ diff --git a/atemOSC/Utilities.mm b/atemOSC/Utilities.mm index e698f2b..4530f0e 100644 --- a/atemOSC/Utilities.mm +++ b/atemOSC/Utilities.mm @@ -179,3 +179,11 @@ bool stringIsNumber(NSString * str) NSCharacterSet* notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; return [str rangeOfCharacterFromSet:notDigits].location == NSNotFound; } + +NSArray *mapObjectsUsingBlock(NSArray *array, id (^block)(id obj, NSUInteger idx)) { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[array count]]; + [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [result addObject:block(obj, idx)]; + }]; + return result; +} diff --git a/atemOSC/atemOSC.xcodeproj/project.pbxproj b/atemOSC/atemOSC.xcodeproj/project.pbxproj index 0d469aa..25119ad 100644 --- a/atemOSC/atemOSC.xcodeproj/project.pbxproj +++ b/atemOSC/atemOSC.xcodeproj/project.pbxproj @@ -16,11 +16,12 @@ 335AB1C115703EF4003EA2D2 /* VVBasics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 335AB1BD15703EF4003EA2D2 /* VVBasics.framework */; }; 335AB1C315703EFA003EA2D2 /* VVOSC.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335AB1BB15703EF4003EA2D2 /* VVOSC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 335AB1C415703EFA003EA2D2 /* VVBasics.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 335AB1BD15703EF4003EA2D2 /* VVBasics.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 335B268D1573A16300F72574 /* BMDSwitcherAPIDispatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 335B268C1573A16300F72574 /* BMDSwitcherAPIDispatch.cpp */; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; A91A812F1FAEA2DC00983D66 /* Utilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = A91A812D1FAEA2DC00983D66 /* Utilities.mm */; }; + A91EFE6B23D7519E00590AD0 /* OSCEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = A91EFE6923D7519E00590AD0 /* OSCEndpoint.m */; }; + A91EFE7123D7559700590AD0 /* BMDSwitcherAPIDispatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A91EFE7023D7559700590AD0 /* BMDSwitcherAPIDispatch.cpp */; }; A92D308F1F832BA900CCAC68 /* FeedbackMonitors.mm in Sources */ = {isa = PBXBuildFile; fileRef = A92D308D1F832BA900CCAC68 /* FeedbackMonitors.mm */; }; A98839421F8D913800F91789 /* OSCAddressPanel.mm in Sources */ = {isa = PBXBuildFile; fileRef = A98839411F8D913800F91789 /* OSCAddressPanel.mm */; }; A98839481F8E307100F91789 /* SettingsWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = A98839471F8E307100F91789 /* SettingsWindow.mm */; }; @@ -56,12 +57,14 @@ 333E3044161395CF0002287B /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; 335AB1BB15703EF4003EA2D2 /* VVOSC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = VVOSC.framework; sourceTree = ""; }; 335AB1BD15703EF4003EA2D2 /* VVBasics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = VVBasics.framework; sourceTree = ""; }; - 335B268B1573A16300F72574 /* BMDSwitcherAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BMDSwitcherAPI.h; sourceTree = ""; }; - 335B268C1573A16300F72574 /* BMDSwitcherAPIDispatch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BMDSwitcherAPIDispatch.cpp; sourceTree = ""; }; 8D1107310486CEB800E47090 /* atemOSC-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "atemOSC-Info.plist"; sourceTree = ""; }; 8D1107320486CEB800E47090 /* atemOSC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = atemOSC.app; sourceTree = BUILT_PRODUCTS_DIR; }; A91A812D1FAEA2DC00983D66 /* Utilities.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Utilities.mm; sourceTree = ""; }; A91A812E1FAEA2DC00983D66 /* Utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + A91EFE6923D7519E00590AD0 /* OSCEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSCEndpoint.m; sourceTree = ""; }; + A91EFE6A23D7519E00590AD0 /* OSCEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSCEndpoint.h; sourceTree = ""; }; + A91EFE6F23D7559700590AD0 /* BMDSwitcherAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BMDSwitcherAPI.h; sourceTree = ""; }; + A91EFE7023D7559700590AD0 /* BMDSwitcherAPIDispatch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BMDSwitcherAPIDispatch.cpp; sourceTree = ""; }; A92D308C1F832B9200CCAC68 /* FeedbackMonitors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FeedbackMonitors.h; sourceTree = ""; }; A92D308D1F832BA900CCAC68 /* FeedbackMonitors.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FeedbackMonitors.mm; sourceTree = ""; }; A98839401F8D913800F91789 /* OSCAddressPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSCAddressPanel.h; sourceTree = ""; }; @@ -102,6 +105,8 @@ A9F790691F89B83C003ACBCF /* OSCReceiver.mm */, A91A812E1FAEA2DC00983D66 /* Utilities.h */, A91A812D1FAEA2DC00983D66 /* Utilities.mm */, + A91EFE6A23D7519E00590AD0 /* OSCEndpoint.h */, + A91EFE6923D7519E00590AD0 /* OSCEndpoint.m */, ); name = Classes; sourceTree = ""; @@ -150,8 +155,8 @@ 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( - 335B268B1573A16300F72574 /* BMDSwitcherAPI.h */, - 335B268C1573A16300F72574 /* BMDSwitcherAPIDispatch.cpp */, + A91EFE6F23D7559700590AD0 /* BMDSwitcherAPI.h */, + A91EFE7023D7559700590AD0 /* BMDSwitcherAPIDispatch.cpp */, 29B97316FDCFA39411CA2CEA /* main.m */, ); name = "Other Sources"; @@ -254,11 +259,12 @@ A98839481F8E307100F91789 /* SettingsWindow.mm in Sources */, 8D11072D0486CEB800E47090 /* main.m in Sources */, A91A812F1FAEA2DC00983D66 /* Utilities.mm in Sources */, + A91EFE6B23D7519E00590AD0 /* OSCEndpoint.m in Sources */, A98839421F8D913800F91789 /* OSCAddressPanel.mm in Sources */, A9F7906A1F89B83D003ACBCF /* OSCReceiver.mm in Sources */, 256AC3DA0F4B6AC300CF3369 /* AppDelegate.mm in Sources */, A92D308F1F832BA900CCAC68 /* FeedbackMonitors.mm in Sources */, - 335B268D1573A16300F72574 /* BMDSwitcherAPIDispatch.cpp in Sources */, + A91EFE7123D7559700590AD0 /* BMDSwitcherAPIDispatch.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -306,6 +312,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @executable_path/../Frameworks"; MACH_O_TYPE = mh_execute; MACOSX_DEPLOYMENT_TARGET = 10.9; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = "cc.buechele.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = atemOSC; SDKROOT = macosx; @@ -332,6 +339,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @executable_path/../Frameworks"; MACH_O_TYPE = mh_execute; MACOSX_DEPLOYMENT_TARGET = 10.9; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = "cc.buechele.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = atemOSC; SDKROOT = macosx; diff --git a/atemOSC/en.lproj/InfoPlist.strings b/atemOSC/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/atemOSC/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/atemOSC_3.png b/atemOSC_3.png new file mode 100644 index 0000000..50b2de6 Binary files /dev/null and b/atemOSC_3.png differ