-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathminimal-webaudio-synth.html
162 lines (144 loc) · 5.84 KB
/
minimal-webaudio-synth.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Minimal WebAudio synth</title>
<style>
body { background-color: darkgray; box-sizing: border-box; }
.box { max-width: 60em; margin: auto; background-color: white; padding: 1em; }
.control input { width: 80%; }
.keyboard {
padding: 0;
overflow: auto;
border-left: 1px solid darkgray;
margin: 1em 0;
display: inline-block;
}
.keyboard .key {
float: left;
display: inline-block;
width: 2em;
height: 6em;
border: 1px solid darkgray;
border-left: none;
margin: 0;
padding: 0;
}
.keyboard .key.black {
background-color: black;
}
</style>
</head>
<body>
<div class="box">
<form id="form">
<h1>Minimal WebAudio synth</h1>
<p><small>This is example implementation of minimal usable WebAudio synth without any external dependencies.</small></p>
<button id="power">POWER ON</button>
<div class="control">
<label>volume</label> <input type="range" id="volume" min="0" max="1" value="0.5" step="0.001">
</div>
<div class="control">
<label>cutoff</label> <input type="range" id="cutoff" min="0" max="20000" value="20000" step="1">
</div>
<div class="control">
<label>waveform</label>
<select id="waveform">
<option>sawtooth</option>
<option>sine</option>
<option>square</option>
<option>triangle</option>
</select>
</div>
</form>
<div class="keyboard" id="keyboard">
<div class="key" data-midi="60"></div>
<div class="key black" data-midi="61"></div>
<div class="key" data-midi="62"></div>
<div class="key black" data-midi="63"></div>
<div class="key" data-midi="64"></div>
<div class="key" data-midi="65"></div>
<div class="key black" data-midi="66"></div>
<div class="key" data-midi="67"></div>
<div class="key black" data-midi="68"></div>
<div class="key" data-midi="69"></div>
<div class="key black" data-midi="70"></div>
<div class="key" data-midi="71"></div>
<div class="key" data-midi="72"></div>
<div class="key black" data-midi="73"></div>
<div class="key" data-midi="74"></div>
<div class="key black" data-midi="75"></div>
<div class="key" data-midi="76"></div>
<div class="key" data-midi="77"></div>
<div class="key black" data-midi="78"></div>
<div class="key" data-midi="79"></div>
<div class="key black" data-midi="80"></div>
<div class="key" data-midi="81"></div>
<div class="key black" data-midi="82"></div>
<div class="key" data-midi="83"></div>
</div>
<div>
<small>This keyboard only work with mouse for now.</small>
</div>
</div>
<script>
document.getElementById('form').reset(); // to reset form to initial state on each opening of page
// determines freq of current MIDI note
function midi2cps(midi) {
const A4 = 440;
return A4 * Math.pow(2, (midi - 69) / 12);
}
document.getElementById('power').addEventListener('click', function (event){ // you need to click on something to be able to construct AudioContext
event.preventDefault(); // to not submit form
document.getElementById('power').setAttribute('disabled', 'disabled'); // to prevent clicking it again
let ac = new AudioContext(); // create audio context to work on
let osc = ac.createOscillator(); // oscillator
osc.frequency.value = 440;
osc.type = 'sawtooth';
osc.start(ac.currentTime); // start oscillator so it makes some sound
let filter = ac.createBiquadFilter(); // filter
filter.type = 'lowpass';
filter.frequency.value = 20000;
let vca = ac.createGain(); // VCA
vca.gain.value = 0;
let amp = ac.createGain(); // final volume knob of synth
amp.gain.value = 0.5;
osc.connect(vca).connect(amp).connect(filter).connect(ac.destination);
// keyboard playing:
document.getElementById('keyboard').addEventListener('mousedown', function (event){ // user pressed an key (mouse up)
osc.frequency.value = midi2cps(event.target.getAttribute('data-midi')); // change freq of OSC
vca.gain.value = 1; // open VCA up
});
document.getElementById('keyboard').addEventListener('mouseover', function (event){ // user slide to another
if (event.buttons == 1 || event.buttons == 3){ // but only if they have pressed button before
if (!event.target.hasAttribute('data-midi')) return; // to prevent triggering from 1px left border with no data-midi
osc.frequency.value = midi2cps(event.target.getAttribute('data-midi')); // change OSC freq
}
});
document.getElementById('keyboard').addEventListener('mouseup', function (event){ // user depressed an key (mouse up)
vca.gain.value = 0; // close VCA
});
document.getElementById('keyboard').addEventListener('mouseleave', function (event){ // user moved mouse out of the keyboard
vca.gain.value = 0; // close VCA
});
document.getElementById('keyboard').addEventListener('mouseenter', function (event){ // user moved mouse into the keyboard
if(event.buttons == 1 || event.buttons == 3) { // so if they have pressed button before
vca.gain.value = 1; // open VCA
}
});
// volume handling
document.getElementById('volume').addEventListener('input', function (event){
amp.gain.value = event.target.value;
});
// moving filter
document.getElementById('cutoff').addEventListener('input', function (event){
filter.frequency.value = event.target.value;
});
// changing waveform
document.getElementById('waveform').addEventListener('change', function (event) {
osc.type = event.target.value;
});
});
</script>
</body>
</html>