3118485Phase perception app: tone editor, blind same/different test, cycling tone, and site buildJeremy Magland 1import { HarmonicSliders } from './HarmonicSliders'
2import { SpectrumPlot } from './SpectrumPlot'
3import { WaveformPlot } from './WaveformPlot'
4import { degToRad, randomPhasesDeg, zeros } from '../synth'
6type Props = {
7 name: 'A' | 'B'
8 otherName: 'A' | 'B'
9 color: string
10 amplitudes: number[]
11 phasesDeg: number[]
12 setPhasesDeg: (p: number[]) => void
13 onCopyFromOther: () => void
14 onPlay: () => void
15 playing: boolean
16 yMax: number
17}
19/** Editor and plots for one tone. Amplitudes are shared; only phases are edited here. */
20export function ToneColumn({ name, otherName, color, amplitudes, phasesDeg, setPhasesDeg, onCopyFromOther, onPlay, playing, yMax }: Props) {
21 const n = amplitudes.length
22 const phases = phasesDeg.map(degToRad)
23 return (
24 <section className="card tone" style={{ borderTopColor: color }}>
25 <header className="tone-header">
26 <h2 style={{ color }}>Tone {name}</h2>
27 <button className="primary" onClick={onPlay} disabled={playing}>
28 Play {name}
29 </button>
30 </header>
32 <div className="plot-label">One period of the waveform</div>
33 <WaveformPlot amplitudes={amplitudes} phases={phases} color={color} yMax={yMax} />
35 <SpectrumPlot amplitudes={amplitudes} phasesDeg={phasesDeg} color={color} />
37 <div className="controls-header">
38 <h3>Phases</h3>
39 <div className="button-row">
40 <button onClick={() => setPhasesDeg(zeros(n))}>Zero</button>
41 <button onClick={() => setPhasesDeg(randomPhasesDeg(n))}>Random</button>
42 <button onClick={onCopyFromOther}>Copy from {otherName}</button>
43 </div>
44 </div>
45 <HarmonicSliders
46 values={phasesDeg}
47 min={-180}
48 max={180}
49 step={1}
50 unit="°"
51 onChange={(k, v) => setPhasesDeg(phasesDeg.map((p, i) => (i === k ? v : p)))}
52 />
53 </section>
54 )
55}