Skip to content

Commit d85d3c9

Browse files
committed
feature: starting to work on a soft synth using tonejs
- adds svg piano (maybe would be nice to separate it to a module) - adds tonejs - adds vscode debugging launch config - adds portfolio/audio page
1 parent 1437c1c commit d85d3c9

File tree

6 files changed

+197
-8
lines changed

6 files changed

+197
-8
lines changed

.vscode/launch.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "chrome",
9+
"request": "launch",
10+
"name": "Next: Chrome",
11+
"url": "http://localhost:3000",
12+
"webRoot": "${workspaceFolder}"
13+
},
14+
{
15+
"type": "node",
16+
"request": "launch",
17+
"name": "Next: Node",
18+
"runtimeExecutable": "next",
19+
"runtimeArgs": [
20+
"--inspect"
21+
],
22+
"port": 9229,
23+
"console": "integratedTerminal"
24+
}
25+
]
26+
}

components/keys/piano.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import styled from 'styled-components';
2+
3+
export type PianoProps = {
4+
blackKeyWith?: number;
5+
whiteKeyWidth?: number;
6+
numberOfOctaves?: number;
7+
};
8+
9+
function getRange(n: number) {
10+
return Array.from(Array(n).keys());
11+
}
12+
13+
// total is 12 keys per octave
14+
// index from 0 to 4 is black
15+
// index from 5 to 11 is white
16+
17+
function getX(index: number, props: PianoProps) {
18+
const pos = getPos(index);
19+
const offset = Math.floor(index / 12) * getFullOctaveWith(props);
20+
21+
if (pos < 7) {
22+
return offset + pos * props.whiteKeyWidth;
23+
}
24+
25+
if (pos < 9) {
26+
return (
27+
offset
28+
+ (pos - 6) * props.whiteKeyWidth
29+
- props.blackKeyWith / 2
30+
);
31+
} else {
32+
return (
33+
offset +
34+
+ (4 + pos - 9) * props.whiteKeyWidth
35+
- props.blackKeyWith / 2
36+
);
37+
}
38+
}
39+
40+
function getRectProps(index: number, props: PianoProps) {
41+
const isWhite = isWhiteKey(index);
42+
return {
43+
fill: isWhite ? 'white' : 'black',
44+
width: isWhite ? props.whiteKeyWidth : props.blackKeyWith,
45+
height: isWhite ? '80' : '50',
46+
};
47+
}
48+
49+
function getPos(index: number) {
50+
return index % 12;
51+
}
52+
53+
function isWhiteKey(index: number) {
54+
return getPos(index) < 7;
55+
}
56+
57+
function getFullOctaveWith({ whiteKeyWidth }: PianoProps) {
58+
return whiteKeyWidth * 7;
59+
}
60+
61+
const Rect = styled.rect`
62+
cursor: pointer;
63+
64+
&:hover {
65+
fill: rgba(0,0,0,0.2);
66+
}
67+
`;
68+
69+
const Piano: React.SFC<PianoProps> = (props) => (
70+
<svg
71+
strokeWidth="1"
72+
viewBox={`0 0 ${props.numberOfOctaves * getFullOctaveWith(props)} 80`}
73+
>
74+
<g>
75+
{getRange(props.numberOfOctaves * 12).map((_, index) => {
76+
const rectProps = getRectProps(index, props);
77+
return (
78+
<Rect
79+
x={getX(index, props)}
80+
y="0"
81+
key={index}
82+
stroke="rgba(0,0,0,0.3)"
83+
strokeWidth="1"
84+
{...rectProps}
85+
/>
86+
);
87+
})}
88+
</g>
89+
</svg>
90+
);
91+
92+
Piano.defaultProps = {
93+
blackKeyWith: 16,
94+
whiteKeyWidth: 20,
95+
numberOfOctaves: 3,
96+
};
97+
98+
export default Piano;

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
"typescript": "^2.9.2"
3232
},
3333
"dependencies": {
34-
"@types/segment-analytics": "^0.0.28"
34+
"@types/segment-analytics": "^0.0.28",
35+
"@types/webmidi": "^2.0.2",
36+
"tone": "^0.12.80"
3537
}
3638
}

pages/portfolio/audio.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Component } from 'react';
2+
3+
import Page from '../../components/Page';
4+
import Layer from '../../components/Layer';
5+
import Piano from '../../components/keys/piano';
6+
import Section from '../../components/section/Section';
7+
8+
type State = {
9+
};
10+
11+
class AudioPage extends Component<{}, State> {
12+
state: State = {
13+
};
14+
15+
componentDidMount() {
16+
navigator.requestMIDIAccess().then(async ({ inputs }) => {
17+
const Tone = await import('tone');
18+
const synth = new Tone.PolySynth(10);
19+
20+
synth.toMaster();
21+
22+
inputs.forEach(el => {
23+
el.onmidimessage = (msg) => {
24+
const [cmd, midi, velocity] = msg.data;
25+
26+
if (cmd === 254) {
27+
return;
28+
}
29+
30+
const freq = new Tone.Frequency(midi, 'midi');
31+
32+
if (cmd === 144 && velocity) {
33+
synth.triggerAttack([freq], undefined, velocity / 127);
34+
} else if (cmd === 144 && !velocity) {
35+
synth.triggerRelease(freq);
36+
}
37+
38+
console.log('cmd', cmd, 'note', freq.toNote(), 'data', msg.data);
39+
};
40+
});
41+
});
42+
}
43+
44+
render() {
45+
return (
46+
<Page title="building audio">
47+
<Section>
48+
<Layer>
49+
controls here
50+
</Layer>
51+
<Layer>
52+
<Piano />
53+
</Layer>
54+
</Section>
55+
</Page>
56+
);
57+
}
58+
}
59+
60+
export default AudioPage;

tsconfig.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,16 @@
33
"compilerOptions": {
44
"lib": [
55
"dom",
6-
"es2015",
7-
"es2016",
8-
"es2017",
9-
"es2017.object"
6+
"es7"
107
],
118
"jsx": "preserve",
12-
"paths": {
13-
"@components/*": [ "components/*" ]
14-
},
159
"module": "esnext",
1610
"target": "esnext",
1711
"baseUrl": "src",
1812
"allowJs": true,
1913
"sourceMap": true,
2014
"skipLibCheck": true,
15+
"importHelpers": true,
2116
"noUnusedLocals": true,
2217
"removeComments": false,
2318
"moduleResolution": "node",

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,10 @@
674674
version "0.0.28"
675675
resolved "https://registry.yarnpkg.com/@types/segment-analytics/-/segment-analytics-0.0.28.tgz#a23e26d3171cf6a735ae2ae12974b5f6a707c414"
676676

677+
"@types/webmidi@^2.0.2":
678+
version "2.0.2"
679+
resolved "https://registry.yarnpkg.com/@types/webmidi/-/webmidi-2.0.2.tgz#0466cf747aaed2b914a1e2564635bbda217cb256"
680+
677681
"@zeit/next-typescript@^1.1.0":
678682
version "1.1.0"
679683
resolved "https://registry.yarnpkg.com/@zeit/next-typescript/-/next-typescript-1.1.0.tgz#57a45c85c336fee8d71c1bd966195565016932b2"
@@ -3977,6 +3981,10 @@ to-regex@^3.0.1, to-regex@^3.0.2:
39773981
regex-not "^1.0.2"
39783982
safe-regex "^1.1.0"
39793983

3984+
tone@^0.12.80:
3985+
version "0.12.80"
3986+
resolved "https://registry.yarnpkg.com/tone/-/tone-0.12.80.tgz#9a40fca603d02e189251db772ef6c7bf082e6c28"
3987+
39803988
39813989
version "3.1.0"
39823990
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"

0 commit comments

Comments
 (0)