Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 changes: 2 additions & 1 deletion packages/key-finder-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"copy:html": "cp src/index.html dist/index.html",
"copy:favicon": "cp src/favicon.ico dist/favicon.ico",
"build:release": "yarn clean && yarn build && yarn copy:html && yarn copy:favicon",
"serve": "serve ./dist -l 3000 -s"
"serve": "serve ./dist -l 3000 -s",
"dev": "rollup -c --watch & serve ./dist -l 3000 -s"
},
"dependencies": {
"@rollup/plugin-node-resolve": "15.2.3",
Expand Down
6 changes: 6 additions & 0 deletions packages/key-finder-web/src/LiveDetection/LiveDetection.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
width: 100%;
}

.live-detection__output-container {
display: flex;
align-items: center;
align-content: center;
}

@media (max-width: 440px) {
.live-detection__container {
grid-template-columns: 1fr;
Expand Down
46 changes: 39 additions & 7 deletions packages/key-finder-web/src/LiveDetection/LiveDetection.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { h, createRef, Fragment, Component } from 'preact';
import { h, createRef, Fragment, Component, JSX } from 'preact';
import { audioUtils, keyFinderUtils } from '../Utils';
import CircleOfFifths from '../CircleOfFifths';
import { keysNotation } from '../defaults';
import theme from '../theme';

import GainControl from './components/GainControl';
import './LiveDetection.css';

const WIDTH = 200;
Expand All @@ -13,6 +13,7 @@ class LiveDetection extends Component {
audioContext: AudioContext | null = null;
recorder: RecorderWorkletNode | null = null;
levelAnalyzer: AudioAnalyzerNode | null = null;
gainNode: GainNode | null = null;
keyAnalyzer: Worker | null = null;
sampleRate: number | null = null;
canvas = createRef();
Expand All @@ -24,6 +25,7 @@ class LiveDetection extends Component {
analyzing: false,
result: null,
error: null,
gain: 0.0,
};

componentDidMount() {
Expand Down Expand Up @@ -75,13 +77,16 @@ class LiveDetection extends Component {

this.recorder = await audioUtils.createRecordingDevice(this.audioContext);
this.levelAnalyzer = audioUtils.createAnalyserDevice(this.audioContext);
this.gainNode = audioUtils.createGainNode(this.audioContext);
this.dataArray = audioUtils.createDataArrayForAnalyzerDevice(
this.levelAnalyzer
);
this.canvasContext = this.canvas.current.getContext('2d');

audioUtils.connectAudioNodes(source, this.recorder);
audioUtils.connectAudioNodes(source, this.levelAnalyzer);
audioUtils.connectAudioNodes(source, this.gainNode);
const postGainSource = this.gainNode;
audioUtils.connectAudioNodes(postGainSource, this.recorder);
audioUtils.connectAudioNodes(postGainSource, this.levelAnalyzer);

this.drawLevelAnalysis();

Expand Down Expand Up @@ -154,7 +159,23 @@ class LiveDetection extends Component {
.setValueAtTime(0, contextTime + 0.1);
};

render({}, { connected, analyzing, result, error }) {
updateGain = (event: JSX.TargetedEvent<HTMLInputElement, Event>) => {
if (!this.gainNode) return;
const newGain = Number(event.currentTarget.value);
this.setState({ gain: newGain });
const gainValue = 10 ** newGain;
this.gainNode.gain.value = gainValue;
};

resetGain = () => {
this.setState({ gain: 0 });
this.gainNode.gain.value = 1;
};

render({}, { connected, analyzing, result, error, gain }) {
const formattedGain = gain === 0 ? 0 : (10 ** gain).toFixed(2);
const sign = gain >= 0 ? '+' : '-';

return (
<div class="live-detection-page">
{error && <h1>{error}</h1>}
Expand Down Expand Up @@ -191,12 +212,23 @@ class LiveDetection extends Component {
disabled={!analyzing}
/>
</div>
<div>
<div className="live-detection__output-container">
{connected && (
<GainControl
gain={gain}
updateGain={this.updateGain}
resetGain={this.resetGain}
/>
)}
<canvas
width={WIDTH}
height={HEIGHT}
ref={this.canvas}
style={{ width: WIDTH, height: HEIGHT }}
style={{
width: WIDTH,
height: HEIGHT,
display: connected ? 'block' : 'none',
}}
/>
</div>
<div style={{ height: '2rem' }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
.gain-input {
display: flex;
flex-direction: column;
max-width: 10rem;
align-items: center;
}

.gain-input label {
width: 100px;
margin-bottom: 20px;
text-align: center;
}

.range-input {
-webkit-appearance: none;
appearance: none;
width: 100%;
appearance: slider-vertical;
-webkit-appearance: slider-vertical;
cursor: pointer;
outline: none;
border-radius: 15px;
height: 6px;
background: #ccc;
height: 200px;
width: 100px;
}

.range-input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
height: 15px;
width: 15px;
background-color: #f50;
border-radius: 50%;
border: none;
transition: 0.2s ease-in-out;
}

.range-input::-moz-range-thumb {
height: 15px;
width: 15px;
background-color: #f50;
border-radius: 50%;
border: none;
transition: 0.2s ease-in-out;
}

.range-input::-webkit-slider-thumb:hover {
box-shadow: 0 0 0 10px rgba(255, 85, 0, 0.1);
}

.range-input:active::-webkit-slider-thumb {
box-shadow: 0 0 0 13px rgba(255, 85, 0, 0.2);
}

.range-input:focus::-webkit-slider-thumb {
box-shadow: 0 0 0 13px rgba(255, 85, 0, 0.2);
}

.range-input::-moz-range-thumb:hover {
box-shadow: 0 0 0 10px rgba(255, 85, 0, 0.1);
}

.range-input:active::-moz-range-thumb {
box-shadow: 0 0 0 13px rgba(255, 85, 0, 0.2);
}

.range-input:focus::-moz-range-thumb {
box-shadow: 0 0 0 13px rgba(255, 85, 0, 0.2);
}

.gain-reset {
font-size: 12px;
margin-top: 12px;
padding: 5px;
max-width: 50px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { FunctionalComponent, h, JSX } from 'preact';
import './GainControl.css';

interface GainControlProps {
gain: number;
updateGain: (event: JSX.TargetedEvent<HTMLInputElement, Event>) => void;
resetGain: () => void;
}

const GainControl: FunctionalComponent<GainControlProps> = ({
gain,
updateGain,
resetGain,
}) => {
const formattedGain = gain === 0 ? '0.00' : (10 ** gain).toFixed(2);
const sign = gain >= 0 ? '+' : '-';

return (
<div className="gain-input">
<label htmlFor="gain">
Gain <br /> {sign}
{formattedGain}
</label>
<input
className="range-input"
type="range"
name="gain"
id="gain"
min="-1"
max="1"
step={0.02}
value={gain}
onChange={updateGain}
disabled={false}
/>
<button className="gain-reset" onClick={resetGain}>
Reset
</button>
</div>
);
};

export default GainControl;
6 changes: 6 additions & 0 deletions packages/key-finder-web/src/Utils/audioUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export function createDataArrayForAnalyzerDevice(
return dataArray;
}

export function createGainNode(audioContext: AudioContext): GainNode {
const gainNode = audioContext.createGain();
gainNode.gain.value = 1;
return gainNode;
}

export function connectAudioNodes(
audioSource: AudioNode,
audioRecorder: AudioNode
Expand Down