Closed asdf23 closed 1 year ago
Strike that 6 buttons the two knobs are buttons. The output from the sample application is:
Connection successful!
Knob knobTL rotated: -1
Button pressed: knobTL
{ serial: 'LDD2201012022390100260B1003', version: '0.1.3' }
Button unpressed: knobTL
Button pressed: knobCL
{ serial: 'LDD2201012022390100260B1003', version: '0.1.3' }
Button unpressed: knobCL
Button pressed: 1
{ serial: 'LDD2201012022390100260B1003', version: '0.1.3' }
Button unpressed: 1
Button pressed: 2
{ serial: 'LDD2201012022390100260B1003', version: '0.1.3' }
Button unpressed: 2
Button pressed: 3
{ serial: 'LDD2201012022390100260B1003', version: '0.1.3' }
Button unpressed: 3
Button pressed: circle
{ serial: 'LDD2201012022390100260B1003', version: '0.1.3' }
Button unpressed: circle
Knob knobTL rotated: 1
Knob knobTL rotated: -1
Knob knobCL rotated: 1
Knob knobCL rotated: -1
const { LoupedeckDevice } = require('loupedeck')
// Detects and opens first connected device
const device = new LoupedeckDevice()
// Observe connect events
device.on('connect', () => {
console.info('Connection successful!')
})
// React to button presses
device.on('down', ({ id }) => {
console.info(`Button pressed: ${id}`);
//Not working
//try {
// device.getInfo().then(t=> {
// console.log(t);
// });
//}catch(e){
// console.log(e);
//}
})
device.on('up', ({ id }) => {
console.info(`Button unpressed: ${id}`)
})
// React to knob turns
device.on('rotate', ({ id, delta }) => {
console.info(`Knob ${id} rotated: ${delta}`)
})
//Not working
//device.on('touchstart', ({ id, delta }) => {
// console.info(`touchstart ${delta}`)
//})
//device.on('touchmove', ({ id, delta }) => {
// console.info(`touchmove ${delta}`)
//})
//device.on('touchend', ({ id, delta }) => {
// console.info(`touchend ${delta}`)
//})
There's a few differences for the Loupedeck Live-S in the way it handles the screens.
See:
Wow! So the change to make this work is:
constants.js
24,27c24,26
< center: { id: Buffer.from('\x00A'), width: 360, height: 270 },
< left: { id: Buffer.from('\x00L'), width: 60, height: 270 },
< right: { id: Buffer.from('\x00R'), width: 60, height: 270 },
< live_s: { id: Buffer.from('\x00M'), width: 480, height: 270 }
---
> center: { id: Buffer.from('\x00A'), width: 360, height: 270 },
> left: { id: Buffer.from('\x00L'), width: 60, height: 270 },
> right: { id: Buffer.from('\x00R'), width: 60, height: 270 }
91c90
No if statements, no hard-coded values for screen size. This is very well written and future proof. The magic number constants are well documented and make since.
Especially when compared to...
--- Notes:
--- * the `response` contains the `id`, `data`, `success`.
--- * the `success` value is a boolean, `true` or `false`.
function mod.mt:updateKnobImage(knobID, imageBytes, callbackFn)
local whichScreen = knobID > 3 and mod.screens.right or mod.screens.left
local y = 0
if knobID == 2 or knobID == 5 then
y = 90
elseif knobID == 3 or knobID == 6 then
y = 180
end
return self:updateScreenImage(whichScreen, imageBytes, {x=0, y=y, w=60,h=90}, callbackFn)
end
Hard-coded, magic numbers
Really?
Haha, yeah our Lua code is a mish-mash of stuff that's changed and evolved a lot over the years. I'm not a programmer by trade, so a lot of the stuff is me just trying to make stuff work. The method you mention is actually to draw an image for a specific/individual knob. I agree - it could definitely be better, but hey, it works, and has clearly pointed you in the right direction, which was my original intention.
@asdf23 thank you for providing this valuable info! I will review your PR and was wondering if you got getInfo()
to work now?
Also, can you confirm the version of firmware you are running? I had someone else report that the new devices are running 0.2.8, but I haven't been able to see this version available for download anywhere and it would be good to know if it works with this library.
Functionalty seems completely working (physical buttons, knobs, touchevents, vibration?!!, writing to the screen). Prediction as to where the virtual buttons are is off. Not surprising since the device is a different shape... actually I never saw the other devices so I don't really know.
About the device's screen: The right edge screen ends before the plastic begins so it feels like the right most column has about 6 pixels width of dead space. Meanwhile the left edge hides 19 pixels under plastic, very annoying. I'm tempted to just remove the stupid plastic grid. The break down is like this:
v--- maybe not? ---v broken device?
X axis width
0 19 plastic -19
19 96 column1 -77
96 109 plastic -13
109 187 column2 -78
187 199 plastic -12
199 275 column3 -76
275 289 plastic -14
289 368 column4 -79
368 380 plastic -12
380 450 column5 -70
Past 450 there is more button looking real-estate but no actual screen.
Y axis width
0 6 plastic -6
6 83 row1 -77
83 97 plastic -14
97 173 row2 -76
173 188 plastic -15
188 263 row3 -75
263 - ?? plastic
getInfo shows:
✅ Connected to Loupedeck at /dev/ttyACM0
Connection successful!
Device serial number LDD2201012022390100260B1003, software version 0.1.3
I got from running the code:
device.on("connect", async({ address }) => {
console.info(`✅ Connected to Loupedeck at ${address}`)
const { serial, version } = await device.getInfo()
console.info(`Device serial number ${serial}, software version ${version}`)
setBrightness seems to want a number between 0 and 1. I was thinking 1-10 which I got from const MAX_BRIGHTNESS = 10
so long as your using a fraction this seems to work.
To draw on the screen I used:
const { LoupedeckDevice } = require("loupedeck");
const device = new LoupedeckDevice();
const { createCanvas } = require("canvas");
...snip...
const canvas = createCanvas(96*5, 90*3); // <--- not exactly right
const context = canvas.getContext("2d", {pixelFormat: "RGB16_565"});
let colors = ["red","blue","green","yellow","purple","gray","orange","teal","cyan","lightblue","lightgreen","lightred","#34FFaa","#aaFF33","#FFaa11"];
var i=0;
for(let x=0; x<5; x++) {
for(let y=0; y<3; y++) {
context.fillRect(x*96, y*90, 96, 90); // <--- not exactly right
context.fillStyle = colors[i++];
}
}
let buff = canvas.toBuffer("raw");
console.log(90*90*2,buff.length);
//width * height * 2
device.drawBuffer({
id: "live_s"
,width: 96*5 // <--- not exactly right
,height: 90*3 // <--- not exactly right
,x: 0
,y: 0
,autoRefresh: true // <--- what is this? window.requestAnimationFrame (fingerscrossed) .?
}
,buff
);
...snip...
I don't know anything about canvas/animation but what I read seems that a browser presents a window.requestAnimationFrame, (I was able to use setTimeout instead). I'm not sure if I can reuse the same buffer object or what autoRefresh is for, I wasn't able to get this to do anything.
On/Off connecting....
I don't get it. Connect works perfectly the first time. Then if I .close()
+ Ctrl+C out of the program, start the program Ctrl+C
out of the program then start the program it always connects. Unplugging the device then re-plugging then starting the program always connects. I think this is just how the device works, not a reflection on this code.
Examples:
simple - drawKey doesn't seem to work. Everything else does. The display is not updated slide-puzzle - it connects but that's all nothing seems to happen.
It was nice to see that most things were working well out of the box in the 'simple' example, though, the big seller is the nicer screens. I have been using a Xencelabs quick key for a year or two, and the Live S seems a lot more like what I was hoping the Xencelabs would have been, the screens on it are extremely basic. Unfortunately, I as well am not very familiar with Canvas, but also general web technologies, so I just have to build from the examples. From the sound of things, though, it seems like it might just take some tweaking to get things working properly?
@asdf23 just a note: instead of creating the canvas
yourself in your example, you can call the drawCanvas
method. The callback that you supply it with will automatically give you a canvas with the right width/height so you can immediately call fillRect()
etc without having to do buffer or color conversions yourself.
Thanks for the tip. It seems my device is broken, their are some dead pixels. I'm going to try to return it, that being the case my measurements for the button locations might be off.
I was able to fix the dead pixels issue, I'm not sure if the original software fixed it or if I was doing something wrong.
The screen and plastic overlay isn't exactly lined up squarely, and the red/green/blue pixels are not exactly vertical, and the plastic overlay has enough height to it that the position of your head matters, and the buttons are not square, and the spacers are not uniform, and pressing a spacer bubbles right down to the screen... I mention this to say that the positioning of the buttons is a bit of a judgement call.
With all that in mind here's an algorithm that draws each button as cleanly as possible. But I'm not sure anyone wants this in their code.
let leftOffset = 18;
let topOffset = 6;
let spacerWidth = 12;
let spacerHeight = 13;
let buttonWidth = 79;
let buttonHeight = 77;
let rows = 3;
let columns = 5;
device.drawCanvas({id: "live_s", autoRefresh: true}, (ctx,w,h)=> {
for(let r=0; r<rows; r++) {
for(let c=0; c<columns; c++) {
ctx.fillStyle = .....
ctx.fillRect(leftOffset + (c * buttonWidth) + (spacerWidth * c), topOffset + (r * buttonHeight) + (spacerHeight * r), buttonWidth, buttonHeight)
}
}
colors.push(colors.shift());
});
yuck
I definitely am waiting this, though, I seem to be missing something. I get
/mnt/x/GitHub/foxxyz/loupedeck/device.js:108
if (!width) width = displayInfo.width
when trying the code you shared.
Looking at a prior snippet you posted, you had
{id: "live_s", width: 96 * 5, height: 90 * 3, x: 0, y: 0, autoRefresh: true}
But that gives:
/mnt/x/GitHub/foxxyz/loupedeck/device.js:100
await this.send(COMMANDS.FRAMEBUFF, Buffer.concat([displayInfo.id, header, buffer]))
I tried updating my project in case there was something I was missing from a framework update, but I didn't see anything. I am sure it must just be something silly I am missing.
Sorry I don't understand, your getting an error message? Did you edit the constants file?
My apologies, I took the simple/index.js demo and tried to add the code you mentioned above to it, just to see if I had any success with it. So, near the bottom of the simple example, I changed the last function to include the following:
After reading your reply about a constants file, I am quite sure I missed something. I didn't realize there was a constants file, so I guess I need to look for that.
Install node.js , create a directory, npm init , npm install loupedeck , edit constants file, create index.js , look at samples for guidance
Yeah, I got it figured out, thanks. Now I just need to spend some time looking into Canvas and how to work with it. :+1:
Edit : One step at a time, but its getting there, lol.
Back to the original software....
I can't get drawKey to work, somewhere the size of the buffer changes and I'm not sure why. Seems like this happens magically when passed through a callback, I've given up on getting this to work.
IMHO, the spaces between the keys should not be considered keys, I don't know if anyone else shares that opinion. What I did to the software was to map it out.....
This functions takes a button index and converts it to an X/Y corrdinate:
buttonIndexToKey(buttonIndex) {
switch(this.thisDevice) {
default:
case "center":
let w = 90;
let h = 90;
let cols = 4;
return {
width: w
,height: h
,x: buttonIndex % cols * w
,y: Math.floor(buttonIndex / cols) * h
};
case "live_s":
console.log("live_s calculation");
let leftOffset = 18;
let topOffset = 6;
let spacerWidth = 12;
let spacerHeight = 13;
let buttonWidth = 79;
let buttonHeight = 77;
let rows = 3;
let columns = 5;
return {
width: buttonWidth
,height: buttonHeight
,x: leftOffset + ((buttonIndex % columns) * buttonWidth) + ((buttonIndex % columns) * spacerWidth)
,y: topOffset + (Math.floor(buttonIndex / rows) * buttonHeight) + (Math.floor(buttonIndex / rows) * spacerHeight)
};
}
}
I don't mind sharing this back, I'm not sure anyone else wants it.
By "sharing this back". I don't mean that terrible code. Instead what I would check in is something like this:
const DISPLAYS = {
center: { id: Buffer.from('\x00A'), width: 360, height: 270, leftOffset: 0, topOffset: 0, horizontalSpacer: 0, verticleSpacer: 0, buttonWidth: 90, buttonHeight: 90, rows: 4, columns: 4 }, // "A" 0x0041
left: { id: Buffer.from('\x00L'), width: 60, height: 270, leftOffset: 0, topOffset: 0, horizontalSpacer: 0, verticleSpacer: 0, buttonWidth: 90, buttonHeight: 90, rows: 4, columns: 4 }, // "L" 0x004C
right: { id: Buffer.from('\x00R'), width: 60, height: 270, leftOffset: 0, topOffset: 0, horizontalSpacer: 0, verticleSpacer: 0, buttonWidth: 90, buttonHeight: 90, rows: 4, columns: 4 }, // "R" 0x0052
live_s: { id: Buffer.from('\x00M'), width: 480, height: 270, leftOffset: 18, topOffset: 6, horizontalSpacer: 12, verticleSpacer: 13, buttonWidth: 79, buttonHeight: 77, rows: 5, columns: 3 }, // "M" 0x004D
}
After that change devices.js would be updated to use these values.
It's up to @foxxyz if you want to go in that direction or not. Meanwhile thanks for the contribution this got me up and running very quickly.
IMHO, the spaces between the keys should not be considered keys, I don't know if anyone else shares that opinion. What I did to the software was to map it out.....
I kind of agree with you, but the loupedeck software appears to not. It is sending 90x90 for each button on this model, with a horizontal padding of 0x0f at the start and end of the display. But in https://github.com/bitfocus/companion I have made it send 80x80, centered within that region. I have noticed that things look a little off center sometimes, but Im not sure its a big enough deal for me to fix it in there
Fun side note: It's worth noting that some CommandPost users have actually taken off the plastic in front of the screen so they have access to the full touch screen. That's the main reason I've keep the buttons the full 90x90.
This is great, thank you.
One small question in regard to this, if you don't mind. I attempted to add some text over the button color using the following:
ctx.font = "5px serif";
ctx.fillStyle = "white";
ctx.fillText("Hello", w, h)
Just trying to get a feel for how things end up looking but nothing seems to show up. Looking at the readme, it says supports "Setting Button Color" and "Writing screen graphics". Is that literal, as in color and graphic (as in image) only, or should things like fillText also work and I am just doing it wrong? I expect it is me / the examples I looked at online but I just wanted to make sure.
This is great, thank you.
One small question in regard to this, if you don't mind. I attempted to add some text over the button color using the following:
ctx.font = "5px serif"; ctx.fillStyle = "white"; ctx.fillText("Hello", w, h)
Just trying to get a feel for how things end up looking but nothing seems to show up. Looking at the readme, it says supports "Setting Button Color" and "Writing screen graphics". Is that literal, as in color and graphic (as in image) only, or should things like fillText also work and I am just doing it wrong? I expect it is me / the examples I looked at online but I just wanted to make sure.
Yeah, this works for sure. You're likely not seeing anything for two reasons: 1) 5px is incredibly small, and 2) the text is left aligned at the right bottom edge of the square at (w, h)
and being rendered outside of the visible area. The coordinates are relative to the key you're targeting, so you should see something if you do this:
ctx.font = "15px serif";
ctx.fillStyle = "white";
ctx.fillText("Hello", 5, 20)
(I'm using a y coordinate of 20px because the default text baseline is alphabetic so the letters are rendered above that coordinate. You can play around with setting textAlign
and textBaseline
to get different alignments.)
Feel free to open another issue if you encounter anything else you think is off!
That did the trick. I figured it had to be something silly I was doing. I wasn't quite sure what the font size was going to be relative to, so I figured starting off small was a good idea, but that was certainly too small it seems, lol. It seems I had something of a "derp" moment, as for whatever reason, I was thinking that w/h was translating to a value in the range of the logged x,y when a button was pressed, even though it clearly isn't. Now that I actually see it in action, it all came together. Thanks again for your work on this. Excited to play around with it, and will be sure to report anything I may come across. :+1:
A new device is out it has 2 rotation knobs (working) --4-- 6 physical buttons, (working) and a touch screen? divided into a 3x5 (3 high 5 wide) buttons. The existing software works for the physical knobs/buttons. I could not get the events touchstart,touchend,touchmove, to work nor the .getInfo method to return. Hooking these events or calling .getInfo just sort of hangs the program.
The lsusb -v for this device is: