From be3bd72fb1a001e42e477aa3e0487f78eae0154b Mon Sep 17 00:00:00 2001 From: trinitou Date: Tue, 14 Feb 2023 09:29:39 +0100 Subject: [PATCH] add files --- .gitignore | 1 + Readme.md | 23 +++++++ doc/help.css | 3 + doc/help.md | 31 ++++++++++ generate_release.ps1 | 28 +++++++++ src/launch xjadeo.bat | 1 + src/xJadeo Video Sync.control.js | 102 +++++++++++++++++++++++++++++++ 7 files changed, 189 insertions(+) create mode 100644 .gitignore create mode 100644 Readme.md create mode 100644 doc/help.css create mode 100644 doc/help.md create mode 100644 generate_release.ps1 create mode 100644 src/launch xjadeo.bat create mode 100644 src/xJadeo Video Sync.control.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc76761 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..4f1b0c0 --- /dev/null +++ b/Readme.md @@ -0,0 +1,23 @@ +# Video playback for Bitwig Studio using xjadeo. + +A lightweight solution for video playback with [Bitwig Studio](https://www.bitwig.com/de/overview/) using [xjadeo](https://xjadeo.sourceforge.net). A video file can be added per Bitwig project which then will be played back in sync with Bitwig's transport. Also the video will be saved and opened together with the project automatically. + +The implementation makes use of xjadeo's OSC remote control capabilities which are documented [here](https://xjadeo.sourceforge.net/osc.html). + +## Setup + +1. Put the .js into the Bitwig controller script folder +2. Add the controller script in Bitwig Studio + - via *Dashboard -> 'Settings' -> 'Controllers' -> '+ Add Controller'* + - select hardware vendor: 'xjadeo' + - select product: 'xJadeo Video Sync' + - click *'Add'* +3. Click the "?" icon and follow the setup instructions in the help HTML + +## Build + +Nothing to build for the controller script, but the help documentation HTML is generated from the [help.md](./doc/help.md) using the [generate_HTML.ps1](./doc/generate_HTML.ps1) script which requires Powershell 7 to work. + +## Contribution + +Please feel free to create a pull request if you want to add or change something! Also have a look into open issues if you want so. diff --git a/doc/help.css b/doc/help.css new file mode 100644 index 0000000..e5dd04f --- /dev/null +++ b/doc/help.css @@ -0,0 +1,3 @@ +html { + font: 14px "Lucida Grande", Lucida, Verdana, sans-serif; +} diff --git a/doc/help.md b/doc/help.md new file mode 100644 index 0000000..222c573 --- /dev/null +++ b/doc/help.md @@ -0,0 +1,31 @@ + + xjadeo Video Sync + + + +# xJadeo Video Synchronization + +## Setup + +- **Download and install xjadeo**, e.g. from [here](https://xjadeo.sourceforge.net/download.html) +- **Launch the *'launch xJadeo.bat'* script**. This opens the xjadeo window. +- In Bitwig Studio, connect the video file for playback like this: + - Open a project. + - In the *Studio I/O panel*, unfold the *xJadeo Video Sync* settings. + - Enter the absolute video file path under *Path*. + - Enter the original frame rate of the video under 'Frame rate'. + +## Features +- If you save the project and open it later, the video will be opened again together with the project. +- You can easily switch Bitwig project tabs with different videos and the xJadeo window will be udpated on the fly! +- There is a *'Keep on top'* checkbox which will ensure that the video window stays in front of Bitwig Studio. (default: on) +- The *'Flush!'* button in the settings will (re-)send all data to the xJadeo window. This is helpful if xJadeo was opened after Bitwig Studio. + +## Tipps & tricks + +- 'Where to quickly find original video frame?' + - When using the *'launch xJadeo.bat'* script, a console window will open as well. Look into the log text! +- How to hear the original sound of the video file in sync with the video? + - If Bitwig supports the file format, you can drag it into the project so that. + - Place it into the Arranger at position 1.1.1.00 + - Make sure that time-stretching is off for that audio clip (*Mode* set to *'Raw'*) diff --git a/generate_release.ps1 b/generate_release.ps1 new file mode 100644 index 0000000..874daad --- /dev/null +++ b/generate_release.ps1 @@ -0,0 +1,28 @@ +$version = "0.1" + +$targetTempDir = ".\target\tmp" +New-Item -ItemType Directory -Force -Path $targetTempDir + +$docSourceDir = ".\doc" +$md = ConvertFrom-Markdown -Path "$docSourceDir\help.md" +if($md) { + $md.Html | Out-File -Encoding utf8 "$targetTempDir\xJadeo Video Sync.html" + $helpCssFileName = "help.css" + $cssTargetDir = "$targetTempDir\doc" + New-Item -ItemType Directory -Force -Path "$cssTargetDir" + Copy-Item -Path "$docSourceDir\$helpCssFileName" -Destination "$cssTargetDir\$helpCssFileName" +} + +$sourceDir = ".\src" +$controlScriptPath = "xJadeo Video Sync.control.js" +Copy-Item -Path "$sourceDir\$controlScriptPath" -Destination "$targetTempDir\$controlScriptPath" +$launchScriptPath = "launch xjadeo.bat" +Copy-Item -Path "$sourceDir\$launchScriptPath" -Destination "$targetTempDir\$launchScriptPath" + +$targetZipPath = "$PSScriptRoot\target\xJadeo_Video_Sync_" + ($version -replace '\.', '_') + ".zip" +$compress = @{ + Path = "$targetTempDir\*" + CompressionLevel = "Fastest" + DestinationPath = $targetZipPath + } +Compress-Archive @compress -Force diff --git a/src/launch xjadeo.bat b/src/launch xjadeo.bat new file mode 100644 index 0000000..638c7c2 --- /dev/null +++ b/src/launch xjadeo.bat @@ -0,0 +1 @@ +"C:\Program Files\xjadeo\xjadeo.exe" -O 12345 diff --git a/src/xJadeo Video Sync.control.js b/src/xJadeo Video Sync.control.js new file mode 100644 index 0000000..09e6f0e --- /dev/null +++ b/src/xJadeo Video Sync.control.js @@ -0,0 +1,102 @@ +loadAPI(17); + +host.setShouldFailOnDeprecatedUse(true); + +host.defineController("xjadeo", "xJadeo Video Sync", "0.1", "295c10cb-b8d6-416b-9be5-dc2375936ac0", "Trinitou"); + +var oscConnection; + +var lastOnTopState; +function invalidateLastOnTopState() { + lastOnTopState = null; +} +function updateOnTop(onTop) { + if(onTop == lastOnTopState) + return; + oscConnection.sendMessage("/jadeo/cmd", "window ontop " + (onTop ? "on" : "off")); + lastOnTopState = onTop; + host.println("Sent 'Keep on top' state: " + (onTop ? "on" : "off")); +} + +var lastVideoPath; +function invalidateLastVideoPath() { + lastVideoPath = null; +} +function updateVideo(path) { + if (path === lastVideoPath) + return false; + oscConnection.sendMessage("/jadeo/load", path); // sending an empty path will close current video file + host.println("Sent video path: '" + path + "'"); + lastVideoPath = path; + return true; +} + +var lastFrame; +function invalidateLastFrame() { + lastFrame = -1; +} +function updateFrame(frame) { + if (oscConnection == null) { + invalidateLastFrame(); + return; + } + if (frame != lastFrame) { + oscConnection.sendMessage("/jadeo/seek", frame); + lastFrame = frame; + // host.println("Sent frame: " + lastFrame); + } +} + +function invalidateAll() { + invalidateLastOnTopState(); + invalidateLastVideoPath(); + invalidateLastFrame(); +} + +var onTopSetting; +var pathSetting; +var frameRateSetting; +var pos; + +function init() { + oscConnection = host.getOscModule().connectToUdpServer("localhost", 12345, null); + + let prefs = host.getPreferences(); + + onTopSetting = prefs.getBooleanSetting("Keep on top", "Video window", true); + onTopSetting.markInterested(); + + let docState = host.getDocumentState(); + + pathSetting = docState.getStringSetting("Path", "File", 256, ""); + pathSetting.markInterested(); + + frameRateSetting = docState.getNumberSetting("FPS", "File", 24, 60, 0.01, "", 24); + frameRateSetting.markInterested(); + + // add flush command to both the preferences and document state for easy access: + let settings = [prefs, docState]; + for(var i = 0; i < settings.length; i++) { + settings[i].getSignalSetting("Flush", "Video window", "Flush!").addSignalObserver(function () { + invalidateAll(); + }); + } + + pos = host.createTransport().playPositionInSeconds(); + pos.markInterested(); + + invalidateAll(); + + println("xJadeo Connect initialized!"); +} + +function flush() { + updateOnTop(onTopSetting.get()); + if (updateVideo(pathSetting.get())) + invalidateLastFrame(); + updateFrame(Math.floor(pos.get() * frameRateSetting.getRaw())); +} + +function exit() { + updateVideo(""); +}