@logue/reverb 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -64
- package/dist/reverb.es.js +188 -0
- package/dist/reverb.umd.js +10 -0
- package/package.json +59 -45
- package/types/Meta.d.ts +4 -0
- package/types/Meta.d.ts.map +1 -0
- package/types/NoiseType.d.ts +14 -0
- package/types/NoiseType.d.ts.map +1 -0
- package/types/Reverb.d.ts +118 -0
- package/types/Reverb.d.ts.map +1 -0
- package/{src/interfaces/MetaInterface.ts → types/interfaces/MetaInterface.d.ts} +2 -3
- package/{dist → types}/interfaces/MetaInterface.d.ts.map +1 -1
- package/{src/interfaces/OptionInterface.ts → types/interfaces/OptionInterface.d.ts} +3 -5
- package/types/interfaces/OptionInterface.d.ts.map +1 -0
- package/.eslintignore +0 -4
- package/.eslintrc.js +0 -21
- package/.gitattributes +0 -1
- package/.prettierrc.js +0 -3
- package/bin/reverb.js +0 -434
- package/bin/reverb.js.map +0 -1
- package/bin/reverb.min.js +0 -2
- package/bin/reverb.min.js.LICENSE.txt +0 -11
- package/dist/Meta.d.ts +0 -4
- package/dist/Meta.d.ts.map +0 -1
- package/dist/Meta.js +0 -9
- package/dist/Meta.js.map +0 -1
- package/dist/NoiseType.d.ts +0 -7
- package/dist/NoiseType.d.ts.map +0 -1
- package/dist/NoiseType.js +0 -12
- package/dist/NoiseType.js.map +0 -1
- package/dist/Reverb.d.ts +0 -114
- package/dist/Reverb.d.ts.map +0 -1
- package/dist/Reverb.js +0 -333
- package/dist/Reverb.js.map +0 -1
- package/dist/interfaces/MetaInterface.d.ts +0 -10
- package/dist/interfaces/MetaInterface.js +0 -3
- package/dist/interfaces/MetaInterface.js.map +0 -1
- package/dist/interfaces/OptionInterface.d.ts +0 -27
- package/dist/interfaces/OptionInterface.d.ts.map +0 -1
- package/dist/interfaces/OptionInterface.js +0 -3
- package/dist/interfaces/OptionInterface.js.map +0 -1
- package/docs/demo.wav +0 -0
- package/docs/index.html +0 -377
- package/docs/localaudio.html +0 -718
- package/docs/reverb.js +0 -434
- package/docs/reverb.js.map +0 -1
- package/src/Meta.ts +0 -8
- package/src/NoiseType.ts +0 -7
- package/src/Reverb.ts +0 -368
- package/tsconfig.json +0 -32
- package/webpack.config.ts +0 -102
package/docs/index.html
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" class="h-100">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="utf-8" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
7
|
-
<meta name="description" content="Reverb effect written in JavaScript." />
|
|
8
|
-
<meta name="author" content="Masashi Yoshikawa" />
|
|
9
|
-
<!-- ogp -->
|
|
10
|
-
<meta property="og:title" content="JavaScript Reverb Effect Test" />
|
|
11
|
-
<meta property="og:type" content="article" />
|
|
12
|
-
<meta property="og:url" content="https://logue.github.io/Reverb.js/" />
|
|
13
|
-
<meta property="og:image"
|
|
14
|
-
content="https://repository-images.githubusercontent.com/194181712/20bdd780-9995-11e9-8811-42e3b44d1cec" />
|
|
15
|
-
<meta property="og:site_name" content="Logue's Lab" />
|
|
16
|
-
<meta property="og:description" content="Reverb effect written in JavaScript." />
|
|
17
|
-
<meta property="fb:app_id" content="129144050466298" />
|
|
18
|
-
<meta property="article:publisher" content="https://www.facebook.com/logue256" />
|
|
19
|
-
<meta name="twitter:card" content="Summary" />
|
|
20
|
-
<meta name="twitter:site" content="@logue256" />
|
|
21
|
-
<meta name="twitter:title" content="JavaScript Reverb Effect Test" />
|
|
22
|
-
<meta name="twitter:url" content="https://logue.dev/Reverb.js/" />
|
|
23
|
-
<meta name="twitter:description" content="Reverb effect written in JavaScript." />
|
|
24
|
-
<meta name="twitter:image"
|
|
25
|
-
content="https://repository-images.githubusercontent.com/194181712/20bdd780-9995-11e9-8811-42e3b44d1cec" />
|
|
26
|
-
<title>Reverb.js Demo</title>
|
|
27
|
-
<!-- Global site tag (gtag.js) - Google Analytics -->
|
|
28
|
-
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-33600926-1"></script>
|
|
29
|
-
<script>
|
|
30
|
-
window.dataLayer = window.dataLayer || [];
|
|
31
|
-
function gtag() {
|
|
32
|
-
dataLayer.push(arguments);
|
|
33
|
-
}
|
|
34
|
-
gtag('js', new Date());
|
|
35
|
-
|
|
36
|
-
gtag('config', 'UA-33600926-1');
|
|
37
|
-
</script>
|
|
38
|
-
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net/" />
|
|
39
|
-
<!-- Bootstrap core CSS -->
|
|
40
|
-
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
41
|
-
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
|
|
42
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
|
|
43
|
-
integrity="sha256-PDJQdTN7dolQWDASIoBVrjkuOEaI137FI15sqI3Oxu8=" crossorigin="anonymous" />
|
|
44
|
-
</head>
|
|
45
|
-
|
|
46
|
-
<body class="d-flex flex-column h-100">
|
|
47
|
-
<header>
|
|
48
|
-
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
|
49
|
-
<div class="container-fluid d-flex justify-content-between">
|
|
50
|
-
<a class="navbar-brand" href="#">smfplayer.js</a>
|
|
51
|
-
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
|
|
52
|
-
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
|
53
|
-
<span class="navbar-toggler-icon"></span>
|
|
54
|
-
</button>
|
|
55
|
-
<div class="collapse navbar-collapse flex-grow-0" id="navbarCollapse">
|
|
56
|
-
<ul class="navbar-nav">
|
|
57
|
-
<li class="nav-item">
|
|
58
|
-
<a class="nav-link" aria-current="page" href="https://logue.dev/">Home</a>
|
|
59
|
-
</li>
|
|
60
|
-
<li class="nav-item">
|
|
61
|
-
<a class="nav-link" href="https://github.com/logue/Reverb.js"><em class="bi bi-github"></em></a>
|
|
62
|
-
</li>
|
|
63
|
-
</ul>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</nav>
|
|
67
|
-
</header>
|
|
68
|
-
|
|
69
|
-
<!-- Begin page content -->
|
|
70
|
-
<main role="main" class="flex-shrink-0 mt-2">
|
|
71
|
-
<div class="container">
|
|
72
|
-
<h1>Reverb.js Demo</h1>
|
|
73
|
-
<p>Refer to the console log for the values.</p>
|
|
74
|
-
<button id="play" class="btn btn-primary" disabled>
|
|
75
|
-
<em class="bi bi-play"></em> Play
|
|
76
|
-
</button>
|
|
77
|
-
<hr />
|
|
78
|
-
<h2>Reverb Control</h2>
|
|
79
|
-
<p>
|
|
80
|
-
Changing these values will regenerate the impulse response each time,
|
|
81
|
-
but due to its nature, it will take some time to see the effect. It
|
|
82
|
-
may be helpful to maximize the value of Wet.
|
|
83
|
-
</p>
|
|
84
|
-
<div class="form-check form-switch">
|
|
85
|
-
<input id="reverb" type="checkbox" class="form-check-input" />
|
|
86
|
-
<label class="form-check-label" for="reverb">Reverb</label>
|
|
87
|
-
</div>
|
|
88
|
-
<div>
|
|
89
|
-
Inpulse Response Noise algorithm:
|
|
90
|
-
<div class="form-check form-check-inline">
|
|
91
|
-
<input class="form-check-input" type="radio" name="noise" id="white" value="0" checked />
|
|
92
|
-
<label class="form-check-label" for="white">White Noise</label>
|
|
93
|
-
</div>
|
|
94
|
-
<div class="form-check form-check-inline">
|
|
95
|
-
<input class="form-check-input" type="radio" name="noise" id="pink" value="1" />
|
|
96
|
-
<label class="form-check-label" for="pink">Pink Noise</label>
|
|
97
|
-
</div>
|
|
98
|
-
<div class="form-check form-check-inline">
|
|
99
|
-
<input class="form-check-input" type="radio" name="noise" id="brown" value="2" />
|
|
100
|
-
<label class="form-check-label" for="brown">Brown Noise</label>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div class="form-check form-switch">
|
|
104
|
-
<input type="checkbox" class="form-check-input" id="reverse" />
|
|
105
|
-
<label class="form-check-label" for="reverse">Reverse Inpulse Response</label>
|
|
106
|
-
</div>
|
|
107
|
-
<hr />
|
|
108
|
-
<div class="row">
|
|
109
|
-
<div class="col-md">
|
|
110
|
-
<label for="time" class="form-label" aria-describedby="time-help">Time</label>
|
|
111
|
-
<input type="range" class="form-range" title="1.1" value="2" min="0" max="50" id="time" />
|
|
112
|
-
<div id="time-help" class="form-text">Room size</div>
|
|
113
|
-
</div>
|
|
114
|
-
<div class="col-md">
|
|
115
|
-
<label for="decay" class="form-label">Decay</label>
|
|
116
|
-
<input type="range" class="form-range" title="2" value="2" min="0" max="100" id="decay" />
|
|
117
|
-
<div id="decay-help" class="form-text">
|
|
118
|
-
Hardness of room wall size
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
<div class="col-md">
|
|
122
|
-
<label for="delay" class="form-label">Delay</label>
|
|
123
|
-
<input type="range" class="form-range" title="0" value="0" min="0" max="100" id="delay" />
|
|
124
|
-
<div id="delay-help" class="form-text">
|
|
125
|
-
Delays due to obstacles in front of room walls
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
<div class="col-md">
|
|
129
|
-
<label for="mix" class="form-label">Dry / Wet</label>
|
|
130
|
-
<input type="range" id="mix" class="form-range" min="0" max="1" step="0.05" value="0.5" title="0.5" />
|
|
131
|
-
<div id="delay-help" class="form-text">
|
|
132
|
-
Closer to the original sound or closer to the effector
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
<h2>Filter for IR</h2>
|
|
137
|
-
<p>
|
|
138
|
-
If the value of the filter is allpass, the filtering will be bypassed.
|
|
139
|
-
</p>
|
|
140
|
-
<div class="row">
|
|
141
|
-
<div class="col-md">
|
|
142
|
-
<label for="filter" class="form-label">Filter Type</label>
|
|
143
|
-
<select id="filter" class="form-select">
|
|
144
|
-
<option value="lowpass" selected="selected">lowpass (Default)</option>
|
|
145
|
-
<option value="highpass">highpass</option>
|
|
146
|
-
<option value="bandpass">bandpass</option>
|
|
147
|
-
<option value="lowshelf">lowshelf</option>
|
|
148
|
-
<option value="highshelf">highshelf</option>
|
|
149
|
-
<option value="peaking">peaking</option>
|
|
150
|
-
<option value="notch">notch</option>
|
|
151
|
-
<option value="allpass">allpass (Through)</option>
|
|
152
|
-
</select>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="col-md">
|
|
155
|
-
<label for="freq" class="form-label">Filter frequency</label>
|
|
156
|
-
<input type="range" class="form-range" value="2500" min="20" max="5000" step="5" id="freq" title="2500" />
|
|
157
|
-
</div>
|
|
158
|
-
<div class="col-md">
|
|
159
|
-
<label for="freq" class="form-label">Quality (Q) value</label>
|
|
160
|
-
<input type="range" class="form-range" value="1" min=".0001" max="10" step=".0005" id="q" title="1" />
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
<h2>FFT</h2>
|
|
164
|
-
<div class="overflow-auto">
|
|
165
|
-
<canvas id="graph" width="512" height="256"></canvas>
|
|
166
|
-
</div>
|
|
167
|
-
<p class="text-right">
|
|
168
|
-
The sound uses the drum part of
|
|
169
|
-
<a href="https://soundcloud.com/logue256/autumn-wind" target="_blank"><em class="bi bi-soundwave"></em> Autmun
|
|
170
|
-
Wind</a>.
|
|
171
|
-
</p>
|
|
172
|
-
</div>
|
|
173
|
-
</main>
|
|
174
|
-
|
|
175
|
-
<footer class="footer mt-auto py-3 bg-light">
|
|
176
|
-
<div class="container">
|
|
177
|
-
<address class="text-muted">
|
|
178
|
-
© 2019-2021 by Logue / MIT License.
|
|
179
|
-
</address>
|
|
180
|
-
</div>
|
|
181
|
-
</footer>
|
|
182
|
-
<script src="reverb.js"></script>
|
|
183
|
-
<script>
|
|
184
|
-
// Setup Audio Context
|
|
185
|
-
const AudioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
186
|
-
|
|
187
|
-
// Defreeze AudioContext for iOS.
|
|
188
|
-
document.addEventListener('touchstart', initAudioContext);
|
|
189
|
-
function initAudioContext() {
|
|
190
|
-
document.removeEventListener('touchstart', initAudioContext);
|
|
191
|
-
// wake up AudioContext
|
|
192
|
-
const emptySource = AudioCtxtx.createBufferSource();
|
|
193
|
-
emptySource.start();
|
|
194
|
-
emptySource.stop();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Setup Reverb Class
|
|
198
|
-
const reverb = new Reverb.default(AudioCtx);
|
|
199
|
-
console.info(
|
|
200
|
-
'Reverb.js loaded. (version:' +
|
|
201
|
-
reverb.version +
|
|
202
|
-
' / build: ' +
|
|
203
|
-
reverb.build +
|
|
204
|
-
')'
|
|
205
|
-
);
|
|
206
|
-
const AudioSrc = AudioCtx.createBufferSource();
|
|
207
|
-
|
|
208
|
-
// Load audio file and decode asyncly.
|
|
209
|
-
const LoadSample = async url => {
|
|
210
|
-
return new Promise(resolve => {
|
|
211
|
-
fetch(url)
|
|
212
|
-
.then(response => response.arrayBuffer())
|
|
213
|
-
.then(arraybuf =>
|
|
214
|
-
AudioCtx.decodeAudioData(
|
|
215
|
-
arraybuf,
|
|
216
|
-
buffer => {
|
|
217
|
-
resolve(buffer);
|
|
218
|
-
},
|
|
219
|
-
e => alert(e)
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
.catch(e => alert(e));
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// Draw FFT to canvas
|
|
227
|
-
// Code taken from https://www.g200kg.com/jp/docs/webaudio/filter.html
|
|
228
|
-
const AnalyserNode = AudioCtx.createAnalyser();
|
|
229
|
-
const canvas = document.getElementById('graph');
|
|
230
|
-
const canvasContext = canvas.getContext('2d');
|
|
231
|
-
|
|
232
|
-
const analysedata = new Float32Array(1024);
|
|
233
|
-
const DrawGraph = () => {
|
|
234
|
-
AnalyserNode.getFloatFrequencyData(analysedata);
|
|
235
|
-
// Background
|
|
236
|
-
canvasContext.fillStyle = '#343a40';
|
|
237
|
-
canvasContext.fillRect(0, 0, canvas.width, canvas.height);
|
|
238
|
-
// FFT Graph
|
|
239
|
-
canvasContext.fillStyle = document.getElementById('reverb').checked
|
|
240
|
-
? '#17a2b8'
|
|
241
|
-
: '#28a745';
|
|
242
|
-
for (let i = 0; i < canvas.width; ++i) {
|
|
243
|
-
const f = (AudioCtx.sampleRate * i) / (canvas.width * 2);
|
|
244
|
-
y = canvas.height / 2 + (analysedata[i] + 48.16) * 2.56;
|
|
245
|
-
canvasContext.fillRect(i, canvas.height - y, 1, y);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// y axis (dB)
|
|
249
|
-
for (let d = -50; d < 50; d += 10) {
|
|
250
|
-
const y = (canvas.height / 2 - (d * canvas.height) / 100) | 0;
|
|
251
|
-
// Line
|
|
252
|
-
canvasContext.fillStyle = '#6c757d';
|
|
253
|
-
canvasContext.fillRect(20, y, canvas.width, 1);
|
|
254
|
-
// Label
|
|
255
|
-
canvasContext.fillStyle = '#fd7e14';
|
|
256
|
-
canvasContext.fillText(d + 'dB', 5, y);
|
|
257
|
-
}
|
|
258
|
-
canvasContext.fillStyle = '#ffc107';
|
|
259
|
-
canvasContext.fillRect(20, canvas.height / 2, canvas.width, 1);
|
|
260
|
-
|
|
261
|
-
// x axis (frequency)
|
|
262
|
-
for (let f = 2200; f < AudioCtx.sampleRate / 2; f += 2000) {
|
|
263
|
-
const x = ((f * (canvas.width * 2)) / AudioCtx.sampleRate) | 0;
|
|
264
|
-
// Line
|
|
265
|
-
canvasContext.fillStyle = '#6c757d';
|
|
266
|
-
canvasContext.fillRect(x, 0, 1, canvas.height - 10);
|
|
267
|
-
// Label
|
|
268
|
-
if (x % 4 == 0) {
|
|
269
|
-
canvasContext.fillStyle = '#e83e8c';
|
|
270
|
-
canvasContext.fillText(f / 1000 + 'kHz', x - 10, canvas.height - 1);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
// Reverb switch handler
|
|
276
|
-
const setReverb = () => {
|
|
277
|
-
AudioSrc.disconnect();
|
|
278
|
-
|
|
279
|
-
if (document.getElementById('reverb').checked) {
|
|
280
|
-
// Connect Reverb
|
|
281
|
-
reverb.connect(AudioSrc).connect(AnalyserNode);
|
|
282
|
-
} else {
|
|
283
|
-
reverb.disconnect(AudioSrc).connect(AnalyserNode);
|
|
284
|
-
}
|
|
285
|
-
AnalyserNode.connect(AudioCtx.destination);
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
window.addEventListener('load', async () => {
|
|
289
|
-
const sound = await LoadSample('./demo.wav');
|
|
290
|
-
|
|
291
|
-
// Draw FFT
|
|
292
|
-
setInterval(DrawGraph, 10);
|
|
293
|
-
|
|
294
|
-
// Play button
|
|
295
|
-
document.getElementById('play').addEventListener(
|
|
296
|
-
'click',
|
|
297
|
-
event => {
|
|
298
|
-
if (event.target != event.currentTarget)
|
|
299
|
-
return;
|
|
300
|
-
|
|
301
|
-
if (AudioSrc.buffer == null) {
|
|
302
|
-
AudioSrc.buffer = sound;
|
|
303
|
-
AudioSrc.loop = true;
|
|
304
|
-
|
|
305
|
-
setReverb();
|
|
306
|
-
AudioSrc.start();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (AudioCtx.state === 'running') {
|
|
310
|
-
AudioCtx.suspend().then(() => {
|
|
311
|
-
|
|
312
|
-
event.target.innerHTML = '<em class="bi bi-play"></em> Play';
|
|
313
|
-
});
|
|
314
|
-
} else if (AudioCtx.state === 'suspended') {
|
|
315
|
-
AudioCtx.resume().then(() => {
|
|
316
|
-
event.target.innerHTML = '<em class="bi bi-pause"></em> Pause';
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
false
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
// Reverb switch button
|
|
324
|
-
document.getElementById('reverb').addEventListener('click', setReverb);
|
|
325
|
-
// Reverse checkbox
|
|
326
|
-
document
|
|
327
|
-
.getElementById('reverse')
|
|
328
|
-
.addEventListener('click', e => reverb.reverse(e.target.checked));
|
|
329
|
-
// Reverb dualation time
|
|
330
|
-
document.getElementById('time').addEventListener('change', e => {
|
|
331
|
-
reverb.time(e.target.value);
|
|
332
|
-
e.target.title = e.target.value;
|
|
333
|
-
});
|
|
334
|
-
// Decay time
|
|
335
|
-
document.getElementById('decay').addEventListener('change', e => {
|
|
336
|
-
reverb.decay(e.target.value);
|
|
337
|
-
e.target.title = e.target.value;
|
|
338
|
-
});
|
|
339
|
-
// Delay time
|
|
340
|
-
document.getElementById('delay').addEventListener('change', e => {
|
|
341
|
-
reverb.delay(e.target.value);
|
|
342
|
-
e.target.title = e.target.value;
|
|
343
|
-
});
|
|
344
|
-
// Filter select box
|
|
345
|
-
document.getElementById('filter').addEventListener('change', e => {
|
|
346
|
-
reverb.filterType(e.target.value);
|
|
347
|
-
e.target.title = e.target.value;
|
|
348
|
-
});
|
|
349
|
-
// Filter frequency
|
|
350
|
-
document.getElementById('freq').addEventListener('change', e => {
|
|
351
|
-
reverb.filterFreq(e.target.value);
|
|
352
|
-
e.target.title = e.target.value;
|
|
353
|
-
});
|
|
354
|
-
// Filter quality
|
|
355
|
-
document.getElementById('q').addEventListener('change', e => {
|
|
356
|
-
reverb.filterQ(e.target.value);
|
|
357
|
-
e.target.title = e.target.value;
|
|
358
|
-
});
|
|
359
|
-
// Dry/Wet
|
|
360
|
-
document.getElementById('mix').addEventListener('change', e => {
|
|
361
|
-
reverb.mix(e.target.value);
|
|
362
|
-
e.target.title = e.target.value;
|
|
363
|
-
});
|
|
364
|
-
// Noise Type
|
|
365
|
-
document
|
|
366
|
-
.querySelectorAll('[name=noise]')
|
|
367
|
-
.forEach(dom =>
|
|
368
|
-
dom.addEventListener('click', e => reverb.setNoise(e.target.value))
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
// remove disabled
|
|
372
|
-
document.getElementById('play').disabled = '';
|
|
373
|
-
});
|
|
374
|
-
</script>
|
|
375
|
-
</body>
|
|
376
|
-
|
|
377
|
-
</html>
|