AppleScript or OSC to play next, previous or specific slice in current playing song.

232 views
Skip to first unread message

nizer

unread,
Jan 6, 2021, 9:18:22 AM1/6/21
to QLab
Working with building a ZOOM/QLab platform for a dance school. Love the Syphon Virtual Webcam integration.

I was hoping to use AppleScript or OSC to play next, previous or specific slice in the current playing song.

The song would change all the time, but would contain various slices, I was hoping to use a cart cuelist trigger from a Qlab Remote APP on a iPad with <Pause>,<Next>,<Previous>,<Play>.

Any guidance would be helpful.

Rich Walsh

unread,
Jan 8, 2021, 11:03:52 AM1/8/21
to ql...@googlegroups.com
This was really fiddly – partly because I'm a bit rusty after 9 months of stagnation, but also because the "action elapsed" property (still, after years) returns unexpected values if working with rates ≠ 1. I can't figure out the pattern at all – you'd think these two things would evaluate the same but they mostly don't:

log (action elapsed of selectedCue) - (pre wait of selectedCue)
log (percent action elapsed of selectedCue) * (duration of selectedCue) * (rate of selectedCue)

You get different answers if the cue is playing, previewing, loaded to time or clicked in the waveform… I really don't understand it! The second form seems to most consistently report something closest to what I need for the loadActionAt method no matter how you abuse the cue – so I've used that.

Based on an existing "Nudge selected cue(s) forward (without changing their running state)" routine, this _should_ skip through slices in the same way:

-- ###BETA: UNTESTED###
-- Only works properly when run as a separate process!
-- NB: the OSC command will NOT WORK if the workspace has a Passcode

set userMode to item 1 of {"start of previous slice", "start of next slice"} -- Choose direction for skipping

tell application id "com.figure53.QLab.4" to tell front workspace
repeat with eachCue in (selected as list)
try -- Skip over cues that don't take slices


set sliceMarkers to slice markers of eachCue
if sliceMarkers is {} then error -- Abort if no slices


if running of eachCue is true then
pause eachCue
set startFlag to true
else
set startFlag to false
end if


set currentTime to (percent action elapsed of eachCue) * (duration of eachCue) * (rate of eachCue)
-- ###FIXME### As of 4.6.6, "action elapsed" reports differently between clicking in waveform and loading to time when rate ≠ 1


-- Testing revealed rounding errors of the order of 1.0E-12 when comparing times so they are truncated to milliseconds for comparison
set roundedCurrent to my roundToNearest(currentTime, 1.0E-3)


set sliceMarkers to slice markers of eachCue
set sliceTimes to {0}
repeat with eachMarker in sliceMarkers
set end of sliceTimes to time of eachMarker
end repeat
set end of sliceTimes to duration of eachCue
set countSliceTimes to count sliceTimes


repeat with i from 1 to countSliceTimes
if (my roundToNearest(item i of sliceTimes, 1.0E-3)) - roundedCurrent > 0 then
exit repeat -- We have found the next slice start
end if
end repeat


if userMode is "start of previous slice" then
if i ≤ 2 then -- Wrap around if in the first slice
set loadTime to item -2 of sliceTimes
else
set loadTime to item (i - 2) of sliceTimes
end if
else
if i is countSliceTimes then -- Wrap around if in the last slice
set loadTime to 0
else
set loadTime to item i of sliceTimes
end if
end if


set eachID to uniqueID of eachCue
tell me to do shell script "echo '/cue_id/" & eachID & "/loadActionAt " & loadTime & "' | nc -u -w 0 localhost 53535"


if startFlag is true then
start eachCue
end if


end try
end repeat
end tell

-- Subroutines

(* === NUMBER WRANGLING === *)

on roundToNearest(theNumber, toNearest) -- [Shared subroutine]
return (round (theNumber / toNearest) rounding as taught in school) * toNearest
end roundToNearest

I have only partially tested it and expect it to still fail in some situations that I haven't tried…

Building on that, this will allow to you to choose a slice from the selected cue:

-- ###BETA: UNTESTED###
-- Only works properly when run as a separate process!
-- NB: the OSC command will NOT WORK if the workspace has a Passcode
-- ###FIXME### The times displayed in the dialog are as shown in the waveform, not in the Action column (ie: confusing if rate ≠ 1)

-- Declarations

global dialogTitle
set dialogTitle to "Choose a slice"

-- Main routine

tell application id "com.figure53.QLab.4" to tell front workspace


try -- This protects against no selection (can't get last item of (selected as list))


set selectedCue to last item of (selected as list)


set sliceMarkers to slice markers of selectedCue -- This will throw an error if the cue doesn't take slices
if sliceMarkers is {} then error -- Abort if no slices


if running of selectedCue is true then
pause selectedCue
set startFlag to true
else
set startFlag to false
end if


set currentTime to (percent action elapsed of selectedCue) * (duration of selectedCue) * (rate of selectedCue)
-- ###FIXME### As of 4.6.6, "action elapsed" reports differently between clicking in waveform and loading to time when rate ≠ 1


-- Testing revealed rounding errors of the order of 1.0E-12 when comparing times so they are truncated to milliseconds for comparison
set roundedCurrent to my roundToNearest(currentTime, 1.0E-3)


set sliceMarkers to slice markers of selectedCue
set sliceTimes to {0}
repeat with eachMarker in sliceMarkers
set end of sliceTimes to time of eachMarker
end repeat
set countSliceTimes to count sliceTimes


set readableTimes to {}
repeat with eachTime in sliceTimes
set end of readableTimes to my makeHHMMSSsss(eachTime)
end repeat


repeat with i from 1 to countSliceTimes
if (my roundToNearest(item i of sliceTimes, 1.0E-3)) - roundedCurrent > 0 then
exit repeat -- We have found the next slice start
end if
end repeat


set loadTimeReadable to my pickFromListCustomDefault(readableTimes, "Choose which slice to jump to:", i - 1)


repeat with i from 1 to countSliceTimes
if item i of readableTimes is loadTimeReadable then
set loadTime to item i of sliceTimes
exit repeat
end if
end repeat


set eachID to uniqueID of selectedCue
tell me to do shell script "echo '/cue_id/" & eachID & "/loadActionAt " & loadTime & "' | nc -u -w 0 localhost 53535"


if startFlag is true then
start selectedCue
end if


end try


end tell

-- Subroutines

(* === INPUT === *)

on pickFromListCustomDefault(theChoice, thePrompt, theDefault) -- [Shared subroutine]
tell application id "com.figure53.QLab.4"
choose from list theChoice with prompt thePrompt with title dialogTitle default items item theDefault of theChoice
if result is not false then
return item 1 of result
else
error number -128
end if
end tell
end pickFromListCustomDefault

(* === TIME === *)

on makeHHMMSSsss(howLong) -- [Shared subroutine]
set howManyHours to howLong div 3600
if howManyHours is 0 then
set hourString to ""
else
set hourString to my padNumber(howManyHours, 2) & ":"
end if
set howManyMinutes to (howLong mod 3600) div 60
set minuteString to my padNumber(howManyMinutes, 2)
set howManySeconds to howLong mod 60 div 1
set secondString to my padNumber(howManySeconds, 2)
set howManyFractionalSeconds to howLong mod 1
set howManyRoundedSeconds to round 1000 * howManyFractionalSeconds rounding as taught in school
set fractionString to my padNumber(howManyRoundedSeconds, 3)
return hourString & minuteString & ":" & secondString & "." & fractionString
end makeHHMMSSsss

(* === NUMBER WRANGLING === *)

on roundToNearest(theNumber, toNearest) -- [Shared subroutine]
return (round (theNumber / toNearest) rounding as taught in school) * toNearest
end roundToNearest

(* === TEXT WRANGLING === *)

on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
set paddedNumber to theNumber as text
repeat while (count paddedNumber) < minimumDigits
set paddedNumber to "0" & paddedNumber
end repeat
return paddedNumber
end padNumber

You'll have to do your own work to change these from working on selections to "last item of active cues whose q type is "Audio"" – or something like that…

Rich

Mark Nizer

unread,
Jan 9, 2021, 10:51:08 AM1/9/21
to ql...@googlegroups.com
WOW. This is above and beyond my expectations. Works great. I will explore them more closely. Thank you sooo much.

The "log (action elapsed of selectedCue) - (pre wait of selectedCue)
log (percent action elapsed of selectedCue) * (duration of selectedCue) * (rate of selectedCue)"

What does that do? Where do I put it to see the result. Again, thank you so much for your help. 

PS: I have been rusting away too. I have my first gig in 5 months in February!!!!!!!!!



Active Media Group
State of the Art Internet and Multimedia
Mark Neisser

--
Contact support anytime: sup...@figure53.com
Follow QLab on Twitter: https://twitter.com/QLabApp
User Group Code of Conduct: https://qlab.app/code-of-conduct/
---
You received this message because you are subscribed to a topic in the Google Groups "QLab" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/qlab/OThBIhapJhg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to qlab+uns...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/qlab/CAB80DD5-BBE5-4046-A175-7B7B1A913C83%40mac.com.

Rich Walsh

unread,
Jan 14, 2021, 7:39:56 AM1/14/21
to ql...@googlegroups.com
On 9 Jan 2021, at 15:51, Mark Nizer <freeun...@gmail.com> wrote:

WOW. This is above and beyond my expectations. Works great. I will explore them more closely. Thank you sooo much.

The "log (action elapsed of selectedCue) - (pre wait of selectedCue)
log (percent action elapsed of selectedCue) * (duration of selectedCue) * (rate of selectedCue)"

What does that do? Where do I put it to see the result. Again, thank you so much for your help. 

You'd run it in Script Editor, wrapped up in enough code to get a selectedCue. I've built the attached workspace to demonstrate the issue more clearly – it's been around for years…

Incidentally, if you want the slice tools I posted previously to work on "current audio cue" you'd use this form to specify which cues to process (replace the appropriate line(s)):

set selectedCue to last item of (cues whose running is true and q type is "Audio")

Rich

Getting elapsed time.zip
Reply all
Reply to author
Forward
0 new messages