| sidebar_position | 6 |
|---|
A non-blocking workspace panel for creating named markers during playback with a 4×4 button grid.
Click for details
The Marker Creator script in this repository is a working example demonstrating:
FrameworkService+WindowClassregistration for non-blocking workspace panelsHost.Objects.registerObject()for binding a script controller to a WindowClassskin.xmlwithWindowClasses,View,Button,TriggerView,TextBox,Slider,ValueBox,SelectBox,Toggle,DialogGroup,Label,Space, andImageresourcesView+Button+TriggerViewoverlay pattern for simultaneous left-click (marker creation) and right-click (rename dialog) handling- SVG icons on toolbar buttons (
Undo,Redo,Save,Clear,Auto #) TriggerView+Invokerpattern for right-click rename dialog handling on individual buttonsHost.GUI.Commands.interpretCommand("Marker", "Insert Named")with attributesHost.GUI.Commands.interpretCommand("Edit", "Undo")/"Redo"for undo/redoHost.GUI.runDialog()for the secondary rename popup form.Host.GUI.ask()for confirmation dialogsCCL:FileSelectorfor native save dialogs- Preset save/load via
Host.IO.createTextFile()/Host.IO.openTextFile() Host.Objects.getObjectByUrl()to access the TransportPanel for cursor-based marker offset- Dynamic frame rate scaling for offset calculation (23.976–60 fps)
- Dynamic control dimming via
param.enabled: empty grid buttons are dimmed, and the Frame Rate selector dims when offset unit is set to ms
Source Code Files:
classfactory.xmlmetainfo.xmlmain.jsskin/skin.xmlskin/images/undo.svgskin/images/redo.svgskin/images/save.svgskin/images/clear.svgskin/images/number.svg
Key features:
- 4×4 grid of 16 named marker buttons with dynamic names via
titlename - Left-click creates a marker at the current cursor position with the slot's name
- Right-click opens a rename dialog for the slot
- Preset save/load system with native file dialogs
- Undo/Redo buttons
- Auto-numbering toggle for creating numbered duplicate markers
- Offset slider to compensate for reaction delay (ms or frames)
- 13 selectable frame rates for frame-based offset (23.976–60 fps)
The Marker Creator is a non-blocking workspace panel that provides a 4×4 grid of buttons, each associated with a text name. During playback, click a button to instantly create a named marker at the cursor position. Right-click any button to rename it. The panel uses the FrameworkService + WindowClass pattern, so it runs alongside Studio Pro's native UI without blocking it.
Unlike typical EditTask scripts that use modal dialogs (context.runDialog()), the Marker Creator is registered as a FrameworkService with a WindowClass form:
classfactory.xml:
<ScriptClass
classID="{B1A2B3C4-0003-4000-8000-000000000030}"
category="FrameworkService"
name="Marker Creator Service"
sourceFile="main.js"
functionName="createInstance"/>The controller registers itself as a named host object in initialize():
Host.Objects.registerObject(this, "MarkerCreatorController")The skin.xml defines a <WindowClass> that references this controller:
<WindowClass name="MarkerCreatorPanel"
title="Marker Creator"
controller="MarkerCreatorController"
form.name="MarkerCreatorForm"
command.category="Marker" command.name="Marker Creator"The form is rendered as a non-blocking workspace window toggleable from Find Command: Marker Creator. Can also be assigned to a keyboard shortcut under command category "Marker > Marker Creator".
| Control | Type | Description |
|---|---|---|
| Undo | Button (SVG) | Executes Edit > Undo to revert the last marker creation |
| Redo | Button (SVG) | Executes Edit > Redo |
| Preset List | SelectBox | Select and load a saved preset. "(No Preset)" clears all names |
| Save | Button (SVG) | Opens a save dialog to export all 16 names as a .markerslots file |
| Clear | Button (SVG) | Clears all 16 slot names. Shows a confirmation dialog before clearing |
| Auto # | Toggle (SVG) | When enabled, repeatedly using the same slot appends a number ("Hit 2", "Hit 3") |
16 buttons, each bound to a StringParam via titlename. The param's value is set by updateBtnStates():
- Slot has a name: Button shows the name, button is enabled, click creates a marker
- Slot is empty: Button shows the slot number (1–16), button is disabled
Right-click any button to open the rename dialog. The right-click is handled by a TriggerView overlaid on the button via size positioning.
| Control | Type | Description |
|---|---|---|
| Offset Slider | Slider | 0 (no offset) to 1000 ms / selected frame rate (max offset). Bargraph option shows fill line |
| ValueBox | ValueBox | Displays the offset as a negative value (e.g., -120) to indicate backward placement |
| Unit Selector | SelectBox | Choose between ms and Frames |
| Frame Rate | SelectBox | 13 frame rates matching Studio Pro's Session Setup: 23.976, 24, 24.975, 25, 29.97 dfps, 29.97, 30 dfps, 30, 50, 59.94 dfps, 59.94, 60 dfps, 60 fps |
A modal dialog (Host.GUI.runDialog) with a single edit box. Opens on right-click. Pre-filled with the current name. Uses firstfocus="RenameText" for immediate typing.
The offset compensates for reaction delay by placing the marker before the cursor position:
- Read the current cursor position from the TransportPanel via
Host.Objects.getObjectByUrl() - Move the cursor backward by the offset amount
- Create the marker at the new cursor position
- Restore the cursor to its original position
Note: Moving the cursor during active playback will cause a brief audible and visual stutter. This is inherent to the cursor-move approach — use offset at 0 for no-stutter operation.
var tp = Host.Objects.getObjectByUrl("://hostapp/DocumentManager/ActiveDocument/Environment/TransportPanel")
var cursor = tp ? tp.findParameter("primaryTime") : null
if (cursor) {
var curPos = cursor.value
cursor.setValue(curPos + offsetSec, true) // move backward
}
// create marker...
cursor.setValue(curPos - offsetSec, true) // restoreThe offset amount is converted to seconds based on the selected unit:
| Unit | Formula | Example |
|---|---|---|
| ms | offsetVal / 1000 |
500 ms = 0.5 seconds |
| Frames | offsetVal / fps |
15 frames @ 30fps = 0.5 seconds |
The slider range is 0–1000. The ValueBox displays a negated value (-500 for 500ms offset). When switching between ms and frames, the value rescales so max slider always equals ~1 second of compensation.
The slider and ValueBox use separate parameters to allow the slider to increase left→right while the ValueBox shows a negative value:
// Slider param: 0 to 1000
this.OffsetSlider = this.paramList.addInteger(0, 1000, "OffsetSlider")
// ValueBox param: -1000 to 0
this.OffsetMs = this.paramList.addInteger(-1000, 0, "OffsetMs")
// Sync in paramChanged:
if (param && param.name === "OffsetSlider") {
var sv = this.OffsetSlider.value
var isFrames = this.OffsetUnit && this.OffsetUnit.value === 1
if (isFrames)
this.OffsetMs.value = -Math.round(sv * this.getFrameRateValue() / 1000)
else
this.OffsetMs.value = -sv
}A _offsetSyncing guard flag prevents update loops when the ValueBox drag adjusts the slider position and vice versa.
Frame rates are parsed from the SelectBox string using a regex to extract the numeric value:
this.getFrameRateValue = function() {
var fpsStr = this.FrameRateList ? this.FrameRateList.string : ""
var m = fpsStr.match(/(\d+\.?\d*)/)
return m ? parseFloat(m[1]) : 30
}Both "29.97 dfps" and "29.97 fps" correctly resolve to 29.97.
The Frame Rate dropdown is dimmed via param.enabled = 0 when the unit is set to ms, since the frame rate is not used in ms mode. This is handled by updateFrameRateDim():
this.updateFrameRateDim = function() {
if (this.FrameRateList)
this.FrameRateList.enabled = (this.OffsetUnit && this.OffsetUnit.value === 1) ? 1 : 0
}Similarly, empty grid buttons are dimmed (SlotBtn.enabled = 0) to visually indicate they have no name assigned and won't create a marker on click. This demonstrates using param.enabled to control visual dimming of controls based on application state.
The button grid uses a View + Button + TriggerView overlay pattern to handle both left-click (marker creation) and right-click (rename):
<View width="75" height="22">
<Button name="SlotBtn0" titlename="SlotDisplay0" size="0,0,75,22" style="Standard.AddIn.Button"/>
<TriggerView style="Ctx0" size="0,0,75,22"/>
</View>The Ctx0 style defines the right-click handler:
<Style name="Ctx0">
<Triggers>
<Trigger event="onContextMenu">
<Invoker target="controller" name="onSlotCtx0"/>
</Trigger>
</Triggers>
</Style>The size="0,0,75,22" attribute on both the Button and TriggerView positions them at the same coordinates, overlaying the transparent TriggerView on top of the visible Button. Only onContextMenu is defined on the TriggerView, so left-clicks pass through to the Button's paramChanged.
When the Auto # toggle is enabled, repeated use of the same button appends an incrementing number:
if (this.AutoNumBtn && this.AutoNumBtn.value === 1) {
if (!this.autoNumCounts[name]) this.autoNumCounts[name] = 0
this.autoNumCounts[name]++
if (this.autoNumCounts[name] > 1)
markerName = name + " " + this.autoNumCounts[name]
}The counter is per-name, so using two different slots with the same name still increments.
The preset system uses a line-based file format (one name per line, ---END--- terminator) stored as .markerslots files:
// Save
for (var i = 0; i < names.length; i++)
file.writeLine(names[i])
file.writeLine("---END---")// Load
while (!file.endOfStream) {
var line = file.readLine()
if (line === "---END---") break
loaded.push(line)
}The CCL:FileSelector provides the native save dialog with a .markerslots filter. The preset directory is created automatically on first save — Host.IO.createTextFile() builds any missing parent directories.
The updateBtnStates() function manages what each button shows:
this.updateBtnStates = function() {
for (var i = 0; i < kSlots; i++) {
var name = this["SlotName" + i] ? this["SlotName" + i].string : ""
if (name && name !== "")
this["SlotDisplay" + i].string = name
else
this["SlotDisplay" + i].string = String(i + 1)
this["SlotBtn" + i].enabled = (name && name !== "") ? 1 : 0
}
}- Named slot: Shows the name, button enabled, click creates marker
- Empty slot: Shows the slot number (1–16), button disabled
This function is called after rename, load, clear, and on initialize.
A FrameworkService controller for WindowClass panels must include at minimum the IComponent interface:
this.interfaces = [
Host.Interfaces.IComponent
]The controller must register itself and implement findParameter():
Host.Objects.registerObject(this, "MarkerCreatorController")this.findParameter = function(name) {
// Return params for all named controls in the form
if (name === "PresetList") return this.PresetList
if (name === "SaveBtn") return this.SaveBtn
// ...etc
for (var i = 0; i < kSlots; i++) {
if (name === "SlotName" + i) return this["SlotName" + i]
if (name === "SlotDisplay" + i) return this["SlotDisplay" + i]
if (name === "SlotBtn" + i) return this["SlotBtn" + i]
}
return null
}SVG files in skin/images/ are referenced via <Resources> and applied to buttons with the icon attribute:
<Resources>
<Image name="UndoIcon" url="images/undo.svg"/>
</Resources>
...
<Button name="UndoBtn" title="" icon="UndoIcon" width="24" height="22" tooltip="Undo last action"/>The rename dialog is opened via Host.GUI.runDialog() with a separate controller:
var dlg = new RenameDialogController(currentName)
dlg.initialize()
var theme = Host.GUI.Themes.getTheme(kPackageID)
var result = Host.GUI.runDialog(theme, "RenameDialog", dlg)
if (result === Host.GUI.Constants.kOkay && dlg.RenameText) {
this["SlotName" + idx].string = dlg.RenameText.string
}
dlg.terminate()- Open Find Command (Ctrl/Cmd + K): then search and select "Marker Creator" to open the panel.
- Right-click any button to rename it. Type the desired marker name and click OK.
- During playback, click a named button to create a marker at the current cursor position.
- Use the Offset slider to compensate for reaction delay (negative values = marker is placed before the cursor).
- Enable # to auto-number repeated markers.
- Save your 16 names as a preset for reuse, Load via the preset SelectBox to restore.
- Use Undo / Redo to revert/restore marker creation.
- Clear (with confirmation) to wipe all slot names.
- The panel is non-blocking — you can interact with Studio Pro while it's open.
- Create multiple named presets for different songs or use cases (Film Scoring, Dialog, Sound Design)
- Adjust the frame rate in the offset controls to match your project's Session Setup Frame Rate for accurate frame-based offset.
- The Clear button shows a confirmation dialog — the "(No Preset)" SelectBox option does not, save before selecting it.
