Skip to content

Commit e976c77

Browse files
authored
Merge pull request #58 from Amanmahe/frame-buffer
Added frame buffer and new device support
2 parents 445c1a2 + 9873b03 commit e976c77

File tree

9 files changed

+263
-196
lines changed

9 files changed

+263
-196
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Chords is an application based on Web Serial connection, you can connect boards
1919
3. Open Chords in a web browser.
2020
4. Click the "Connect" button to establish a connection with the Arduino and stream.
2121
5. Click the "Zoom" button to zoom in on data visualization.
22-
6. Click the "Play/Pause" button to stop and start data on screen.
22+
6. Click the "Play/Pause" to control data flow and navigate frames with forward/backward buttons.
2323
7. Click the "Record" button to record data.
2424
8. Click the "download" button to download the recorded data.
2525
9. Click the "Delete" button to delete recorded data.
@@ -42,17 +42,18 @@ Chords is an application based on Web Serial connection, you can connect boards
4242

4343
## Roadmap for upcoming update
4444

45-
**Data Filtering** : We will be adding bio-potential signal filtering options which includes 50/60 Hz notch filter to remove AC interference noise and highpass/lowpass remove artefacts from ECG, Emg ,Eog and EEg. Under filters, we will be adding different highpass and lowpass filters for specific bio-potential signals this feature will further enhance the user experience to record even more clear biopotential signals.
45+
- [X] **Data Filtering** : We will be adding bio-potential signal filtering options which includes 50/60 Hz notch filter to remove AC interference noise and highpass/lowpass remove artefacts from ECG, Emg ,Eog and EEg. Under filters, we will be adding different highpass and lowpass filters for specific bio-potential signals this feature will further enhance the user experience to record even more clear biopotential signals.
4646

4747

48-
**Snapshot of data** : We will add the option to show up to 10 snapshots of length 4 seconds each providing you the option to take a peek into past 40 seconds of your data.
48+
- [X] **Frame Buffers of data** : We will add Frame Buffer Feature this option to show upto 5 snapshots of length each of 4 seconds, you can now view upto last five snapshots of your data and save them as images.
4949

50-
**Multiple file download support** : We’re excited to enhance your options for downloading recorded data! Currently, you can record a file and choose to save or delete it. Soon, you’ll be able to download multiple files at once and have the flexibility to download or delete individual recorded files as needed.
5150

52-
**Raspberry Pi Pico support** : We will be releasing Raspberry Pi Pico support for chords which by the way works very well with our new Heart BioAmp Candy. Let us know your favorite board in the comments section below and we will make sure to add chords support for your board in the upcoming updates.
51+
- [ ] **Multiple file download support** : We’re excited to enhance your options for downloading recorded data! Currently, you can record a file and choose to save or delete it. Soon, you’ll be able to download multiple files at once and have the flexibility to download or delete individual recorded files as needed.
5352

53+
- [ ] **Raspberry Pi Pico support** : We will be releasing Raspberry Pi Pico support for chords which by the way works very well with our new Heart BioAmp Candy. Let us know your favorite board in the comments section below and we will make sure to add chords support for your board in the upcoming updates.
5454

55-
**CSV compatibility with [Chords Python](https://github.com/upsidedownlabs/Chords-Python)** : we will update the CSV data format and file names for both chords-web and chords-python so that you can use csvplotter.py to easily plot the recorded data.
55+
56+
- [ ] **CSV compatibility with [Chords Python](https://github.com/upsidedownlabs/Chords-Python)** : we will update the CSV data format and file names for both chords-web and chords-python so that you can use csvplotter.py to easily plot the recorded data.
5657

5758

5859
## Contributors

Version.tsx

Lines changed: 0 additions & 2 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Chords",
3-
"version": "0.1.0",
3+
"version": "2.3.0a",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

src/components/Canvas.tsx

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ interface CanvasProps {
1616
isDisplay: boolean;
1717
canvasCount?: number;
1818
Zoom: number;
19-
}
20-
interface Batch {
21-
time: number;
22-
values: number[];
19+
currentSnapshot: number;
20+
snapShotRef: React.MutableRefObject<boolean[]>;
2321
}
2422

2523
const Canvas = forwardRef(
@@ -30,6 +28,8 @@ const Canvas = forwardRef(
3028
isDisplay,
3129
canvasCount = 6, // default value in case not provided
3230
Zoom,
31+
currentSnapshot,
32+
snapShotRef,
3333
}: CanvasProps,
3434
ref
3535
) => {
@@ -45,6 +45,13 @@ const Canvas = forwardRef(
4545
const sweepPositions = useRef<number[]>(new Array(6).fill(0)); // Array for sweep positions
4646
const currentSweepPos = useRef<number[]>(new Array(6).fill(0)); // Array for sweep positions
4747
let numX: number;
48+
const array3DRef = useRef<number[][][]>(
49+
Array.from({ length: 6 }, () =>
50+
Array.from({ length: 6 }, () => Array())
51+
)
52+
);
53+
const activebuffer = useRef(0); // Initialize useRef with 0
54+
const indicesRef = useRef<number[]>([]); // Use `useRef` for indices
4855

4956
const getpoints = useCallback((bits: BitSelection): number => {
5057
switch (bits) {
@@ -57,6 +64,39 @@ const Canvas = forwardRef(
5764
}
5865
}, []);
5966
numX = getpoints(selectedBits);
67+
const prevCanvasCountRef = useRef<number>(canvasCount);
68+
69+
const processIncomingData = (incomingData: number[]) => {
70+
for (let i = 0; i < canvasCount; i++) {
71+
72+
if (prevCanvasCountRef.current !== canvasCount) {
73+
// Clear the entire buffer if canvasCount changes
74+
for (let bufferIndex = 0; bufferIndex < 6; bufferIndex++) {
75+
array3DRef.current[bufferIndex] = Array.from({ length: canvasCount }, () => []);
76+
snapShotRef.current[bufferIndex] = false;
77+
}
78+
prevCanvasCountRef.current = canvasCount;
79+
}
80+
if (array3DRef.current[activebuffer.current][i].length >= numX) {
81+
array3DRef.current[activebuffer.current][i] = [];
82+
}
83+
array3DRef.current[activebuffer.current][i].push(incomingData[i]);
84+
85+
if (array3DRef.current[activebuffer.current][i].length < numX && !pauseRef.current) {
86+
array3DRef.current[activebuffer.current][i] = [];
87+
}
88+
}
89+
if (array3DRef.current[activebuffer.current][0].length >= numX) {
90+
snapShotRef.current[activebuffer.current] = true;
91+
activebuffer.current = (activebuffer.current + 1) % 6;
92+
snapShotRef.current[activebuffer.current] = false;
93+
}
94+
indicesRef.current = [];
95+
for (let i = 1; i < 6; i++) {
96+
indicesRef.current.push((activebuffer.current - i + 6) % 6);
97+
}
98+
};
99+
60100
useEffect(() => {
61101
setNumChannels(canvasCount);
62102
}, [canvasCount]);
@@ -66,11 +106,14 @@ const Canvas = forwardRef(
66106
() => ({
67107
updateData(data: number[]) {
68108
// Reset the sweep positions if the number of channels has changed
69-
if (currentSweepPos.current.length !== numChannels) {
109+
if (currentSweepPos.current.length !== numChannels || !pauseRef.current) {
70110
currentSweepPos.current = new Array(numChannels).fill(0);
71111
sweepPositions.current = new Array(numChannels).fill(0);
72112
}
73-
updatePlots(data, Zoom);
113+
processIncomingData(data);
114+
if (pauseRef.current) {
115+
updatePlots(data, Zoom);
116+
}
74117
if (previousCounter !== null) {
75118
// If there was a previous counter value
76119
const expectedCounter: number = (previousCounter + 1) % 256; // Calculate the expected counter value
@@ -136,7 +179,7 @@ const Canvas = forwardRef(
136179
// Append grid lines to the wrapper
137180
canvasWrapper.appendChild(gridLineX);
138181
}
139-
const horizontalline=50;
182+
const horizontalline = 50;
140183
for (let j = 1; j < horizontalline; j++) {
141184
const gridLineY = document.createElement("div");
142185
gridLineY.className = "absolute bg-[rgb(128,128,128)]";
@@ -158,8 +201,8 @@ const Canvas = forwardRef(
158201

159202
const canvas = document.createElement("canvas");
160203
canvas.id = `canvas${i + 1}`;
161-
canvas.width = canvasContainerRef.current.clientWidth ;
162-
const canvasHeight = (canvasContainerRef.current.clientHeight / numChannels) ;
204+
canvas.width = canvasContainerRef.current.clientWidth;
205+
const canvasHeight = (canvasContainerRef.current.clientHeight / numChannels);
163206
canvas.height = canvasHeight;
164207
canvas.className = "w-full h-full block rounded-xl";
165208

@@ -259,31 +302,63 @@ const Canvas = forwardRef(
259302
createCanvases();
260303
}, [numChannels, theme]);
261304

262-
const getValue = useCallback((bits: BitSelection): number => {
263-
switch (bits) {
264-
case "ten":
265-
return 10;
266-
case "twelve":
267-
return 12;
268-
case "fourteen":
269-
return 14;
270-
default:
271-
return 0; // Or any other fallback value you'd like
272-
}
273-
}, []);
274305

275306
const animate = useCallback(() => {
276-
if (pauseRef.current) {
307+
if (!pauseRef.current) {
308+
// If paused, show the buffered data (this part runs when paused)
309+
updatePlotSnapshot(currentSnapshot);
310+
} else {
311+
// If not paused, continue with normal updates (e.g., real-time plotting)
277312
wglPlots.forEach((wglp) => wglp.update());
278-
requestAnimationFrame(animate);
313+
requestAnimationFrame(animate); // Continue the animation loop
279314
}
280-
}, [wglPlots, pauseRef]);
315+
}, [currentSnapshot, numX, pauseRef.current, wglPlots, Zoom]);
316+
317+
318+
const updatePlotSnapshot = (currentSnapshot: number) => {
319+
for (let i = 0; i < canvasCount; i++) {
320+
wglPlots.forEach((wglp, index) => {
321+
if (wglp) {
322+
try {
323+
wglp.gScaleY = Zoom; // Adjust the zoom value
324+
} catch (error) {
325+
console.error(
326+
`Error setting gScaleY for WebglPlot instance at index ${index}:`,
327+
error
328+
);
329+
}
330+
} else {
331+
console.warn(`WebglPlot instance at index ${index} is undefined.`);
332+
}
333+
});
334+
if (
335+
array3DRef.current &&
336+
indicesRef.current &&
337+
indicesRef.current[currentSnapshot] !== undefined &&
338+
array3DRef.current[indicesRef.current[currentSnapshot]] !== undefined
339+
) {
340+
const yArray = new Float32Array(array3DRef.current[indicesRef.current[currentSnapshot]][i]);
341+
// Check if the line exists
342+
const line = linesRef.current[i];
343+
if (line) {
344+
line.shiftAdd(yArray); // Efficiently add new points
345+
} else {
346+
console.error(`Line at index ${i} is undefined or null.`);
347+
}
348+
349+
} else {
350+
console.warn("One of the references is undefined or invalid");
351+
}
352+
281353

282-
useEffect(() => {
283-
if (pauseRef.current) {
284-
requestAnimationFrame(animate);
285354
}
286-
}, [pauseRef.current, animate]);
355+
wglPlots.forEach((wglp) => wglp.update()); // Redraw the plots
356+
};
357+
358+
useEffect(() => {
359+
requestAnimationFrame(animate);
360+
361+
}, [animate]);
287362

288363
useEffect(() => {
289364
const handleResize = () => {

0 commit comments

Comments
 (0)