Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ All tempo changes are taken into account, so the timecode should be as accurate

Usage
-----
<img src="ScoreTimecodeMainForm.png" align="right" />
<img width="353" alt="Score Timecode  wFrames" src="https://github.com/user-attachments/assets/a54a7163-7362-4d8e-acfa-cf1d154761ae" align="right" />

**Offset time**\
Enter the timecode for the first measure, in minutes, seconds and milliseconds. Only numbers may be entered, and the correct formatting will be forced by the plugin. A maximum of 99 minutes can be entered.\
Expand All @@ -23,6 +23,9 @@ Check this box to _always_ include the minutes component when writing the timeco
- When this box is _unchecked_ (default) the minutes will only be included once it is required, and the seconds will be written using one or two digits as required.
- When this box _checked_ the minutes will always be included in the timecode (even when 0), and the seconds will always be written using two digits.

**Show frames not millisecs**\
Check this box to show frames instead of milliseconds. Enter a frame rate below. The plugin automatically uses drop/non-drop frames according to standard timecode conventions.

**Style**\
Set various text style parameters for the timecode. Bold, Italic, Undescore and Square border may be combined as required.
- **B** - Make the timecode text appear in **bold**
Expand Down Expand Up @@ -59,5 +62,6 @@ To use the plugin:

Release History
-------------
**v1.0** - 24/4/2025 - Initial Release
**v1.0** - 24/4/2025 - Initial Release
**v1.5** - 7/5/2025 - Add millisec -> frame/dropframe display options

176 changes: 142 additions & 34 deletions ScoreTimeCode.qml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,21 @@ import Muse.UiComponents
//
// Change History
// v1.0 - Initial development
// v1.5 - Add millisec -> frame/dropframe display options (by Eric Warren - https://github.com/eakwarren/ScoreTimecode)
//
//=============================================================================

MuseScore
{
version: "1.0"
version: "1.5"

title: "Score Timecode"
description: "This plug-in adds text to each measure to display the elapsed time in minutes, seconds, and milliseconds."
description: "This plug-in adds text to each measure to display the elapsed time in minutes, seconds, and milliseconds or frames."
pluginType: "dialog"
thumbnailName: "ScoreTimecodeIcon.png"

implicitHeight: 350;
implicitWidth: 300;
implicitWidth: 350;


//=============================================================================
Expand All @@ -47,8 +48,12 @@ MuseScore
property var boldText: false;
property var borderText: false;
property var underlineText: false;
property var aboveText: false

property var aboveText: false

property bool useFps: false; // defaults
property var fps: 24;
property var dropFrames: false;

//=============================================================================
// Main UI Layout
//
Expand Down Expand Up @@ -107,25 +112,77 @@ MuseScore
CheckBox
{
id: excludeFirstMeasureCheckbox
Layout.columnSpan:2
Layout.columnSpan:1
text: "Exclude the first measure"
checked: excludeFirstMeasure
onClicked: {
excludeFirstMeasure = !excludeFirstMeasure;
}
}

CheckBox
{
id: useFpsField
Layout.columnSpan:1
text: "Show fps not millisecs"

checked: useFps
onClicked: { useFps = !useFps; }
}

CheckBox
{
id: alwaysIncludeMinuteCheckbox
Layout.columnSpan:2
Layout.columnSpan:1
text: "Always include minutes"
checked: alwaysIncludeMinutes
onClicked: {
alwaysIncludeMinutes = !alwaysIncludeMinutes;
}
}

RowLayout
{
id: fpsInfo
Layout.columnSpan:1
Layout.alignment: Qt.AlignRight

Label
{
id: fpsDesc
font.italic: true
font.pointSize: 12
color: palette.text
text: "24, 29.97, 60, etc. "
}

TextField
{
id: fpsField
Layout.alignment: Qt.AlignRight

text: fps

placeholderText: "24"
color: ui.theme.fontPrimaryColor
// Set the placeholder with dynamic opacity when empty
placeholderTextColor: Qt.rgba(
ui.theme.fontPrimaryColor.r,
ui.theme.fontPrimaryColor.g,
ui.theme.fontPrimaryColor.b,
text.length > 1 ? 1.0 : 0.5
)

implicitHeight: 24
Layout.maximumWidth:50
horizontalAlignment: TextInput.AlignRight

validator: RegularExpressionValidator { regularExpression: /^((\d{1,2}).(\d{1,3}))$/ || /^((\d{1,2})$/ }

enabled: useFps ? 1 : 0
}
}

Label
{
id: styleLabel
Expand Down Expand Up @@ -319,9 +376,9 @@ MuseScore
onClicked: aboutDialog.open()
}

}
}
}
}

//=============================================================================
// About Dialog

Expand Down Expand Up @@ -470,7 +527,7 @@ console.log("Error: More than one staff selected")

cursor.nextMeasure()

while ((cursor.tick != 0) && (cursor.tick < endTick))
while ((cursor.tick !== 0) && (cursor.tick < endTick))
{
var measureSeconds = 0;

Expand Down Expand Up @@ -520,7 +577,6 @@ console.log("Error: More than one staff selected")

curScore.endCmd()
}


//=============================================================================
//
Expand Down Expand Up @@ -559,28 +615,40 @@ console.log("Error: More than one staff selected")

function formatTime(seconds)
{
var minutes = Math.floor(seconds / 60);

seconds = seconds - (minutes*60);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds - (minutes * 60);

var secondsPart = Math.floor(seconds);
secondsPart = (((minutes > 0 || alwaysIncludeMinutes) && secondsPart < 10) ? "0" : "") + secondsPart;

seconds = seconds - Math.floor(seconds);
var milliseconds = Math.round(seconds * 1000)

while (milliseconds.toString().length < 3) milliseconds = "0" + milliseconds;

var result = secondsPart + "." + milliseconds + '"';

if (minutes > 0 || alwaysIncludeMinutes)
{
result = minutes
+ (colonsButton.checked ? ":" : "'")
+ result;
}

return result;
var secondsPart = Math.floor(remainingSeconds);
secondsPart = (((minutes > 0 || alwaysIncludeMinutes) && secondsPart < 10) ? "0" : "") + secondsPart;

var result = "";

if (useFps) {
var fpsVal = parseFloat(fpsField.text);
var dropFrames = fpsVal === 29.97 || fpsVal === 59.94 || fpsVal === 23.976 || fpsVal === 23.98;

// total milliseconds for the entire time (minutes + seconds + milliseconds)
var totalMilliseconds = Math.round(seconds * 1000);

// use totalMilliseconds in formatTimecode
var frameCode = formatTimecode(totalMilliseconds, fpsVal, dropFrames);

result = secondsPart + frameCode;

} else {
// Non-frame format fallback (e.g., 01:23.456")
var fractionalPart = remainingSeconds - Math.floor(remainingSeconds);
var milliseconds = Math.round(fractionalPart * 1000);
while (milliseconds.toString().length < 3) milliseconds = "0" + milliseconds;

result = secondsPart + "." + milliseconds + '"';
}

if (minutes > 0 || alwaysIncludeMinutes) {
result = minutes + (colonsButton.checked ? ":" : "'") + result;
}

return result;
}


Expand Down Expand Up @@ -698,6 +766,46 @@ console.log("Error: More than one staff selected")
{
elapsedTime = 0.0;

}
}

//=============================================================================

function formatTimecode(ms, fps, drop) {
const isDrop = drop && (fps === 29.97 || fps === 59.94 || fps === 23.976 || fps === 23.98);
const frInt = Math.round(fps); // 30 or 60 or 24
const dropFrames = (fps === 29.97) ? 2 :
(fps === 59.94) ? 4 :
(fps === 23.976 || fps === 23.98) ? 1 : 0;

const totalSeconds = ms / 1000;
let totalFrames = Math.floor(totalSeconds * frInt); // use integer fps (30, 60)

if (isDrop && dropFrames > 0) {
const framesPer10Minutes = frInt * 60 * 10;
const d = totalFrames;

const tenMinuteChunks = Math.floor(d / framesPer10Minutes);
const framesSinceLast10 = d % framesPer10Minutes;
const minutesSinceLast10 = Math.floor(framesSinceLast10 / (frInt * 60));

const dropCount = dropFrames * (tenMinuteChunks * 9 + Math.max(0, minutesSinceLast10 - Math.floor(minutesSinceLast10 / 10)));
totalFrames += dropCount;
}

const hours = Math.floor(totalFrames / (frInt * 3600));
const minutes = Math.floor((totalFrames % (frInt * 3600)) / (frInt * 60));
const seconds = Math.floor((totalFrames % (frInt * 60)) / frInt);
const frames = totalFrames % frInt;

const separator = isDrop ? ";" : ":";

function pad(n) {
return n < 10 ? "0" + n : "" + n;
}

return separator + pad(frames);
}



}
Binary file modified ScoreTimecodeMainForm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.