forked from lichen-community-systems/youme
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lichen-community-systemsGH-8: Added demos for sending/receiving MTC q…
…uarter frame messages.
- Loading branch information
Showing
8 changed files
with
447 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright 2023, Tony Atkins | ||
* | ||
* Licensed under the MIT license, see LICENSE for details. | ||
*/ | ||
|
||
.timestamp { | ||
align-items: stretch; | ||
column-gap: 2rem; | ||
display: flex; | ||
flex-direction: row; | ||
font-size: 10rem; | ||
padding: 1rem; | ||
} | ||
|
||
.timestamp > * { | ||
align-content: center; | ||
border: 1px solid #969696; | ||
border-radius: 0.5rem; | ||
font-family: monospace; | ||
text-align: center; | ||
width: 100%; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright 2023, Tony Atkins | ||
* | ||
* Licensed under the MIT license, see LICENSE for details. | ||
*/ | ||
(function (fluid) { | ||
"use strict"; | ||
|
||
var youme = fluid.registerNamespace("youme"); | ||
|
||
fluid.defaults("youme.demos.quarterFrame.timestamp", { | ||
gradeNames: ["youme.templateRenderer"], | ||
model: { | ||
hour: 0, | ||
minute: 0, | ||
second: 0, | ||
frame: 0 | ||
}, | ||
selectors: { | ||
hour: ".timestamp-hour", | ||
minute: ".timestamp-minute", | ||
second: ".timestamp-second", | ||
frame: ".timestamp-frame" | ||
}, | ||
markup: { | ||
container: "<div class='timestamp'><div class='timestamp-hour'></div><div class='timestamp-minute'></div><div class='timestamp-second'></div><div class='timestamp-frame'></div></div>" | ||
}, | ||
modelRelay: { | ||
hour: { | ||
singleTransform: { | ||
input: "{that}.model.hour", | ||
type: "youme.demos.quarterFrame.timestamp.padStart" | ||
}, | ||
target: "{that}.model.dom.hour.text" | ||
}, | ||
minute: { | ||
singleTransform: { | ||
input: "{that}.model.minute", | ||
type: "youme.demos.quarterFrame.timestamp.padStart" | ||
}, | ||
target: "{that}.model.dom.minute.text" | ||
}, | ||
second: { | ||
singleTransform: { | ||
input: "{that}.model.second", | ||
type: "youme.demos.quarterFrame.timestamp.padStart" | ||
}, | ||
target: "{that}.model.dom.second.text" | ||
}, | ||
frame: { | ||
singleTransform: { | ||
input: "{that}.model.frame", | ||
type: "youme.demos.quarterFrame.timestamp.padStart" | ||
}, | ||
target: "{that}.model.dom.frame.text" | ||
} | ||
} | ||
}); | ||
|
||
youme.demos.quarterFrame.timestamp.padStart = function (number) { | ||
return (number).toString().padStart(2, "0"); | ||
}; | ||
})(fluid); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright 2023, Tony Atkins | ||
* | ||
* Licensed under the MIT license, see LICENSE for details. | ||
*/ | ||
(function (fluid) { | ||
"use strict"; | ||
var youme = fluid.registerNamespace("youme"); | ||
fluid.defaults("youme.demos.quarterFrame.receive", { | ||
gradeNames: ["youme.templateRenderer"], | ||
markup: { | ||
container: "<div class='quarter-frame'><div class='timestamp'></div><div class='input'></div></div>" | ||
}, | ||
selectors: { | ||
input: ".input", | ||
timestamp: ".timestamp" | ||
}, | ||
model: { | ||
rate: 3, | ||
hour: 0, | ||
minute: 0, | ||
second: 0, | ||
frame: 0 | ||
}, | ||
|
||
components: { | ||
timestamp: { | ||
type: "youme.demos.quarterFrame.timestamp", | ||
container: "{that}.dom.timestamp", | ||
options: { | ||
model: { | ||
hour: "{youme.demos.quarterFrame.receive}.model.hour", | ||
minute: "{youme.demos.quarterFrame.receive}.model.minute", | ||
second: "{youme.demos.quarterFrame.receive}.model.second", | ||
frame: "{youme.demos.quarterFrame.receive}.model.frame" | ||
} | ||
} | ||
}, | ||
input: { | ||
type: "youme.portSelectorView.input", | ||
container: "{that}.dom.input", | ||
options: { | ||
desiredPortSpec: { name: "IAC Driver Bus.+" }, | ||
listeners: { | ||
"onMessage.handleMessage": { | ||
funcName: "youme.demos.quarterFrame.receive.handleMessage", | ||
args: ["{youme.demos.quarterFrame.receive}", "{arguments}.0"] // message | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
|
||
youme.demos.quarterFrame.receive.handleMessage = function (that, message) { | ||
if (message.type === "quarterFrameMTC") { | ||
switch (message.piece) { | ||
case 0: | ||
var oldHighFrameNibble = that.model.frame & 16; | ||
var frameValueWithNewLowNibble = oldHighFrameNibble + (message.frame & 15); | ||
that.applier.change("frame", frameValueWithNewLowNibble); | ||
break; | ||
case 1: | ||
var oldLowFrameNibble = that.model.frame & 15; | ||
var frameValueWithNewHighNibble = (message.frame & 16) + oldLowFrameNibble; | ||
that.applier.change("frame", frameValueWithNewHighNibble); | ||
break; | ||
case 2: | ||
var oldHighSecondNibble = that.model.second & 48; | ||
var secondValueWithNewLowNibble = oldHighSecondNibble + (message.second & 15); | ||
that.applier.change("second", secondValueWithNewLowNibble); | ||
break; | ||
case 3: | ||
var oldLowSecondNibble = that.model.second & 15; | ||
var secondValueWithNewHighNibble = (message.second & 48) + oldLowSecondNibble; | ||
that.applier.change("second", secondValueWithNewHighNibble); | ||
break; | ||
case 4: | ||
var oldHighMinuteNibble = that.model.minute & 48; | ||
var minuteValueWithNewLowNibble = oldHighMinuteNibble + (message.minute & 15); | ||
that.applier.change("minute", minuteValueWithNewLowNibble); | ||
break; | ||
case 5: | ||
var oldLowMinuteNibble = that.model.minute & 15; | ||
var minuteValueWithNewHighNibble = (message.minute & 48) + oldLowMinuteNibble; | ||
that.applier.change("minute", minuteValueWithNewHighNibble); | ||
break; | ||
case 6: | ||
var oldHighHourNibble = that.model.hour & 16; | ||
var hourValueWithNewLowNibble = oldHighHourNibble + (message.hour & 15); | ||
that.applier.change("hour", hourValueWithNewLowNibble); | ||
break; | ||
case 7: | ||
var oldLowHourNibble = that.model.hour & 15; | ||
var hourValueWithNewHighNibble = (message.hour & 16) + oldLowHourNibble; | ||
that.applier.change("hour", hourValueWithNewHighNibble); | ||
that.applier.change("rate", message.rate); | ||
default: | ||
fluid.log(fluid.logLevel.ERROR, "Invalid piece number: " + message.piece); | ||
} | ||
|
||
} | ||
}; | ||
})(fluid); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/* | ||
* Copyright 2023, Tony Atkins | ||
* | ||
* Licensed under the MIT license, see LICENSE for details. | ||
*/ | ||
(function (fluid) { | ||
"use strict"; | ||
var youme = fluid.registerNamespace("youme"); | ||
fluid.defaults("youme.demos.quarterFrame.send", { | ||
gradeNames: ["youme.templateRenderer", "youme.messageSender"], | ||
markup: { | ||
container: "<div class='quarter-frame'><div class='timestamp'></div><div class='outputs'></div></div>" | ||
}, | ||
selectors: { | ||
outputs: ".outputs", | ||
timestamp: ".timestamp" | ||
}, | ||
model: { | ||
timestamp: 0, | ||
direction: 1, | ||
piece: 0, | ||
rate: 3, | ||
hour: 0, | ||
minute: 0, | ||
second: 0, | ||
frame: 0 | ||
}, | ||
|
||
invokers: { | ||
handleQuarterFrame: { | ||
funcName: "youme.demos.quarterFrame.send.handleQuarterFrame", | ||
args: ["{that}"] | ||
} | ||
}, | ||
|
||
listeners: { | ||
"onCreate.startScheduler": { | ||
funcName: "youme.demos.quarterFrame.send.startScheduler", | ||
args: ["{that}"] | ||
}, | ||
"sendMessage.sendToOutputs": "{outputs}.events.sendMessage.fire" | ||
}, | ||
|
||
components: { | ||
// TODO: Something to control direction. | ||
timestamp: { | ||
type: "youme.demos.quarterFrame.timestamp", | ||
container: "{that}.dom.timestamp", | ||
options: { | ||
model: { | ||
timestamp: "{youme.demos.quarterFrame.send}.model.timestamp", | ||
hour: "{youme.demos.quarterFrame.send}.model.hour", | ||
minute: "{youme.demos.quarterFrame.send}.model.minute", | ||
second: "{youme.demos.quarterFrame.send}.model.second", | ||
frame: "{youme.demos.quarterFrame.send}.model.frame" | ||
} | ||
} | ||
}, | ||
outputs: { | ||
type: "youme.multiPortSelectorView.outputs", | ||
container: "{that}.dom.outputs", | ||
options: { | ||
desiredPortSpecs: ["Arturia BeatStep"] | ||
} | ||
}, | ||
scheduler: { | ||
type: "berg.scheduler", | ||
options: { | ||
components: { | ||
clock: { | ||
type: "berg.clock.raf", | ||
options: { | ||
freq: 60 // times per second | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
|
||
// I am choosing not to properly deal with drop frame, and pretend it's just 30 FPS. | ||
youme.demos.quarterFrame.send.rates = [24, 25, 30, 30]; | ||
|
||
youme.demos.quarterFrame.send.startScheduler = function (that) { | ||
that.scheduler.schedule({ | ||
type: "repeat", | ||
// These are both about the best I can get the RAF scheduler to do. | ||
// A `freq` higher than 100 hangs the window. | ||
freq: 100, | ||
// An `interval` less than 0.01 hangs the window. | ||
// interval: 0.01, | ||
callback: that.handleQuarterFrame | ||
}); | ||
|
||
that.scheduler.start(); | ||
}; | ||
|
||
youme.demos.quarterFrame.send.handleQuarterFrame = function (that) { | ||
// Set the time values to match the current moment. | ||
var quarterFrameMessage = { | ||
type: "quarterFrameMTC", | ||
piece: that.model.piece, | ||
rate: that.model.rate, | ||
hour: that.model.hour, | ||
minute: that.model.minute, | ||
second: that.model.second, | ||
frame: that.model.frame | ||
}; | ||
|
||
// Send the quarter-frame MTC to any connected devices. | ||
that.events.sendMessage.fire(quarterFrameMessage); | ||
|
||
// Batch all model updates, as we'll be making them quite frequently. | ||
var transaction = that.applier.initiate(); | ||
|
||
// Track the elapsed time internally rather than peeking at the current time. | ||
var fps = youme.demos.quarterFrame.send.rates[that.model.rate]; | ||
var msPerPiece = (1000 / (fps * 4)); | ||
var newTimestamp = that.model.timestamp + (that.model.direction * msPerPiece); | ||
var newSecond = (60 + Math.round(newTimestamp / 1000)) % 60; | ||
var newMinute = (60 + Math.floor( newTimestamp / 60000)) % 60; | ||
var newHour = (24 + Math.floor(newTimestamp / 3600000)) % 24; | ||
transaction.fireChangeRequest({ path: "timestamp", value: newTimestamp}); | ||
transaction.fireChangeRequest({ path: "second", value: newSecond}); | ||
transaction.fireChangeRequest({ path: "minute", value: newMinute}); | ||
transaction.fireChangeRequest({ path: "hour", value: newHour}); | ||
|
||
// Update the frame in whichever direction we're going. | ||
if ((that.model.direction === 1 && that.model.piece === 7) || (that.model.direction === -1 && that.model.piece === 0)) { | ||
var nextFrame = (fps + that.model.frame + (that.model.direction * 2)) % fps; | ||
transaction.fireChangeRequest({ path: "frame", value: nextFrame}); | ||
} | ||
// Update the piece in whichever direction we're going. | ||
var nextPiece = (8 + that.model.piece + that.model.direction) % 8; | ||
transaction.fireChangeRequest({ path: "piece", value: nextPiece}); | ||
transaction.commit(); | ||
}; | ||
})(fluid); |
Oops, something went wrong.