refactored from single to multiple pages
parent
0e511f0220
commit
cb99feea2f
|
|
@ -0,0 +1,176 @@
|
||||||
|
window.onload = () => {
|
||||||
|
let isInterpolating = false;
|
||||||
|
let currentFrame = 0;
|
||||||
|
let dialKeyframes = Array.from({ length: 5 }, () => ({}));
|
||||||
|
let port, reader, writer;
|
||||||
|
const dials = [];
|
||||||
|
const frameSlider = document.getElementById('frameSlider');
|
||||||
|
const frameDisplay = document.getElementById('frameDisplay');
|
||||||
|
const canvas = document.getElementById('timelineCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const totalFrames = 400;
|
||||||
|
|
||||||
|
frameSlider.oninput = () => {
|
||||||
|
currentFrame = parseInt(frameSlider.value);
|
||||||
|
frameDisplay.textContent = currentFrame;
|
||||||
|
isInterpolating = true;
|
||||||
|
|
||||||
|
for (let ch = 0; ch < 5; ch++) {
|
||||||
|
const keyframes = dialKeyframes[ch];
|
||||||
|
let prevFrame = null, nextFrame = null;
|
||||||
|
|
||||||
|
for (let f = currentFrame; f >= 0; f--) {
|
||||||
|
if (keyframes[f] !== undefined) {
|
||||||
|
prevFrame = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let f = currentFrame; f <= 399; f++) {
|
||||||
|
if (keyframes[f] !== undefined) {
|
||||||
|
nextFrame = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let value;
|
||||||
|
if (prevFrame !== null && nextFrame !== null && prevFrame !== nextFrame) {
|
||||||
|
const prevVal = keyframes[prevFrame];
|
||||||
|
const nextVal = keyframes[nextFrame];
|
||||||
|
const t = (currentFrame - prevFrame) / (nextFrame - prevFrame);
|
||||||
|
value = Math.round(prevVal + (nextVal - prevVal) * t);
|
||||||
|
} else if (prevFrame !== null) {
|
||||||
|
value = keyframes[prevFrame];
|
||||||
|
} else if (nextFrame !== null) {
|
||||||
|
value = keyframes[nextFrame];
|
||||||
|
} else {
|
||||||
|
value = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
dials[ch].value = value;
|
||||||
|
document.getElementById(`value${ch}`).textContent = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTimelineMarkers();
|
||||||
|
isInterpolating = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
dials[i] = new Nexus.Dial(`#dial${i}`, {
|
||||||
|
size: [80, 80],
|
||||||
|
min: 0,
|
||||||
|
max: 1023,
|
||||||
|
value: 512
|
||||||
|
});
|
||||||
|
|
||||||
|
dials[i].on('change', (v) => {
|
||||||
|
if (isInterpolating) return;
|
||||||
|
const val = Math.round(v);
|
||||||
|
document.getElementById(`value${i}`).textContent = val;
|
||||||
|
dialKeyframes[i][currentFrame] = val;
|
||||||
|
drawTimelineMarkers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('connect').onclick = async () => {
|
||||||
|
port = await navigator.serial.requestPort();
|
||||||
|
await port.open({ baudRate: 115200 });
|
||||||
|
writer = port.writable.getWriter();
|
||||||
|
reader = port.readable.getReader();
|
||||||
|
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
while (true) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
document.getElementById('log').value += decoder.decode(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('send').onclick = async () => {
|
||||||
|
const text = document.getElementById('input').value + '\n';
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
await writer.write(encoder.encode(text));
|
||||||
|
document.getElementById('input').value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('sendFrame').onclick = async () => {
|
||||||
|
const positions = dials.map(d => Math.round(d.value));
|
||||||
|
const message = `FRAME ${positions.join(',')}\n`;
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
await writer.write(encoder.encode(message));
|
||||||
|
};
|
||||||
|
|
||||||
|
function drawTimelineMarkers() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
const width = canvas.width;
|
||||||
|
const height = canvas.height;
|
||||||
|
|
||||||
|
for (let ch = 0; ch < 5; ch++) {
|
||||||
|
for (let frame in dialKeyframes[ch]) {
|
||||||
|
const x = (frame / 400) * width;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, 0);
|
||||||
|
ctx.lineTo(x, height);
|
||||||
|
ctx.strokeStyle = ['red', 'green', 'blue', 'orange', 'purple'][ch];
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentX = (currentFrame / 400) * width;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(currentX, 0);
|
||||||
|
ctx.lineTo(currentX, height);
|
||||||
|
ctx.strokeStyle = 'black';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('saveAnimation').onclick = async () => {
|
||||||
|
if (!writer) {
|
||||||
|
alert("Serial not connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = new ArrayBuffer(totalFrames * 5 * 2);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
|
||||||
|
for (let frame = 0; frame < totalFrames; frame++) {
|
||||||
|
for (let ch = 0; ch < 5; ch++) {
|
||||||
|
const keyframes = dialKeyframes[ch];
|
||||||
|
let prevFrame = null, nextFrame = null;
|
||||||
|
|
||||||
|
for (let f = frame; f >= 0; f--) {
|
||||||
|
if (keyframes[f] !== undefined) {
|
||||||
|
prevFrame = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let f = frame; f <= totalFrames - 1; f++) {
|
||||||
|
if (keyframes[f] !== undefined) {
|
||||||
|
nextFrame = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let value;
|
||||||
|
if (prevFrame !== null && nextFrame !== null && prevFrame !== nextFrame) {
|
||||||
|
const prevVal = keyframes[prevFrame];
|
||||||
|
const nextVal = keyframes[nextFrame];
|
||||||
|
const t = (frame - prevFrame) / (nextFrame - prevFrame);
|
||||||
|
value = Math.round(prevVal + (nextVal - prevVal) * t);
|
||||||
|
} else if (prevFrame !== null) {
|
||||||
|
value = keyframes[prevFrame];
|
||||||
|
} else if (nextFrame !== null) {
|
||||||
|
value = keyframes[nextFrame];
|
||||||
|
} else {
|
||||||
|
value = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.setUint16((frame * 5 + ch) * 2, value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.write(buffer);
|
||||||
|
alert("Animation sent over serial.");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 30px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dial {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue