diff --git a/public/assets/dark/Indexeddbdark.png b/public/assets/dark/Indexeddbdark.png deleted file mode 100644 index c0accc6..0000000 Binary files a/public/assets/dark/Indexeddbdark.png and /dev/null differ diff --git a/public/assets/dark/Webserialdark.svg b/public/assets/dark/Webserialdark.svg new file mode 100644 index 0000000..a174e54 --- /dev/null +++ b/public/assets/dark/Webserialdark.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/assets/dark/indexDBdark.svg b/public/assets/dark/indexDBdark.svg new file mode 100644 index 0000000..1fcb776 --- /dev/null +++ b/public/assets/dark/indexDBdark.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/public/assets/dark/serialdevicedark.png b/public/assets/dark/serialdevicedark.png deleted file mode 100644 index 4098fc7..0000000 Binary files a/public/assets/dark/serialdevicedark.png and /dev/null differ diff --git a/public/assets/light/Indexeddblight.png b/public/assets/light/Indexeddblight.png deleted file mode 100644 index 5d1aaad..0000000 Binary files a/public/assets/light/Indexeddblight.png and /dev/null differ diff --git a/public/assets/light/indexDBlight.svg b/public/assets/light/indexDBlight.svg new file mode 100644 index 0000000..9d9eac8 --- /dev/null +++ b/public/assets/light/indexDBlight.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/public/assets/light/serialdevicelight.png b/public/assets/light/serialdevicelight.png deleted file mode 100644 index 06fc727..0000000 Binary files a/public/assets/light/serialdevicelight.png and /dev/null differ diff --git a/public/assets/light/serialdevicelight.svg b/public/assets/light/serialdevicelight.svg new file mode 100644 index 0000000..9661ddd --- /dev/null +++ b/public/assets/light/serialdevicelight.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/app/globals.css b/src/app/globals.css index 91fd074..9ab2fc0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -99,43 +99,3 @@ border-color: hsl(var(--gray-light)); } } - -.loader { - position: fixed; - top: 70%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 9999; /* Ensure the loader is on top of other content */ -} - -.spinner { - border: 4px solid rgba(0, 0, 0, 0.1); - border-radius: 50%; - border-top: 4px solid #000; - width: 50px; - height: 50px; - animation: spin 1s linear infinite; -} - -.fade-in { - opacity: 0; - animation: fadeIn 0.9s ease-in-out forwards; -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5d67918..f12bf4e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,7 +15,7 @@ const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Chords", - description: "Web Serial based BioSignal recorder applicaion.", + description: "Web Serial based Biopotential Signal recorder applicaion.", }; const lobsterTwo = Lobster_Two({ diff --git a/src/app/page.tsx b/src/app/page.tsx index ba15632..96de939 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from "react"; import Navbar from "../components/Navbar"; import { Skeleton } from "../components/ui/skeleton"; diff --git a/src/components/Canvas.tsx b/src/components/Canvas.tsx index 7b2b070..7c84269 100644 --- a/src/components/Canvas.tsx +++ b/src/components/Canvas.tsx @@ -17,10 +17,8 @@ interface CanvasProps { canvasCount?: number; currentValue?: number; Zoom: number; -} -interface Batch { - time: number; - values: number[]; + currentSnapshot: number; + snapShotRef: React.MutableRefObject; } const Canvas = forwardRef( @@ -32,6 +30,8 @@ const Canvas = forwardRef( canvasCount = 6, // default value in case not provided currentValue = 4, Zoom, + currentSnapshot, + snapShotRef, }: CanvasProps, ref ) => { @@ -127,7 +127,7 @@ const Canvas = forwardRef( () => ({ updateData(data: number[]) { // Reset the sweep positions if the number of channels has changed - if (currentSweepPos.current.length !== numChannels) { + if (currentSweepPos.current.length !== numChannels || !pauseRef.current) { currentSweepPos.current = new Array(numChannels).fill(0); sweepPositions.current = new Array(numChannels).fill(0); } @@ -139,14 +139,14 @@ const Canvas = forwardRef( if (previousCounter !== null) { // If there was a previous counter value const expectedCounter: number = (previousCounter + 1) % 256; // Calculate the expected counter value - if (data[6] !== expectedCounter) { + 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[6]}` + `Data loss detected in canvas! Previous counter: ${previousCounter}, Current counter: ${data[0]}` ); } } - previousCounter = data[6]; // Update the previous counter with the current counter + previousCounter = data[0]; // Update the previous counter with the current counter }, }), [Zoom, numChannels, currentValue] @@ -201,7 +201,7 @@ const Canvas = forwardRef( // Append grid lines to the wrapper canvasWrapper.appendChild(gridLineX); } - const horizontalline=50; + const horizontalline = 50; for (let j = 1; j < horizontalline; j++) { const gridLineY = document.createElement("div"); gridLineY.className = "absolute bg-[rgb(128,128,128)]"; @@ -223,8 +223,8 @@ const Canvas = forwardRef( const canvas = document.createElement("canvas"); canvas.id = `canvas${i + 1}`; - canvas.width = canvasContainerRef.current.clientWidth ; - const canvasHeight = (canvasContainerRef.current.clientHeight / numChannels) ; + canvas.width = canvasContainerRef.current.clientWidth; + const canvasHeight = (canvasContainerRef.current.clientHeight / numChannels); canvas.height = canvasHeight; canvas.className = "w-full h-full block rounded-xl"; @@ -303,14 +303,11 @@ const Canvas = forwardRef( }); linesRef.current.forEach((line, i) => { - const bitsPoints = Math.pow(2, getValue(selectedBits)); // Adjust according to your ADC resolution - const yScale = 2 / bitsPoints; - const chData = (data[i] - bitsPoints / 2) * yScale; // Use a separate sweep position for each line currentSweepPos.current[i] = sweepPositions.current[i]; // Plot the new data at the current sweep position - line.setY(currentSweepPos.current[i] % line.numPoints, chData); + line.setY(currentSweepPos.current[i] % line.numPoints, data[i + 1]); // Clear the next point to create a gap (optional, for visual effect) const clearPosition = Math.ceil((currentSweepPos.current[i] + (numXRef.current / 100)) % line.numPoints); @@ -327,23 +324,15 @@ const Canvas = forwardRef( createCanvases(); }, [numChannels, theme, currentValue]); - const getValue = useCallback((bits: BitSelection): number => { - switch (bits) { - case "ten": - return 10; - case "twelve": - return 12; - case "fourteen": - return 14; - default: - return 0; // Or any other fallback value you'd like - } - }, []); const animate = useCallback(() => { - if (pauseRef.current) { + if (!pauseRef.current) { + // If paused, show the buffered data (this part runs when paused) + updatePlotSnapshot(currentSnapshot); + } else { + // If not paused, continue with normal updates (e.g., real-time plotting) wglPlots.forEach((wglp) => wglp.update()); - requestAnimationFrame(animate); + requestAnimationFrame(animate); // Continue the animation loop } }, [currentSnapshot, numXRef.current, pauseRef.current, wglPlots, Zoom]); diff --git a/src/components/Connection.tsx b/src/components/Connection.tsx index fb9b462..9bbd2d6 100644 --- a/src/components/Connection.tsx +++ b/src/components/Connection.tsx @@ -13,13 +13,20 @@ import { Trash2, Download, FileArchive, - FileDown, Pause, Play, Plus, Minus, ZoomIn, // For magnify/zoom in functionality ZoomOut, // For zoom out functionality + CircleOff, + ReplaceAll, + Heart, + Brain, + Eye, + BicepsFlexed, + ArrowRightToLine, + ArrowLeftToLine, } from "lucide-react"; import { BoardsList } from "./boards"; import { toast } from "sonner"; @@ -37,14 +44,10 @@ import { PopoverContent, PopoverTrigger, } from "../components/ui/popover"; -import { getRandomValues } from "crypto"; -interface Dataset { - id: number; - data: string[]; // Each dataset is an array of strings -} + interface ConnectionProps { onPauseChange: (pause: boolean) => void; // Callback to pass pause state to parent - dataSteam: (data: number[]) => void; + datastream: (data: number[]) => void; Connection: (isConnected: boolean) => void; selectedBits: BitSelection; setSelectedBits: React.Dispatch>; @@ -56,18 +59,24 @@ interface ConnectionProps { currentValue: number; setCurrentValue: React.Dispatch>; SetZoom: React.Dispatch>; + SetcurrentSnapshot: React.Dispatch>; + currentSnapshot: number; Zoom: number; + snapShotRef: React.RefObject; } const Connection: React.FC = ({ onPauseChange, - dataSteam, + datastream, Connection, setSelectedBits, isDisplay, setIsDisplay, setCanvasCount, canvasCount, + SetcurrentSnapshot, + currentSnapshot, + snapShotRef, SetZoom, Zoom, currentValue, @@ -87,8 +96,7 @@ const Connection: React.FC = ({ const [customTime, setCustomTime] = useState(""); // State to store the custom stop time input const [clickCount, setClickCount] = useState(0); // Track how many times the left arrow is clicked const endTimeRef = useRef(null); // Ref to store the end time of the recording - const startTimeRef = useRef(null); // Ref to store the start time of the recording - const bufferRef = useRef([]); // Ref to store the data temporary buffer during recording + const [popoverVisible, setPopoverVisible] = useState(false); const portRef = useRef(null); // Ref to store the serial port const [ifBits, setifBits] = useState("auto"); const [showAllChannels, setShowAllChannels] = useState(false); @@ -209,8 +217,8 @@ const Connection: React.FC = ({ toast.success("Recording set to no time limit"); } else { // If the time is not null, set the end time - const newEndTimeSeconds = minutes * 60; - if (newEndTimeSeconds <= elapsedTime) { + const newEndTimeSeconds = minutes * 60 * 1000; + if (newEndTimeSeconds <= recordingElapsedTime) { // Check if the end time is greater than the current elapsed time toast.error("End time must be greater than the current elapsed time"); } else { @@ -410,7 +418,7 @@ const Connection: React.FC = ({ const usbVendorId = newPortInfo.usbVendorId ?? 0; const usbProductId = newPortInfo.usbProductId ?? 0; - if (usbProductId === 29987) { + if (usbProductId === 29987|| usbProductId === 67) { baudRate = 115200; } @@ -429,7 +437,7 @@ const Connection: React.FC = ({ const portInfo = port.getInfo(); const usbProductId = portInfo.usbProductId ?? 0; - if (usbProductId === 29987) { + if (usbProductId === 29987|| usbProductId === 67) { baudRate = 115200; } @@ -440,6 +448,7 @@ const Connection: React.FC = ({ setIsConnected(true); onPauseChange(true); setIsDisplay(true); + setCanvasCount(1); isConnectedRef.current = true; portRef.current = port; @@ -449,9 +458,11 @@ const Connection: React.FC = ({ const writer = port.writable?.getWriter(); if (writer) { // Query the board for its name - // Query the board for information + // Query the board for + writerRef.current = writer; + const whoAreYouMessage = new TextEncoder().encode("WHORU\n"); - await writer.write(whoAreYouMessage); + await writerRef.current.write(whoAreYouMessage); setTimeout(() => writer.write(whoAreYouMessage), 2000); const { value, done } = await reader.read(); @@ -491,8 +502,8 @@ const Connection: React.FC = ({ const data = await getFileCountFromIndexedDB(); setDatasets(data); // Update datasets with the latest data readData(); - await navigator.wakeLock.request("screen"); + } catch (error) { await disconnectDevice(); console.error("Error connecting to device:", error); @@ -538,20 +549,25 @@ const Connection: React.FC = ({ } catch (error) { console.error("Failed to send STOP command:", error); } - writerRef.current.releaseLock(); - writerRef.current = null; + if (writerRef.current) { + writerRef.current.releaseLock(); + writerRef.current = null; + } } - + snapShotRef.current?.fill(false); if (readerRef.current) { try { await readerRef.current.cancel(); } catch (error) { console.error("Failed to cancel reader:", error); } - readerRef.current.releaseLock(); - readerRef.current = null; + if (readerRef.current) { + readerRef.current.releaseLock(); + readerRef.current = null; + } } + // Close port if (portRef.current.readable) { await portRef.current.close(); } @@ -573,6 +589,8 @@ const Connection: React.FC = ({ Connection(false); } }; + + const appliedFiltersRef = React.useRef<{ [key: number]: number }>({}); const appliedEXGFiltersRef = React.useRef<{ [key: number]: number }>({}); const [, forceUpdate] = React.useReducer((x) => x + 1, 0); @@ -688,18 +706,30 @@ const Connection: React.FC = ({ // Validate the packet by checking the sync and end bytes const packet = buffer.slice(syncIndex, syncIndex + PACKET_LENGTH); // Extract the packet from the buffer const channelData: number[] = []; // Array to store the extracted channel data - for (let channel = 0; channel < NUM_CHANNELS; channel++) { - // Loop through each channel in the packet - const highByte = packet[channel * 2 + HEADER_LENGTH]; // Extract the high byte for the channel - const lowByte = packet[channel * 2 + HEADER_LENGTH + 1]; // Extract the low byte for the channel - const value = (highByte << 8) | lowByte; // Combine high and low bytes to get the channel value - channelData.push(value); // Convert the value to string and store it in the array - } const counter = packet[2]; // Extract the counter value from the packet channelData.push(counter); // Add the counter to the channel data - dataSteam(channelData); // Pass the channel data to the LineData function for further processing + for (let channel = 0; channel < NUM_CHANNELS; channel++) { + const highByte = packet[channel * 2 + HEADER_LENGTH]; + const lowByte = packet[channel * 2 + HEADER_LENGTH + 1]; + const value = (highByte << 8) | lowByte; + + channelData.push( + notchFilters[channel].process( + EXGFilters[channel].process( + value, + appliedEXGFiltersRef.current[channel] + ), + appliedFiltersRef.current[channel] + ) + ); + } + datastream(channelData); // Pass the channel data to the LineData function for further processing if (isRecordingRef.current) { + const channeldatavalues = channelData + .slice(0, canvasnumbersRef.current + 1) + .map((value) => (value !== undefined ? value : null)) + .filter((value): value is number => value !== null); // Filter out null values // Check if recording is enabled recordingBuffers[activeBufferIndex][fillingindex.current] = channeldatavalues; @@ -798,22 +828,22 @@ const Connection: React.FC = ({ const deleteFilesByFilename = async (filename: string) => { try { - const dbRequest = indexedDB.open("adcReadings"); + const dbRequest = indexedDB.open("ChordsRecordings"); dbRequest.onsuccess = (event) => { const db = (event.target as IDBOpenDBRequest).result; - const transaction = db.transaction("adcReadings", "readwrite"); - const store = transaction.objectStore("adcReadings"); - - // Check if the index exists - if (!store.indexNames.contains("sessionId")) { - throw new Error("Index 'sessionId' does not exist."); + const transaction = db.transaction("ChordsRecordings", "readwrite"); + const store = transaction.objectStore("ChordsRecordings"); + + // Check if the "filename" index exists + if (!store.indexNames.contains("filename")) { + console.error("Index 'filename' does not exist."); + toast.error("Unable to delete files: index not found."); + return; } - const index = store.index("sessionId"); - - // Open cursor with KeyRange - const deleteRequest = index.openCursor(IDBKeyRange.only(sessionId)); + const index = store.index("filename"); + const deleteRequest = index.openCursor(IDBKeyRange.only(filename)); // Make this callback async deleteRequest.onsuccess = async (event) => { @@ -831,7 +861,8 @@ const Connection: React.FC = ({ }; deleteRequest.onerror = () => { - throw new Error("Failed to delete data."); + console.error("Error during delete operation."); + toast.error("Failed to delete the file. Please try again."); }; transaction.oncomplete = () => { @@ -839,16 +870,18 @@ const Connection: React.FC = ({ }; transaction.onerror = () => { - throw new Error("Transaction failed."); + console.error("Transaction failed during deletion."); + toast.error("Failed to delete the file. Please try again."); }; }; dbRequest.onerror = () => { - throw new Error("Failed to open database."); + console.error("Failed to open IndexedDB database."); + toast.error("An error occurred while accessing the database."); }; } catch (error) { - // Handle errors and show toast notification if needed - toast.error("An error occurred during deletion."); + console.error("Error occurred during file deletion:", error); + toast.error("An unexpected error occurred. Please try again."); } }; @@ -925,7 +958,7 @@ const Connection: React.FC = ({
@@ -1019,7 +1052,7 @@ const Connection: React.FC = ({ - {/* Zoom */} + {/* Autoscale/Bit selection */} {isConnected && ( @@ -1030,7 +1063,7 @@ const Connection: React.FC = ({ @@ -1048,7 +1081,6 @@ const Connection: React.FC = ({ @@ -1066,7 +1098,7 @@ const Connection: React.FC = ({ - - -

- {isDisplay ? "Pause Data Display" : "Resume Data Display"} -

-
-
-
- )} +
+ + + + + + + +

+ {isDisplay ? "Pause Data Display" : "Resume Data Display"} +

+
+
+
+ +
+ )} {/* Record button with tooltip */} {isConnected && ( @@ -1155,12 +1202,15 @@ const Connection: React.FC = ({
+ {/* Save file by filename */} + + {/* Delete file by filename */}
- ))} + )) + ) : ( +

No datasets available

+ )} + + + {/* Download all as ZIP and delete all options */} + {datasets.length > 0 && (
-
- - - )} + )} + + + )} + {isConnected && ( + + + + + +
+
+ {/* Filter Name */} +
+ {/* Buttons */} +
+
+ + +
+
+ + + + +
+
+
+
+ {["CH1", "CH2", "CH3", "CH4", "CH5", "CH6"].map((filterName, index) => ( +
+ {/* Filter Name */} +
{filterName}
+ {/* Buttons */} +
+
+ + + + + +
+
+ + + +
+
+
+ ))} +
+
+
+
+ )} + {/* Canvas control buttons with tooltip */} {isConnected && ( @@ -1250,14 +1545,14 @@ const Connection: React.FC = ({

- {canvasCount >= 6 + {canvasCount >= maxCanvasCountRef.current ? "Maximum Channels Reached" : "Increase Channel"}

diff --git a/src/components/Contributors.tsx b/src/components/Contributors.tsx index b4e0eb8..66985e0 100644 --- a/src/components/Contributors.tsx +++ b/src/components/Contributors.tsx @@ -11,15 +11,15 @@ import { CardHeader, CardTitle, } from "../components/ui/card"; -import { Separator } from "@/components/ui/separator"; +import { Separator } from "../components/ui/separator"; import { Avatar, AvatarFallback, AvatarImage } from "../components/ui/avatar"; import { Dialog, DialogContent, DialogTrigger } from "../components/ui/dialog"; import { CircleAlert } from "lucide-react"; import { Button } from "../components/ui/button"; import Link from "next/link"; -import { VERSION } from "../../Version"; import Chords from "./LandingComp/Chords"; import { Badge } from "./ui/badge"; +import packageJson from '../../package.json'; const contributors = [ { @@ -72,7 +72,7 @@ export default function Contributors() { - v{VERSION} + v{packageJson.version}

Contributors

diff --git a/src/components/DataPass.tsx b/src/components/DataPass.tsx index 0550ffc..9c1b816 100644 --- a/src/components/DataPass.tsx +++ b/src/components/DataPass.tsx @@ -2,11 +2,11 @@ import Connection from "./Connection"; import Steps from "./Steps"; -import React, { useState ,useCallback,useRef} from "react"; +import React, { useState, useCallback, useRef } from "react"; import Canvas from "./Canvas"; import Navbar from "./Navbar"; // Import the Navbar -export type BitSelection = "ten" | "twelve" | "fourteen" | "auto"; +export type BitSelection = "ten" | "twelve" | "fourteen" | "sixteen" | "auto"; const DataPass = () => { const [selectedBits, setSelectedBits] = useState("auto"); // Selected bits @@ -18,12 +18,13 @@ const DataPass = () => { const canvasRef = useRef(null); // Create a ref for the Canvas component let previousCounter: number | null = null; // Variable to store the previous counter value for loss detection const [Zoom, SetZoom] = useState(1); // Number of canvases + const [currentSnapshot, SetcurrentSnapshot] = useState(0); // Number of canvases const pauseRef = useRef(true); const handlePauseChange = (newPauseState: boolean) => { pauseRef.current = newPauseState; }; - - const dataSteam = useCallback((data: number[]) => { + const snapShotRef = useRef(Array(6).fill(false)); + const datastream = useCallback((data: number[]) => { if (canvasRef.current) { canvasRef.current.updateData(data); // Assuming data is the new data to be displayed @@ -31,25 +32,27 @@ const DataPass = () => { if (previousCounter !== null) { // If there was a previous counter value const expectedCounter: number = (previousCounter + 1) % 256; // Calculate the expected counter value - if (data[6] !== expectedCounter) { + 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[6]}` + `Data loss detected in datapass! Previous counter: ${previousCounter}, Current counter: ${data[0]}` ); } } - previousCounter =data[6]; // Update the previous counter with the current counter + previousCounter = data[0]; // Update the previous counter with the current counter }, []); return (
-
- +
+
{isConnected ? ( { )} { currentValue={currentValue} channelCount={channelCount} SetZoom={SetZoom} + SetcurrentSnapshot={SetcurrentSnapshot} + currentSnapshot={currentSnapshot} Zoom={Zoom} />
); }; -export default DataPass; +export default DataPass; \ No newline at end of file diff --git a/src/components/LandingComp/HeadSection.tsx b/src/components/LandingComp/HeadSection.tsx index ad4b07a..df35fe8 100644 --- a/src/components/LandingComp/HeadSection.tsx +++ b/src/components/LandingComp/HeadSection.tsx @@ -96,14 +96,14 @@ const HeadSection: React.FC = () => { fill="none" className="stroke-gray-600" - stroke-width="2" + strokeWidth="2" d="m 0,151.91036 c 1.4089684,-1.39202 2.8179419,-2.78405 4.4681851,-4.13231 1.6502432,-1.34827 3.5416817,-2.65272 5.8528189,-3.58815 2.311136,-0.93544 5.041824,-1.50181 8.026352,-1.9935 2.984527,-0.49169 6.222708,-0.90867 8.694494,-0.94351 2.471786,-0.0348 4.177047,0.31246 6.199305,0.0836 2.022258,-0.2289 4.361387,-1.03398 6.832335,-1.86309 2.470949,-0.82911 5.073566,-1.6822 7.177224,-2.40615 2.103658,-0.72394 3.708237,-1.31869 5.762729,-1.5884 2.054492,-0.26971 4.558759,-0.21435 6.498614,-0.41988 1.939854,-0.20554 3.31519,-0.67193 4.602769,-1.82816 1.287578,-1.15623 2.487324,-3.0022 3.530011,-4.75273 1.042687,-1.75054 1.928253,-3.40553 2.930166,-5.06322 1.001914,-1.65768 2.120116,-3.31797 3.025582,-4.36086 0.905467,-1.0429 1.598146,-1.46835 2.340165,-1.38478 0.742018,0.0836 1.533331,0.67613 2.358804,1.80648 0.825474,1.13035 1.685057,2.7984 2.532417,4.6308 0.847361,1.8324 1.682448,3.82904 2.421924,5.5969 0.739476,1.76787 1.383299,3.30686 2.169375,4.95085 0.786076,1.64399 1.714361,3.39289 2.473003,4.77641 0.758643,1.38353 1.347608,2.40162 1.833436,3.04871 0.485827,0.64708 0.868495,0.92314 1.238392,0.75947 0.369896,-0.16366 0.727004,-0.76703 1.059198,-2.09126 0.332194,-1.32423 0.639456,-3.36923 0.928193,-6.1011 0.288738,-2.73188 0.558934,-6.15045 0.868146,-10.64747 0.309213,-4.49703 0.657424,-10.07222 0.926815,-14.56848 0.269391,-4.49626 0.459946,-7.91333 0.564546,-11.864862 0.1046,-3.951531 0.12324,-8.437265 0.24807,-12.319313 0.12483,-3.882048 0.355841,-7.160189 0.594626,-11.246823 0.238785,-4.086634 0.485328,-8.9815 0.868959,-15.227791 0.383631,-6.246291 0.904324,-13.843606 1.270866,-20.242696 0.366542,-6.399089 0.578914,-11.599592 0.825642,-15.273484 0.246728,-3.673892 0.527796,-5.820983 0.743951,-7.450531 0.216157,-1.629547 0.367387,-2.741464 0.700637,-2.697678 0.33326,0.04379 0.84851,1.24325 1.2553,3.918753 0.40679,2.675504 0.70509,6.826856 1.04777,11.706554 0.34268,4.879699 0.72972,10.487438 1.10576,17.957982 0.37604,7.470544 0.74105,16.803407 1.07762,23.817415 0.33657,7.014009 0.64468,11.708788 0.82928,17.391016 0.18459,5.682228 0.24566,12.351548 0.46814,17.257868 0.22247,4.90633 0.60634,8.04941 0.98347,14.22494 0.37714,6.17552 0.74752,15.38307 1.21736,22.10594 0.46984,6.72286 1.0391,10.9607 1.5022,14.53279 0.46309,3.57209 0.81999,6.47823 1.16662,8.36498 0.34663,1.88676 0.68298,2.75404 1.12462,3.00595 0.44163,0.25191 0.98851,-0.11155 1.55014,-1.28942 0.56162,-1.17787 1.13795,-3.17006 1.93868,-6.87995 0.80073,-3.7099 1.8258,-9.13725 2.80452,-13.56605 0.97872,-4.4288 1.91103,-7.85881 2.64362,-10.29296 0.73259,-2.43415 1.26542,-3.87232 1.8567,-4.82241 0.59128,-0.95008 1.24096,-1.41204 1.86394,-1.51639 0.62298,-0.10435 1.21922,0.1489 1.83281,0.44756 0.61359,0.29866 1.24449,0.64271 2.08275,0.52793 0.83825,-0.11477 1.8838,-0.68834 3.30454,-3.03162 1.42075,-2.34328 3.21659,-6.45608 4.97147,-10.99867 1.75487,-4.5426 3.46867,-9.5147 5.00259,-14.00225 1.53392,-4.487552 2.88786,-8.490284 3.84014,-11.102991 0.95227,-2.612708 1.50283,-3.835259 2.05443,-4.712306 0.5516,-0.877046 1.1042,-1.408539 1.69746,-1.334752 0.59325,0.07379 1.22712,0.752841 1.96808,3.003871 0.74096,2.25103 1.58897,6.073869 2.593,11.707598 1.00403,5.63373 2.16403,13.07798 3.08806,19.31281 0.92404,6.23483 1.61206,11.25988 2.21371,15.2664 0.60166,4.00651 1.11692,6.99427 1.75325,9.80203 0.63632,2.80776 1.39367,5.43538 1.97584,7.2456 0.58217,1.81022 0.98912,2.80296 1.47237,3.45171 0.48326,0.64874 1.04278,0.95346 1.63677,0.97115 0.594,0.0177 1.22243,-0.25167 2.1727,-1.24317 0.95027,-0.99151 2.22232,-2.7051 3.44483,-4.26899 1.22252,-1.56389 2.39544,-2.97799 3.40604,-3.97841 1.01059,-1.00043 1.85881,-1.58714 2.77056,-1.68394 0.91175,-0.0968 1.88699,0.29633 2.90814,0.44724 1.02115,0.15092 2.08817,0.0596 3.23285,-0.28694 1.14467,-0.34656 2.36694,-0.94838 3.30891,-1.56253 0.94197,-0.61415 1.60358,-1.24061 2.73305,-1.58523 1.12946,-0.34462 2.7267,-0.40738 4.59882,-0.45684 1.87212,-0.0495 4.01899,-0.0856 5.61794,-0.33548 1.59894,-0.24987 2.64985,-0.71344 4.05227,-1.34899 1.40243,-0.63555 3.15626,-1.44303 4.57184,-2.08836 1.41558,-0.64532 2.49284,-1.12845 3.50633,-1.17734 1.01349,-0.0489 1.96316,0.33645 2.80166,0.63525 0.8385,0.2988 1.56578,0.51103 2.44837,0.44262 0.88259,-0.0684 1.92045,-0.41748 3.38571,-1.34269 1.46526,-0.92521 3.35783,-2.4265 5.3355,-4.3712 1.97767,-1.9447 4.04031,-4.33268 5.83434,-6.78245 1.79402,-2.44977 3.31932,-4.96116 4.37436,-6.58675 1.05504,-1.62559 1.63977,-2.3653 2.36626,-2.17635 0.72648,0.18895 1.59468,1.30651 3.10385,4.57937 1.50918,3.27286 3.65924,8.70078 5.15171,12.31497 1.49246,3.61419 2.32727,5.41448 3.12153,6.78414 0.79427,1.36965 1.54795,2.30859 2.12408,2.69433 0.57613,0.38575 0.97468,0.21828 1.42355,-1.34404 0.44887,-1.56231 0.94803,-4.51936 1.31556,-7.8305 0.36753,-3.31114 0.60341,-6.97619 0.78225,-10.03692 0.17885,-3.06074 0.30064,-5.517 0.64367,-10.56359 0.34303,-5.04659 0.90729,-12.68316 1.39442,-22.294541 0.48713,-9.61138 0.89712,-21.196955 1.46027,-33.478869 0.56314,-12.281913 1.2794,-25.259415 1.86178,-33.139148 0.58239,-7.879733 1.03085,-10.661327 1.47898,-10.901076 0.44813,-0.239748 0.89588,2.062317 1.54612,8.734983 0.65023,6.672666 1.5029,17.71545 2.13378,27.18548 0.63089,9.47003 1.03996,17.36677 1.53719,28.534393 0.49724,11.167623 1.0826,25.605398 1.74802,38.164268 0.66541,12.55887 1.41083,23.23812 1.98819,30.44353 0.57737,7.2054 0.98665,10.9366 1.54054,13.67813 0.5539,2.74152 1.25237,4.49324 2.05864,4.40281 0.80627,-0.0904 1.72029,-2.02295 2.6288,-5.20942 0.9085,-3.18647 1.81143,-7.62665 3.10339,-12.69331 1.29196,-5.06666 2.97286,-10.75947 4.11884,-13.65377 1.14598,-2.8943 1.75697,-2.98997 2.8881,-3.31013 1.13112,-0.32016 2.7823,-0.86478 4.6565,-2.78114 1.87421,-1.91637 3.97131,-5.20432 6.4053,-10.96413 2.43398,-5.7598 5.2047,-13.99105 7.02612,-19.629221 1.82141,-5.63817 2.69342,-8.682969 3.47491,-10.608802 0.78148,-1.925834 1.47239,-2.732606 2.18342,-2.516723 0.71102,0.215884 1.44211,1.454392 2.5786,5.791765 1.13649,4.337374 2.6783,11.773291 3.65564,17.530481 0.97734,5.75718 1.39015,9.83531 1.95739,14.54295 0.56724,4.70763 1.28887,10.04447 1.91043,13.98867 0.62157,3.9442 1.14304,6.49556 1.65044,8.66736 0.5074,2.17179 1.0007,3.96389 1.61637,5.524 0.61567,1.56011 1.35368,2.88814 2.2153,3.39299 0.86163,0.50485 1.84681,0.1865 3.35108,-1.06931 1.50427,-1.25582 3.52753,-3.44901 5.62056,-5.21377 2.09304,-1.76475 4.25573,-3.10097 6.08898,-3.92807 1.83324,-0.82709 3.33693,-1.14501 5.23016,-0.94456 1.89323,0.20045 4.17588,0.91925 6.15206,1.00161 1.97618,0.0823 3.64579,-0.47173 5.13936,-1.09221 1.49356,-0.62047 2.811,-1.30729 4.1581,-1.79069 1.3471,-0.48339 2.72378,-0.76333 3.86259,-0.60563 1.13882,0.1577 2.0397,0.75302 3.5206,0.93945 1.4809,0.18643 3.54172,-0.036 5.14366,-0.47132 1.60194,-0.43528 2.74492,-1.08334 3.88793,-1.73141"> {/* */} {/* */} - + { const [currentIndex, setCurrentIndex] = useState(0); const [isPaused, setIsPaused] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); - const [fadeIn, setFadeIn] = useState(false); // State to control fade-in animation + useEffect(() => { // Declare the interval variable with a clear type. @@ -50,12 +50,6 @@ const CardSlider = () => { }; }, [isPaused, cards.length]); // Dependency array now includes isPaused. - useEffect(() => { - // Trigger fade-in animation on image change - setFadeIn(true); - const timer = setTimeout(() => setFadeIn(false), 0); // Reset after animation completes - return () => clearTimeout(timer); // Cleanup timer - }, [currentIndex]); const currentCard = cards[currentIndex]; const handleImageClick = () => { @@ -81,7 +75,7 @@ const CardSlider = () => {
{/* Progress Line and Steps for Small and Medium Screens */} -
+
    {Array.from({ length: 4 }).map((_, index) => (
  1. @@ -118,13 +112,12 @@ const CardSlider = () => {
    {/* Image */}
    - {/* Apply fade-in animation conditionally */} {currentCard.title} setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} onClick={handleImageClick} @@ -132,7 +125,7 @@ const CardSlider = () => {
    {/* Index Sidebar for Large Screens */} -
    +
      {Array.from({ length: 4 }).map((_, index) => (
    1. @@ -152,16 +145,16 @@ const CardSlider = () => { {/* Modal for Enlarged Image */} {isModalOpen && (
      -
      +
      {currentCard.title} setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} onClick={handleImageClick} diff --git a/src/components/LandingComp/TechStack.tsx b/src/components/LandingComp/TechStack.tsx index 24bcb58..65b2d3e 100644 --- a/src/components/LandingComp/TechStack.tsx +++ b/src/components/LandingComp/TechStack.tsx @@ -43,8 +43,8 @@ const Stack = () => { name: "Web Serial API", logo: theme === "light" - ? "./assets/dark/serialdevicedark.png" - : "./assets/light/serialdevicelight.png", + ? "./assets/dark/Webserialdark.svg" + : "./assets/light/serialdevicelight.svg", url: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API", description: "For connecting to the serial port of the device.", }, @@ -52,8 +52,8 @@ const Stack = () => { name: "IndexedDB API", logo: theme === "light" - ? "./assets/dark/Indexeddbdark.png" - : "./assets/light/Indexeddblight.png", + ? "./assets/dark/indexDBdark.svg" + : "./assets/light/indexDBlight.svg", url: "https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API", description: "IndexedDB is a low-level API for client-side storage.", }, @@ -124,4 +124,4 @@ const Stack = () => { ); }; -export default Stack; +export default Stack; \ No newline at end of file diff --git a/src/components/filters.tsx b/src/components/filters.tsx new file mode 100644 index 0000000..a2d4ebc --- /dev/null +++ b/src/components/filters.tsx @@ -0,0 +1,256 @@ +// TypeScript filter classes for Chords +// Made with <3 at Upside Down labs +// Author: Aman Maheshwari +// +// Reference: +// https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html +// https://courses.ideate.cmu.edu/16-223/f2020/Arduino/FilterDemos/filter_gen.py +// +// Note: +// filter_gen.py provides C/C++ type functions which we have converted to TS + +//Notch Filter 50Hz/60Hz +export class EXGFilter { + // Properties to hold the state of the filter + private z1: number; + private z2: number; + private x1: number; + private x2: number; + private x3: number; + private x4: number; + private sample: string | null; + private bitsPoints: number; + private yScale: number; + + + constructor() { + // Initialize state variables + this.z1 = 0; + this.z2 = 0; + this.x1 = 0; + this.x2 = 0; + this.x3 = 0; + this.x4 = 0; + this.sample = null; + this.bitsPoints=0; + this.yScale=0; + } + //sample- + //1.500 + //2.250 + //TYPE- + //1.ECG + //2.EOG + //3.EEG + //4.EMG + // function to apply the + setSample(sample: string): void { + this.sample = sample; + this.bitsPoints = Math.pow(2, + sample === "sixteen" ?16 : + sample === "fourteen" ? 14 : + sample === "twelve" ? 12 : 10 + ); // Adjust according to your ADC resolution + this.yScale = 2 / this.bitsPoints; + } + + process(input: number, type: number): number { + if(!type) return (input - this.bitsPoints / 2) * this.yScale; + let output = input; + let chData=0; + switch (this.sample) { + //samplerate 500Hz + case "sixteen": + case "fourteen": + case "twelve": // 500Hz + switch (type) { + case 1: // ECG Sampling rate: 500.0 Hz, frequency: 30.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x1 = output - (-1.47548044 * this.z1) - (0.58691951 * this.z2); + output = 0.02785977 * this.x1 + 0.05571953 * this.z1 + 0.02785977 * this.z2; + this.z2 = this.z1; + this.z1 = this.x1; + chData = (output - this.bitsPoints / 2) * this.yScale; + break; + case 2: // EOG Sampling rate: 500.0 Hz, frequency: 10.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x2 = output - (-1.82269493 * this.z1) - (0.83718165 * this.z2); + output = 0.00362168 * this.x2 + 0.00724336 * this.z1 + 0.00362168 * this.z2; + this.z2 = this.z1; + this.z1 = this.x2; + chData = (output - this.bitsPoints / 2) * this.yScale; + break; + case 3: // EEG Sampling rate: 500.0 Hz, frequency: 45.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x3 = output - (-0.51930341 * this.z1) - (0.21965398 * this.z2); + output = 0.17508764 * this.x3 + 0.35017529 * this.z1 + 0.17508764 * this.z2; + this.z2 = this.z1; + this.z1 = this.x3; + chData = (output - this.bitsPoints / 2) * this.yScale; + break; + case 4: // EMG Sampling rate: 500.0 Hz, frequency: 70.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x4 = output - (-0.82523238 * this.z1) - (0.29463653 * this.z2); + output = 0.52996723 * this.x4 + -1.05993445 * this.z1 + 0.52996723 * this.z2; + this.z2 = this.z1; + this.z1 = this.x4; + chData = output * this.yScale; + break; + default: + break; + } + break; + case "ten": + //samplerate 250Hz + switch (type) { + case 1: // ECG Sampling rate: 250.0 Hz, frequency: 30.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x1 = output - -0.98240579 * this.z1 - 0.34766539 * this.z2; + output = 0.09131490 * this.x1 + 0.18262980 * this.z1 + 0.09131490 * this.z2; + this.z2 = this.z1; + this.z1 = this.x1; + chData = (output - this.bitsPoints / 2) * this.yScale; + break; + + case 2: // EOG Sampling rate: 250.0 Hz, frequency: 10.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x2 = output - -1.64745998 * this.z1 - 0.70089678 * this.z2; + output = 0.01335920 * this.x2 + 0.02671840 * this.z1 + 0.01335920 * this.z2; + this.z2 = this.z1; + this.z1 = this.x2; + chData = (output - this.bitsPoints / 2) * this.yScale; + break; + + case 3: // EEG Sampling rate: 250.0 Hz, frequency: 45.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x3 = output - -0.51930341 * this.z1 - 0.21965398 * this.z2; + output = 0.17508764 * this.x3 + 0.35017529 * this.z1 + 0.17508764 * this.z2; + this.z2 = this.z1; + this.z1 = this.x3; + chData = (output - this.bitsPoints / 2) * this.yScale; + break; + + case 4: // EMG Sampling rate: 250.0 Hz, frequency: 70.0 Hz. + // Filter is order 2, implemented as second-order sections (biquads). + this.x4 = output - 0.22115344 * this.z1 - 0.18023207 * this.z2; + output = 0.23976966 * this.x4 + -0.47953932 * this.z1 + 0.23976966 * this.z2; + this.z2 = this.z1; + this.z1 = this.x4; + chData = output * this.yScale; + break; + + default: + break; + } + break; + default: + break; + } + return chData; + } +} + +export class Notch { + // Properties to hold the state of the filter sections + private z1_1: number; + private z2_1: number; + private z1_2: number; + private z2_2: number; + private x_1: number; + private x_2: number; + private sample: string | null; + + constructor() { + // Initialize state variables for both filter sections + this.z1_1 = 0; + this.z2_1 = 0; + this.z1_2 = 0; + this.z2_2 = 0; + this.x_1 = 0; + this.x_2 = 0; + this.sample = null; + } + + setSample(sample: string): void { + this.sample = sample; + } + + // Method to apply the filter + process(input: number, type: number): number { + if(!type) return input; + let output = input; + switch (this.sample) { + case "sixteen" : + case "fourteen": // 500Hz + case "twelve": // 500Hz + switch (type) { + case 1: // Notch Sampling rate: 500.0 Hz, frequency: [48.0, 52.0] Hz. + this.x_1 = output - (-1.56858163 * this.z1_1) - (0.96424138 * this.z2_1); + output = 0.96508099 * this.x_1 + -1.56202714 * this.z1_1 + 0.96508099 * this.z2_1; + this.z2_1 = this.z1_1; + this.z1_1 = this.x_1; + // Second filter section + this.x_2 = output - (-1.61100358 * this.z1_2) - (0.96592171 * this.z2_2); + output = 1.0 * this.x_2 + -1.61854514 * this.z1_2 + 1.0 * this.z2_2; + this.z2_2 = this.z1_2; + this.z1_2 = this.x_2; + break; + case 2: // Notch Sampling rate: 500.0 Hz, frequency: [58.0, 62.0] Hz. + this.x_1 = output - (-1.40810535 * this.z1_1) - (0.96443153 * this.z2_1); + output = 0.96508099 * this.x_1 + (-1.40747202 * this.z1_1) + (0.96508099 * this.z2_1); + this.z2_1 = this.z1_1; + this.z1_1 = this.x_1; + // Second filter section + this.x_2 = output - (-1.45687509 * this.z1_2) - (0.96573127 * this.z2_2); + output = 1.00000000 * this.x_2 + (-1.45839783 * this.z1_2) + (1.00000000 * this.z2_2); + this.z2_2 = this.z1_2; + this.z1_2 = this.x_2; + break; + default: + break; + } + break; + + case "ten": // 250Hz + switch (type) { + case 1: // Notch Sampling rate: 250.0 Hz, frequency: [48.0, 52.0] Hz. + this.x_1 = output - (-0.53127491 * this.z1_1) - (0.93061518 * this.z2_1); + output = 0.93137886 * this.x_1 + (-0.57635175 * this.z1_1) + 0.93137886 * this.z2_1; + this.z2_1 = this.z1_1; + this.z1_1 = this.x_1; + + // Second filter section + this.x_2 = output - (-0.66243374 * this.z1_2) - (0.93214913 * this.z2_2); + output = 1.00000000 * this.x_2 + (-0.61881558 * this.z1_2) + 1.00000000 * this.z2_2; + this.z2_2 = this.z1_2; + this.z1_2 = this.x_2; + break; + + case 2: // Notch Sampling rate: 250.0 Hz, frequency: [58.0, 62.0] Hz. + this.x_1 = output - (-0.05269865 * this.z1_1) - (0.93123336 * this.z2_1); + output = 0.93137886 * this.x_1 + (-0.11711144 * this.z1_1) + 0.93137886 * this.z2_1; + this.z2_1 = this.z1_1; + this.z1_1 = this.x_1; + + // Second filter section + this.x_2 = output - (-0.18985625 * this.z1_2) - (0.93153034 * this.z2_2); + output = 1.00000000 * this.x_2 + (-0.12573985 * this.z1_2) + 1.00000000 * this.z2_2; + this.z2_2 = this.z1_2; + this.z1_2 = this.x_2; + break; + + default: + break; + } + break; + + default: + break; + } + + return output; + } +} + + +