-
Notifications
You must be signed in to change notification settings - Fork 194
Scriptable moodlamps #362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Scriptable moodlamps #362
Changes from 14 commits
c63c85d
65b607d
f16c86f
770b5ca
7d1d309
455b07d
5a1aac0
60a28c8
bc63e22
235b2ad
d1e0378
26eb5d3
f1cf733
2aa226b
b752756
b6513da
7905351
4f4348a
9a63512
8496b9a
beb41dc
e5980fc
c8c99b8
327dd5a
624b07a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| Moodlamp scripting | ||
| --------- | ||
| Prismatik uses [QJSEngine](https://doc.qt.io/qt-5/qjsengine.html) for parsing and running moodlamp scripts, so if you are familiar with JavaScript you'll be fine. | ||
|
|
||
| The scripts live in your user's Prismatik folder: | ||
| - Windows: `C:\Users\<you>\Prismatik\moodlamps` | ||
| - macOS & Linux: `~/.Prismatik/moodlamps` | ||
|
|
||
| File name should obey these rules: | ||
| - allowed characters `a-z`, `0-9` and `_` (underscore) | ||
| - `.mjs` extension | ||
| - unique name | ||
|
|
||
| For ex: `my_cool_lamp.mjs` | ||
|
|
||
|
|
||
| **The bare minimum template:** | ||
| ```js | ||
| export const name = "your lamp name here" | ||
| export const interval = 50 // frame time in ms | ||
|
|
||
| /** | ||
| * @param {integer} baseColor integer RGB value of the base color that you set in Prismatik | ||
| * @param {array} colors array of RGB integers representing your LEDs | ||
| * @return {array} array of new RGB integers representing your LEDs | ||
| */ | ||
| export function shine(baseColor, colors) | ||
|
zomfg marked this conversation as resolved.
Outdated
|
||
| { | ||
| for (let i = 0; i < colors.length; i++) | ||
| { | ||
| // your code here, for ex: | ||
| colors[i] = baseColor // will set all LEDs to the color you set in Prismatik | ||
| } | ||
|
|
||
| return colors | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| **More examples:** | ||
|
|
||
| ```js | ||
| export const name = "your lamp name here" | ||
| export const interval = 50 // frame time in ms | ||
|
|
||
| // if you need a global constant | ||
| const myBlueConst = 0x0000FF // blue color | ||
|
|
||
| // if you need a variable that persists between frames (a counter for ex), declare it here | ||
| let someVariable = 0 | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| for (let i = 0; i < colors.length; i++) | ||
| { | ||
| // to set all your LEDs to white | ||
| colors[i] = 0xFFFFFF | ||
| // or to blue | ||
| colors[i] = myBlueConst | ||
|
|
||
| // random colors for each LED | ||
| colors[i] = Math.round(Math.random() * 0xFFFFFF) | ||
| } | ||
|
|
||
| someVariable++ // counter from above | ||
|
|
||
| return colors | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| **Recommended way:** | ||
|
|
||
| For convinience, Prismatik partially exposes `QColor` class, see [here for documentation](https://doc.qt.io/qt-5/qcolor.html) and [here for exposed methods](/Software/src/QColorMetaWrapper.hpp). | ||
|
zomfg marked this conversation as resolved.
Outdated
|
||
| Prismatik uses `QColor` internally so for best compatibility it's better to use this class. | ||
| Raw RGB values should work, but use them at your own risk. | ||
|
|
||
| Example from `rgb_is_life.js`: | ||
| ```js | ||
| export const name = "RGB is Life" | ||
| export const interval = 33 // ms | ||
|
|
||
| const speed = 1.5 | ||
|
|
||
| let m_frames = 0 | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| // transform base RGB value into QColor | ||
| baseColor = new QColor(baseColor) | ||
|
|
||
| // RGB transition math | ||
| const degrees = 360.0 / colors.length | ||
| const step = speed * m_frames++ | ||
| for (let i = 0; i < colors.length; i++) | ||
| { | ||
| // transform current color for a given LED into QColor | ||
| let color = new QColor(colors[i]) | ||
|
|
||
| // increment baseColor's hue value for that smooth RGB rotation | ||
| const hue = baseColor.hslHue() + degrees * i + step | ||
|
|
||
| // set new hue while preserving baseColor's saturation and lightness | ||
| color.setHsl(hue, baseColor.hslSaturation(), baseColor.lightness()) | ||
|
|
||
| // assign the new color | ||
| // colors[] has to contain .rgb() values | ||
| colors[i] = color.rgb() | ||
| } | ||
| if (step > 360) | ||
| m_frames = 0 | ||
|
|
||
| return colors | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| To see your changes use the reload button or relaunch the app. | ||
|
|
||
|
|
||
| **Warnig:** scripts bundled with Prismatik are overwritten on launch, if you want to customize Prismatik's scripts, make sure to make a copy of them and modify those. | ||
|
|
||
|
|
||
| **Logging and console** | ||
|
|
||
| If you enable logs in Prismatik your JS errors will be logged to | ||
| - Windows: `C:\Users\<you>\Prismatik\Logs` | ||
| - macOS & Linux: `~/.Prismatik/Logs` | ||
|
|
||
|
|
||
| You can also enable the [Console API](https://doc.qt.io/qt-5/qtquick-debugging.html#console-api) | ||
|
|
||
| ```js | ||
| export const enableConsole = true | ||
| ... | ||
| export function shine() | ||
| { | ||
| ... | ||
| console.log("some debug") | ||
| ... | ||
| } | ||
| ``` | ||
|
|
||
| Don't forget to remove when you are done | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| export const name = "Breathing" | ||
| export const interval = 40 // ms | ||
|
|
||
| let lightnessInc = -1 | ||
| let lightness = -1 | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| baseColor = new QColor(baseColor) | ||
| if (lightness == -1) | ||
| lightness = baseColor.lightness() | ||
| let color = new QColor() | ||
| if (lightness <= baseColor.lightness() / 2 && lightnessInc == -1) | ||
| lightnessInc = 1 | ||
| else if (lightness >= baseColor.lightness() - 1 && lightnessInc == 1) | ||
| lightnessInc = -1 | ||
|
|
||
| lightness += lightnessInc | ||
|
|
||
| color.setHsl(baseColor.hslHue(), baseColor.hslSaturation(), lightness) | ||
| return colors.fill(color.rgb()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| export const name = "Fire" | ||
| export const interval = 33 // ms | ||
|
|
||
| let m_lightness = [] | ||
| let m_center = 0 | ||
|
|
||
| const Cooling = 8 | ||
| const SparkMax = 160 | ||
| const SparkMin = 100 | ||
| const DefaultLightness = 127 | ||
|
|
||
| function randomBounded(max) { | ||
| return Math.floor(Math.random() * Math.floor(max)) | ||
| } | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| if (colors.length < 2) | ||
| return colors | ||
|
|
||
| if (colors.length > m_lightness.length) { | ||
| for (let i = m_lightness.length; i < colors.length; ++i) | ||
| m_lightness.push(DefaultLightness) | ||
| } | ||
|
|
||
| // heavily inspired by FastLED Fire2012 demo | ||
| // https://github.com/FastLED/FastLED/blob/master/examples/Fire2012/Fire2012.ino | ||
|
|
||
| const centerMax = Math.round(colors.length / 4) | ||
| const middleLed = Math.floor(colors.length / 2) | ||
| const sparkCount = Math.round(colors.length / 12) | ||
|
|
||
| m_center += randomBounded(2) ? -1 : 1 | ||
| m_center = Math.max(-centerMax, Math.min(centerMax, m_center)) | ||
|
|
||
| for (let i = 0; i < middleLed + m_center; ++i) { | ||
| const minLightnessReduction = Cooling * Math.pow(i / (middleLed + m_center), 3) | ||
| const maxLightnessReduction = minLightnessReduction * 2 // useless? | ||
| const lightnessReduction = minLightnessReduction + randomBounded(Math.max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3 | ||
| m_lightness[i] = Math.round(Math.max(0, m_lightness[i] - lightnessReduction)) % 256 | ||
| } | ||
|
|
||
| for (let i = colors.length - 1; i >= middleLed + m_center; --i) { | ||
| const minLightnessReduction = Cooling * Math.pow((colors.length - 1 - i) / (middleLed - m_center), 3) | ||
| const maxLightnessReduction = minLightnessReduction * 2 // useless? | ||
| const lightnessReduction = minLightnessReduction + randomBounded(Math.max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3 | ||
| m_lightness[i] = Math.round(Math.max(0, m_lightness[i] - lightnessReduction)) % 256 | ||
| } | ||
|
|
||
| for (let k = middleLed + m_center; k > 1; --k) | ||
| m_lightness[k] = Math.round((m_lightness[k - 1] + m_lightness[k - 2] * 2) / 3) % 256 | ||
|
|
||
| for (let k = middleLed + m_center; k < colors.length - 2; ++k) | ||
| m_lightness[k] = Math.round((m_lightness[k + 1] + m_lightness[k + 2] * 2) / 3) % 256 | ||
|
|
||
|
|
||
| if (randomBounded(2) == 0) { | ||
| const y = randomBounded(Math.max(1, sparkCount)) | ||
| m_lightness[y] = Math.max(SparkMax, m_lightness[y] + (SparkMin + randomBounded(SparkMax - SparkMin))) % 256 | ||
| } | ||
| if (randomBounded(2) == 0) { | ||
| const z = colors.length - 1 - randomBounded(Math.max(1, sparkCount)) | ||
| m_lightness[z] = Math.max(SparkMax, m_lightness[z] + (SparkMin + randomBounded(SparkMax - SparkMin))) % 256 | ||
| } | ||
|
|
||
| let color = new QColor(baseColor) | ||
| for (let i = 0; i < colors.length; i++) | ||
| { | ||
| color.setHsl(color.hslHue(), color.hslSaturation(), m_lightness[i]) | ||
| colors[i] = color.rgb() | ||
| } | ||
|
|
||
| return colors | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| export const name = "Random noise" | ||
| export const interval = 50 // ms | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| let color = new QColor(baseColor) | ||
| for (let i = 0; i < colors.length; i++) { | ||
| color.setRgb(Math.random() * 0xFFFFFF) | ||
| colors[i] = color.rgb() | ||
| } | ||
| return colors | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export const name = "RGB Cycle" | ||
| export const interval = 33 // ms | ||
|
|
||
| let degrees = 0 | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| baseColor = new QColor(baseColor) | ||
| baseColor.setHsl(baseColor.hslHue() + degrees++, baseColor.hslSaturation(), baseColor.lightness()) | ||
|
|
||
| if (degrees > 360) | ||
| degrees = 0 | ||
|
|
||
| return colors.fill(baseColor.rgb()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| export const name = "RGB is Life" | ||
| export const interval = 33 // ms | ||
| const speed = 1.5 | ||
|
|
||
| let m_frames = 0 | ||
|
|
||
| export default function shine(baseColor, colors) | ||
| { | ||
| baseColor = new QColor(baseColor) | ||
| const degrees = 360.0 / colors.length | ||
| const step = speed * m_frames++ | ||
| for (let i = 0; i < colors.length; i++) | ||
| { | ||
| let color = new QColor(colors[i]) | ||
| color.setHsl(baseColor.hslHue() + degrees * i + step, baseColor.hslSaturation(), baseColor.lightness()) | ||
| colors[i] = color.rgb() | ||
| } | ||
| if (step > 360) | ||
| m_frames = 0 | ||
|
|
||
| return colors | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| export const name = "Static (default)" | ||
|
|
||
| // note: in static or very slow animations it's better to keep the refresh interval under 500ms to avoid bugs | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 250 is also under 500. Did you have some bugs because of this? At some point I added
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's been a while, I think the issue was that putting a too high of a value was causing lag when changing modes, so the lower the better
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "putting a too high of a value was causing lag when changing modes" - switching the mode should reset the timer, so if it does cause a delay, it would be worth checking out why.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm only 99% sure about this, but I found and fixed the lag that occurred on lamp change: there was no call to update so you had to wait for the timer to fire |
||
| export const interval = 50 // ms | ||
|
|
||
| export function shine(baseColor, colors) | ||
| { | ||
| return colors.fill(baseColor) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.