Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,648 changes: 893 additions & 2,755 deletions package-lock.json

Large diffs are not rendered by default.

44 changes: 22 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@
"start": "next start",
"lint": "next lint",
"export": "next export",
"distDir": "out"
"distDir": "out",
"generate-manifest": "ts-node scripts/generateManifest.mjs"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@types/p5": "^1.7.6",
"@types/w3c-web-serial": "^1.0.6",
"class-variance-authority": "^0.7.0",
Expand All @@ -41,17 +42,16 @@
"html2canvas": "^1.4.1",
"jszip": "^3.10.1",
"lucide-react": "^0.460.0",
"next": "^15.3.3",
"next": "^16.0.3",
"next-pwa": "^5.6.0",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-icons": "^5.3.0",
"smoothie": "^1.36.1",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"tailwindcss-textshadow": "^2.1.3",
"uuid": "^11.0.3",
"webgl-plot": "^0.7.2"
},
Expand Down
13 changes: 13 additions & 0 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "Chords",
"short_name": "Chords",
"start_url": ".",
"display": "standalone",
"icons": [
{ "src": "chords-logo-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "chords-logo-512x512.png", "sizes": "512x512", "type": "image/png" }
],
"screenshots": [
{ "src": "screenshot-chords.png", "sizes": "1280x720", "type": "image/png", "form_factor": "wide" }
]
}
Binary file removed src/app/favicon.ico
Binary file not shown.
6 changes: 2 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<link rel="manifest" href={`${basePath}/manifest.json`} /> {/* ✅ Dynamic manifest */}
</head>
<head><link rel="manifest" href={`${basePath}/manifest.json`} /></head>
<body
className={cn(
lobsterTwo.variable,
Expand All @@ -70,4 +68,4 @@ export default function RootLayout({
</body>
</html>
);
}
}
3 changes: 2 additions & 1 deletion src/app/npg-lite/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,6 @@ const NPG_Ble = () => {

const toggleChannel = (channelIndex: number) => {
setSelectedChannels((prevSelected) => {
setManuallySelected(true);
const updatedChannels = prevSelected.includes(channelIndex)
? prevSelected.filter((ch) => ch !== channelIndex)
: [...prevSelected, channelIndex];
Expand All @@ -799,6 +798,8 @@ const NPG_Ble = () => {

return sortedChannels;
});

setManuallySelected(true);
};


Expand Down
10 changes: 5 additions & 5 deletions src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const Canvas = forwardRef(
ref
) => {
const { theme } = useTheme();
let previousCounter: number | null = null; // Variable to store the previous counter value for loss detection
const previousCounterRef = useRef<number | null>(null); // Variable to store the previous counter value for loss detection
const canvasContainerRef = useRef<HTMLDivElement>(null);
const [numChannels, setNumChannels] = useState<number>(selectedChannels.length);
const dataPointCountRef = useRef<number>(2000); // To track the calculated value
Expand Down Expand Up @@ -165,17 +165,17 @@ const Canvas = forwardRef(
processIncomingData(data);
updatePlots(data, Zoom);
}
if (previousCounter !== null) {
if (previousCounterRef.current !== null) {
// If there was a previous counter value
const expectedCounter: number = (previousCounter + 1) % 256; // Calculate the expected counter value
const expectedCounter: number = (previousCounterRef.current + 1) % 256; // Calculate the expected counter value
if (data[0] !== expectedCounter) {
// Check for data loss by comparing the current counter with the expected counter
console.warn(
`Data loss detected in canvas! Previous counter: ${previousCounter}, Current counter: ${data[0]}`
`Data loss detected in canvas! Previous counter: ${previousCounterRef.current}, Current counter: ${data[0]}`
);
}
}
previousCounter = data[0]; // Update the previous counter with the current counter
previousCounterRef.current = data[0]; // Update the previous counter with the current counter
},
}),
[Zoom, numChannels, timeBase]
Expand Down
45 changes: 31 additions & 14 deletions src/components/Connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ const Connection: React.FC<ConnectionProps> = ({


const toggleChannel = (channelIndex: number) => {
// Mark as manually selected and update selectedChannels
setSelectedChannels((prevSelected) => {
setManuallySelected(true);
const updatedChannels = prevSelected.includes(channelIndex)
? prevSelected.filter((ch) => ch !== channelIndex)
: [...prevSelected, channelIndex];
Expand All @@ -292,24 +292,29 @@ const Connection: React.FC<ConnectionProps> = ({
sortedChannels.push(1);
}

// Retrieve saved devices from localStorage
const savedPorts = JSON.parse(localStorage.getItem('savedDevices') || '[]');
const portInfo = portRef.current?.getInfo();

if (portInfo) {
const deviceIndex = savedPorts.findIndex(
(saved: SavedDevice) => saved.deviceName === devicenameref.current
);
// Retrieve saved devices from localStorage and persist selection
try {
const savedPorts = JSON.parse(localStorage.getItem('savedDevices') || '[]');
const portInfo = portRef.current?.getInfo();

if (deviceIndex !== -1) {
savedPorts[deviceIndex].selectedChannels = sortedChannels;
localStorage.setItem('savedDevices', JSON.stringify(savedPorts));
if (portInfo) {
const deviceIndex = savedPorts.findIndex(
(saved: SavedDevice) => saved.deviceName === devicenameref.current
);

if (deviceIndex !== -1) {
savedPorts[deviceIndex].selectedChannels = sortedChannels;
localStorage.setItem('savedDevices', JSON.stringify(savedPorts));
}
}
} catch (e) {
console.warn('Failed to persist selected channels:', e);
}

return sortedChannels;
});

setManuallySelected(true);
};

// Handle right arrow click (reset count and disable button if needed)
Expand Down Expand Up @@ -357,11 +362,21 @@ const Connection: React.FC<ConnectionProps> = ({
if (!workerRef.current) {
initializeWorker();
}
setCanvasCount(selectedChannels.length)
// Update parent canvasCount only when it differs to avoid unnecessary rerenders
const newCount = selectedChannels.length;
if (typeof setCanvasCount === 'function' && newCount !== canvasCount) {
setCanvasCount(newCount);
}

// Send canvasCount independently to the worker
workerRef.current?.postMessage({ action: 'setCanvasCount', canvasCount: canvasElementCountRef.current });
};
setCanvasCountInWorker(canvasElementCountRef.current);

// Run the canvas count sync after render (and when selectedChannels change)
useEffect(() => {
setCanvasCountInWorker(canvasElementCountRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedChannels.length]);

const setSelectedChannelsInWorker = (selectedChannels: number[]) => {
if (!workerRef.current) {
Expand Down Expand Up @@ -412,8 +427,10 @@ const Connection: React.FC<ConnectionProps> = ({

if (zipBlob) {
saveAs(zipBlob, 'ChordsWeb.zip');
toast.success("Data successfully downloaded as ZIP.");
} else if (error) {
console.error(error);
toast.error(`Error while creating ZIP: ${error}`);
}
};
}
Expand Down
25 changes: 13 additions & 12 deletions src/components/Contributors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "../components/ui/card";
import { Separator } from "../components/ui/separator";
import { Avatar, AvatarFallback, AvatarImage } from "../components/ui/avatar";
import { Dialog, DialogContent, DialogTrigger } from "../components/ui/dialog";
import { Dialog, DialogContent, DialogTrigger, DialogTitle } from "../components/ui/dialog";
import { CircleAlert } from "lucide-react";
import { Button } from "../components/ui/button";
import Link from "next/link";
Expand Down Expand Up @@ -62,22 +62,23 @@ const contributors = [
export default function Contributors() {
return (
<Dialog>
<DialogTrigger>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button variant="ghost" size="icon">
<CircleAlert className="h-5 w-5" />
<span className="sr-only">View Contributors</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Contributors</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</DialogTrigger>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent>
<p>Contributors</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DialogContent className="sm:max-w-[425px] md:max-w-[570px] lg:max-w-[650px]">
<DialogTitle className="sr-only">Contributors</DialogTitle>
<Card className="border-none">
<CardHeader className="p-0 mb-2">
<CardTitle className="font-bold items-center gap-2 flex mb-1">
Expand Down
8 changes: 4 additions & 4 deletions src/components/DataPass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const DataPass = () => {
const [channelCount, setChannelCount] = useState<number>(1); // Number of channels
const canvasRef = useRef<any>(null); // Create a ref for the Canvas component
const [selectedChannels, setSelectedChannels] = useState<number[]>([1]);
let previousCounter: number | null = null; // Variable to store the previous counter value for loss detection
const previousCounter = useRef<number | null>(null); // Variable to store the previous counter value for loss detection
const [Zoom, SetZoom] = useState<number>(1); // Number of canvases
const [currentSnapshot, SetCurrentSnapshot] = useState<number>(0); // Number of canvases
const pauseRef = useRef<boolean>(true);
Expand All @@ -33,17 +33,17 @@ const DataPass = () => {
if (canvasRef.current) {
canvasRef.current.updateData(data); // Assuming data is the new data to be displayed
}
if (previousCounter !== null) {
if (previousCounter.current !== null) {
// If there was a previous counter value
const expectedCounter: number = (previousCounter + 1) % 256; // Calculate the expected counter value
const expectedCounter: number = (previousCounter.current + 1) % 256; // Calculate the expected counter value
if (data[0] !== expectedCounter) {
// Check for data loss by comparing the current counter with the expected counter
console.warn(
`Data loss detected in datapass! Previous counter: ${previousCounter}, Current counter: ${data[0]}`
);
}
}
previousCounter = data[0]; // Update the previous counter with the current counter
previousCounter.current = data[0]; // Update the previous counter with the current counter
}, []);
return (
<div className="flex flex-col h-screen m-0 p-0 bg-g ">
Expand Down
8 changes: 5 additions & 3 deletions src/components/FFT.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const FFT = forwardRef(
}
`;

let samplesReceived = 0;
const samplesReceivedRef = useRef<number>(0);
class SmoothingFilter {
private bufferSize: number;
private circularBuffers: number[][];
Expand Down Expand Up @@ -202,8 +202,8 @@ const FFT = forwardRef(
if (fftBufferRef.current[i].length > fftSize) {
fftBufferRef.current[i].shift();
}
samplesReceived++;
if (samplesReceived % sampleupdateref.current === 0) {
samplesReceivedRef.current++;
if (samplesReceivedRef.current % sampleupdateref.current === 0) {
const processedBuffer = fftBufferRef.current[i].slice(0, fftSize);
const floatInput = new Float32Array(processedBuffer);
const fftMags = fftProcessor.computeMagnitudes(floatInput);
Expand All @@ -215,6 +215,8 @@ const FFT = forwardRef(
newData[i] = smoothedMags;
return newData;
});
// prevent overflow
if (samplesReceivedRef.current > 1e9) samplesReceivedRef.current = 0;
}
}
},
Expand Down
10 changes: 6 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
Expand All @@ -35,9 +35,11 @@
"**/*.ts",
".next/types/**/*.ts",
"**/*.tsx",
"out/types/**/*.ts"
, "scripts/generateManifest.mjs" ],
"out/types/**/*.ts",
"scripts/generateManifest.mjs",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
}
7 changes: 3 additions & 4 deletions workers/indexedDBWorker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import JSZip from 'jszip';
import { toast } from "sonner";

// Global variables
let canvasCount = 0;
Expand Down Expand Up @@ -193,13 +192,13 @@ const convertToCSV = (data: any[], canvasCount: number, selectedChannels: number
const header = ["Counter", ...selectedChannels.map((channel) => `Channel${channel}`)];

// Create rows by filtering and mapping valid data
const rows = data
const rows = data
.filter((item, index) => {
// Ensure each item is an array and has valid data
if (!item || !Array.isArray(item) || item.length === 0) {
console.warn(`Skipping invalid data at index ${index}:`, item);
return false;
} ``
}
return true;
})
.map((item, index) => {
Expand Down Expand Up @@ -256,7 +255,7 @@ const saveAllDataAsZip = async (canvasCount: number, selectedChannels: number[])
}
});

toast.success("Data successfully downloaded as ZIP.");
// Worker must not access UI. Return the blob to the main thread instead.

const content = await zip.generateAsync({ type: "blob" });
return content;
Expand Down
Loading