import React from "react";
import { html } from "htm";
import * as Store from "./store.js";
import * as Tone from "./tone.js";
/** @typedef {({children, classes}:{children?:preact.ComponentChildren, classes?:string})=>preact.VNode} BasicElement */
/** @type {({children, icon, light, disabled, inactive, onClick, classes}:{children:preact.VNode, icon?:preact.VNode, light:boolean, disabled:boolean, inactive:boolean, onClick:()=>void, classes?:string})=>preact.VNode} */
export function Button({children, icon, light, disabled, inactive, onClick, classes})
{
const [FlashGet, FlashSet] = React.useState(0);
const handleClick =()=>
{
if(inactive||disabled){ return; }
FlashSet(FlashGet+1);
onClick();
};
return html`
`;
}
/** @type {BasicElement} */
export const Header =()=>
{
const [State, Dispatch] = Store.Consumer();
const grade = State.Live.Test?.Done || {Marks:0, Total:0, Score:0};
/** @type {(e:Event)=>void} */
const handleChange =(e)=> Dispatch({Name:"Test", Data:parseInt(/** @type {HTMLSelectElement}*/(e.target).value)});
return html`
`;
}
/** @type {BasicElement} */
export const Display =()=>
{
const [State, Dispatch] = Store.Consumer();
return html`
`;
};
const ErrorCol =
[30, 25, 20, 15, 10, 5, 0, -5, -10, -15 ]
const ErrorLUT = [
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00],
[0.00, 0.00, 0.00, 0.00, 0.10, 0.15, 0.10, 0.10, 0.00, 0.00],
[0.00, 0.00, 0.02, 0.05, 0.15, 0.20, 0.30, 0.15, 0.05, 0.00],
[0.00, 0.02, 0.05, 0.10, 0.20, 0.40, 0.60, 0.30, 0.05, 0.00]
];
/** @type {(inDelta:number, inPatentError:number)=>number} */
const ErrorProbability =(inDelta, inPatentError)=> ErrorLUT[inPatentError]?.[ErrorCol.indexOf(inDelta)] ?? 0;
/** @type {BasicElement} */
export const Controls =()=>
{
const [State, Dispatch] = Store.Consumer();
const [pulsedGet, pulsedSet] = React.useState(true);
const [playGet, playSet] = React.useState(0);
React.useEffect(()=>
{
/** @type {number|undefined} */
let timer;
if(playGet == 1)
{
const volNorm = (State.Stim.Value-State.Stim.Min)/(State.Stim.Max-State.Stim.Min);
Tone.Play(!pulsedGet, State.Chan.Value, Store.ColumnMapping[State.Freq.Value][0], (volNorm*0.8) + 0.1);
if(State.Live.Freq)
{
const testMark = State.Live.Freq[/** @type {"TestL"|"TestR"}*/(`Test${State.Chan.Value ? "R":"L"}`)];
const audible = State.Stim.Value >= testMark.Stim;
const errorScaled = ErrorProbability(State.Stim.Value - testMark.Stim, State.Errs);
const errorSampled = Math.random() < errorScaled;
const percieved = errorSampled ? !audible : audible;
const handler = percieved ? ()=>playSet(2) : ()=>playSet(0);
console.log("Audible:", audible, "Error Scaled:", errorScaled, "Error Sampled:", errorSampled, "Percieved", percieved);
timer = setTimeout(handler, 800 + Math.random()*1300);
}
}
return () => clearTimeout(timer);
}, [playGet]);
const classTitle = "flex-1 text-sm"
return html`
`;
};
/** @type {BasicElement} */
export const Audiogram =()=>
{
const [State] = Store.Consumer();
const testMarksL = State.Draw.TestL.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${false}/>`);
const userMarksL = State.Draw.UserL.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${false} classes=${State.Live.Mark == p.Mark ? "stroke-bold":""}/>`);
const testMarksR = State.Draw.TestR.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${true} />`);
const userMarksR = State.Draw.UserR.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${true} classes=${State.Live.Mark == p.Mark ? "stroke-bold":""}/>`);
const testLinesL = State.Draw.TestL.Paths.map( p=>html``);
const userLinesL = State.Draw.UserL.Paths.map( p=>html``);
const testLinesR = State.Draw.TestR.Paths.map( p=>html``);
const userLinesR = State.Draw.UserR.Paths.map( p=>html``);
return html`
${
State.Show.Answer && html`
`
}
${
State.Show.Cursor && html`
`
}`;
};
/** @type {BasicElement} */
export function Chart({children})
{
const [State] = Store.Consumer();
const inset = 20;
/** @type {Array} */
const rules = [];
Store.ColumnMapping.forEach(([label, position, normal])=>
{
rules.push(html`
${label}
`
);
});
for(let db = State.Stim.Min; db <= State.Stim.Max; db+=10)
{
rules.push(html`
${db}
`
);
}
return html`
Frequency in Hz
Hearing Level (dbHL)
`;
}
/** @type {Record} */
export const Glyph = {
Arrow:()=> html`
`,
X: ({children})=> html`
${children}`,
O: ({children})=> html`
${children}`,
Minus:()=>html` `,
Plus:()=>html`
`,
Null:()=>html`
`
};
/** @type {({right, response, x, y, classes}:{right:boolean, response?:boolean, x:number|string, y:number|string, classes:string})=>preact.VNode} */
export const Mark =({right, response, x, y, classes})=>
{
return html`
`;
};