Tonejs

Seto Elkahfi committed Jan 23, 2022 at 11:58 UTC 78582622ca17f6e2550a16970c174722c4dfe7aa
3 files changed +185 -108
package.json
+1
@@ -22,6 +22,7 @@
22 "react-router-dom": "^5.3.0",
23 "react-scripts": "^4.0.0",
24 "react-youtube": "^7.14.0",
25 + "tone": "^14.7.77",
26 "typescript": "^3.9.10"
27 },
28 "scripts": {
src/components/AudioPlayer.tsx
+157 -106
@@ -1,13 +1,18 @@
1 /* eslint-disable no-script-url */
2 /* eslint-disable jsx-a11y/anchor-is-valid */
3 import axios from 'axios';
4 -import React from 'react';
4 +import React, { CSSProperties } from 'react';
5 import { Component } from 'react';
6 -import { Col, Row } from 'react-bootstrap';
6 +import { Col, Row, Spinner } from 'react-bootstrap';
7 import YouTube, { Options } from 'react-youtube';
8 -import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys } from 'react-icons/gi';
8 +import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys, GiPlayButton } from 'react-icons/gi';
9 import { IconContext } from 'react-icons';
10 import { db, AudioFiles } from './db';
11 +import { Channel, Player, Time, Transport } from 'tone';
12 +
13 +const buttonStyle: CSSProperties = {
14 +
15 +}
16
17 interface Result {
18 source_file: string,
@@ -25,7 +30,9 @@ type AudioProps = {
30
31 type AudioState = {
32 audioFile: AudioFile | null,
33 + playerTime: number,
34 isPlayerReady: boolean,
35 + isYoutubePlayerReady: boolean,
36 isPlaying: boolean
37 vocalAudioOn: boolean
38 guitarAudioOn: boolean
@@ -40,23 +47,25 @@ export enum Mode {
47
48 class AudioPlayer extends Component<AudioProps, AudioState> {
49
43 - vocalAudio = new Audio();
44 - guitarAudio = new Audio();
45 - bassAudio = new Audio();
46 - drumsAudio = new Audio();
47 - pianoAudio = new Audio()
50 + vocalPlayer: Player | null = null;
51 + otherPlayer: Player | null = null;
52 + bassPlayer: Player | null = null;
53 + drumsPlayer: Player | null = null;
54 + pianoPlayer: Player | null = null;
55
56 constructor(props: AudioProps) {
57 super(props);
58 this.state = {
59 audioFile: null,
60 + playerTime: 0,
61 isPlayerReady: false,
62 + isYoutubePlayerReady: false,
63 isPlaying: false,
55 - vocalAudioOn: false,
56 - guitarAudioOn: false,
57 - bassAudioOn: false,
58 - drumsAudioOn: false,
59 - pianoAudioOn: false,
64 + vocalAudioOn: true,
65 + guitarAudioOn: true,
66 + bassAudioOn: true,
67 + drumsAudioOn: true,
68 + pianoAudioOn: true,
69 }
70 }
71
@@ -68,7 +77,11 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
77 this.setState({ audioFile });
78 console.log(this.state);
79 this.downloadAudioFilesIfNeeded();
71 - })
80 + });
81 + }
82 +
83 + componentWillUnmount() {
84 + this.pauseAudio();
85 }
86
87 downloadAudioFilesIfNeeded() {
@@ -137,146 +150,184 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
150
151 _setAudioSrc(type: Mode, file: Blob) {
152 // Get window.URL object
140 - var URL = window.URL || window.webkitURL;
153 + const URL = window.URL || window.webkitURL;
154 // Create and revoke ObjectURL
142 - var audioFileURL = URL.createObjectURL(file);
155 + const audioFileURL = URL.createObjectURL(file);
156 + const channel = new Channel().toDestination();
157
158 switch (type) {
159 case Mode.vocalist:
146 - this.vocalAudio.src = audioFileURL;
160 + this.vocalPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
161 + this.vocalPlayer.connect(channel);
162 break;
163 case Mode.bassist:
149 - this.bassAudio.src = audioFileURL;
164 + this.bassPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
165 + this.bassPlayer.connect(channel);
166 break;
167 case Mode.drummer:
152 - this.drumsAudio.src = audioFileURL;
168 + this.drumsPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
169 + this.drumsPlayer.connect(channel);
170 break;
171 case Mode.guitarist:
155 - this.guitarAudio.src = audioFileURL;
172 + this.otherPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
173 + this.otherPlayer.connect(channel);
174 break;
175 case Mode.keyboardist:
158 - this.pianoAudio.src = audioFileURL;
176 + this.pianoPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
177 + this.pianoPlayer.connect(channel);
178 break;
179 }
180 }
181
163 - playAudio() {
164 - this.vocalAudio.play();
165 - this.guitarAudio.play();
166 - this.bassAudio.play();
167 - this.guitarAudio.play();
168 - this.drumsAudio.play();
182 + _updatePlayerStatus: (() => void) = () => {
183 + if (
184 + this.vocalPlayer?.loaded &&
185 + this.drumsPlayer?.loaded &&
186 + this.otherPlayer?.loaded &&
187 + this.bassPlayer?.loaded &&
188 + this.pianoPlayer?.loaded
189 + ) {
190 + this.setState({ isPlayerReady: true });
191 + }
192 + }
193 +
194 + playAudio(at: number) {
195 + this.vocalPlayer?.seek(at).start()
196 + this.bassPlayer?.seek(at).start()
197 + this.drumsPlayer?.seek(at).start()
198 + this.pianoPlayer?.seek(at).start()
199 + this.otherPlayer?.seek(at).start()
200 +
201 + console.log("Start playing at", at);
202 }
203
204 pauseAudio() {
172 - this.vocalAudio.pause();
173 - this.guitarAudio.pause();
174 - this.bassAudio.pause();
175 - this.guitarAudio.pause();
176 - this.drumsAudio.pause();
205 + this.vocalPlayer?.stop();
206 + this.bassPlayer?.stop();
207 + this.drumsPlayer?.stop();
208 + this.pianoPlayer?.stop();
209 + this.otherPlayer?.stop();
210 +
211 + console.log("Stop playing");
212 }
213
214 setToggleInstrument(mode: Mode) {
215 switch (mode) {
216 case Mode.vocalist:
182 - this.setState({vocalAudioOn: !this.state.vocalAudioOn});
183 - this.vocalAudio.volume = this.state.vocalAudioOn ? 1 : 0;
217 + if (!this.vocalPlayer) return;
218 + this.setState({ vocalAudioOn: !this.state.vocalAudioOn });
219 + this.vocalPlayer.mute = this.state.vocalAudioOn;
220 break;
221 case Mode.bassist:
186 - this.setState({bassAudioOn: !this.state.bassAudioOn});
187 - this.bassAudio.volume = this.state.bassAudioOn ? 1 : 0;;
222 + if (!this.bassPlayer) return;
223 + this.setState({ bassAudioOn: !this.state.bassAudioOn });
224 + this.bassPlayer.mute = this.state.bassAudioOn;
225 break;
226 case Mode.guitarist:
190 - this.setState({guitarAudioOn: !this.state.guitarAudioOn});
191 - this.guitarAudio.volume = this.state.guitarAudioOn ? 1 : 0;;
227 + if (!this.otherPlayer) return;
228 + this.setState({ guitarAudioOn: !this.state.guitarAudioOn });
229 + this.otherPlayer.mute = this.state.guitarAudioOn;
230 break;
231 case Mode.keyboardist:
194 - this.setState({pianoAudioOn: !this.state.pianoAudioOn});
195 - this.pianoAudio.volume = this.state.pianoAudioOn ? 1 : 0;;
232 + if (!this.pianoPlayer) return;
233 + this.setState({ pianoAudioOn: !this.state.pianoAudioOn });
234 + this.pianoPlayer.mute = this.state.pianoAudioOn;;
235 break;
236 case Mode.drummer:
198 - this.setState({drumsAudioOn: !this.state.drumsAudioOn});
199 - this.drumsAudio.volume = this.state.drumsAudioOn ? 1 : 0;;
237 + if (!this.drumsPlayer) return;
238 + this.setState({ drumsAudioOn: !this.state.drumsAudioOn });
239 + this.drumsPlayer.mute = this.state.drumsAudioOn;
240 break;
241 }
242 }
243
244 render() {
205 - const opts: Options = {
206 - width: '100%',
207 - playerVars: {
208 - // https://developers.google.com/youtube/player_parameters
209 - autoplay: 0,
210 - mute: 1,
211 - controls: 0,
212 - rel: 0,
213 - showinfo: 0
214 - },
215 - };
216 -
217 - let youtubeContent: any = 'Loading player...';
218 - if (this.state.audioFile) {
219 - youtubeContent = <YouTube
220 - videoId={this.state.audioFile?.youtube_video_id}
221 - opts={opts}
222 - onReady={this._onReady.bind(this)}
223 - onStateChange={this._onStateChange.bind(this)}
224 - onPlay={this._onPlay.bind(this)}
225 - onPause={this._onPause.bind(this)}
226 - />
227 - }
228 -
229 - return (
230 - <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
231 - <Row className="mb-3 mt-3">
232 - <Col>
233 - <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.vocalist)} >
234 - <GiMicrophone color={this.state.vocalAudioOn ? `white` : `red`} />
235 - </a>
236 - </Col>
237 - <Col>
238 - <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.drummer)} >
239 - <GiDrumKit color={this.state.drumsAudioOn ? `white` : `red`} />
240 - </a>
241 - </Col>
242 - <Col>
243 - <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.bassist)} >
244 - <GiGuitarBassHead color={this.state.bassAudioOn ? `white` : `red`} />
245 - </a>
246 - </Col>
247 - <Col>
248 - <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.guitarist)} >
249 - <GiGuitarHead color={this.state.guitarAudioOn ? `white` : `red`} />
250 - </a>
251 - </Col>
252 - <Col>
253 - <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.keyboardist)} >
254 - <GiPianoKeys color={this.state.pianoAudioOn ? `white` : `red`} />
255 - </a>
256 - </Col>
257 - </Row>
245 + if (this.state.isPlayerReady) {
246 + const opts: Options = {
247 + width: '100%',
248 + playerVars: {
249 + // https://developers.google.com/youtube/player_parameters
250 + autoplay: 0,
251 + mute: 1,
252 + controls: 0,
253 + rel: 0,
254 + showinfo: 0
255 + },
256 + };
257 + return (
258 + <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
259 + <Row className="mb-3 mt-3">
260 + <Col>
261 + <GiMicrophone
262 + color={this.state.vocalAudioOn ? `white` : `red`}
263 + onClick={() => this.setToggleInstrument(Mode.vocalist)}
264 + style={buttonStyle} />
265 + </Col>
266 + <Col>
267 + <GiDrumKit
268 + color={this.state.drumsAudioOn ? `white` : `red`}
269 + onClick={() => this.setToggleInstrument(Mode.drummer)}
270 + style={buttonStyle} />
271 + </Col>
272 + <Col>
273 + <GiGuitarBassHead
274 + color={this.state.bassAudioOn ? `white` : `red`}
275 + onClick={() => this.setToggleInstrument(Mode.bassist)}
276 + style={buttonStyle} />
277 + </Col>
278 + <Col>
279 + <GiGuitarHead
280 + color={this.state.guitarAudioOn ? `white` : `red`}
281 + onClick={() => this.setToggleInstrument(Mode.guitarist)}
282 + style={buttonStyle} />
283 + </Col>
284 + <Col>
285 + <GiPianoKeys
286 + color={this.state.pianoAudioOn ? `white` : `red`}
287 + onClick={() => this.setToggleInstrument(Mode.keyboardist)}
288 + style={buttonStyle} />
289 + </Col>
290 + </Row>
291 + <Row>
292 + <Col>
293 + <YouTube
294 + videoId={this.state.audioFile?.youtube_video_id}
295 + opts={opts}
296 + onReady={this._onReady.bind(this)}
297 + onStateChange={this._onStateChange.bind(this)}
298 + />
299 + </Col>
300 + </Row>
301 + </IconContext.Provider>
302 + )
303 + } else {
304 + return (
305 <Row>
259 - <Col>
260 - {youtubeContent}
306 + <Col className="mb-3 mt-3">
307 + <p>Loading player...</p>
308 + <Spinner animation='grow'></Spinner>
309 </Col>
310 </Row>
263 - </IconContext.Provider>
264 - )
311 + )
312 + }
313 }
314
315 _onReady(event: any) {
268 - console.log('YouTube ready: ', event)
316 + console.log('YouTube ready: ', event);
317 + this.setState({ isYoutubePlayerReady: true });
318 }
319
320 _onStateChange(event: any) {
321 console.log('State changed: ', event)
273 - }
322 + console.log('getCurrentTime: ', event.target.getCurrentTime())
323 + console.log('getPlayerState: ', event.target.getPlayerState())
324
275 - _onPlay() {
276 - this.playAudio()
277 - }
278 - _onPause() {
279 - this.pauseAudio()
325 + if (event.target.getPlayerState() === 1) { // Play
326 + const at = event.target.getCurrentTime()
327 + this.playAudio(at)
328 + } else if (event.target.getPlayerState() === 2) {// pause
329 + this.pauseAudio()
330 + }
331 }
332 }
333
yarn.lock
+27 -2
@@ -2917,6 +2917,14 @@ atob@^2.1.2:
2917 resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
2918 integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
2919
2920 +automation-events@^4.0.12:
2921 + version "4.0.12"
2922 + resolved "https://registry.yarnpkg.com/automation-events/-/automation-events-4.0.12.tgz#53a65907187aa3d4b9dc53042b05887c5e643f65"
2923 + integrity sha512-7tu7q/rw3vFuAwjBoarCGql3V0HkC5QAbZUYhOblxxkHzdLUTjVotm0iaIRwiCSqxMeoFK64LrgaSCuSZguvGA==
2924 + dependencies:
2925 + "@babel/runtime" "^7.16.7"
2926 + tslib "^2.3.1"
2927 +
2928 autoprefixer@^9.6.1:
2929 version "9.8.8"
2930 resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
@@ -9709,7 +9717,7 @@ react-rotating-text@^1.4.1:
9717 lodash.toarray "^4.4.0"
9718 prop-types "^15.5.10"
9719
9712 -react-router-dom@^5.3.1:
9720 +react-router-dom@^5.3.0:
9721 version "5.3.0"
9722 resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
9723 integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==
@@ -10736,6 +10744,15 @@ stackframe@^1.1.1:
10744 resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
10745 integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
10746
10747 +standardized-audio-context@^25.1.8:
10748 + version "25.3.19"
10749 + resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.3.19.tgz#bcb5bea0365571f5b3bef2c40860069371a3c2d1"
10750 + integrity sha512-GOXncphk6PLnT+79lyvVen2hn68zEX5K++qvRQXdZXobYjPriW0sShgFxqg3AMVysZifc/5S+8IM/9mNf0x+sA==
10751 + dependencies:
10752 + "@babel/runtime" "^7.16.7"
10753 + automation-events "^4.0.12"
10754 + tslib "^2.3.1"
10755 +
10756 static-extend@^0.1.1:
10757 version "0.1.2"
10758 resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -11222,6 +11239,14 @@ toidentifier@1.0.1:
11239 resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
11240 integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
11241
11242 +tone@^14.7.77:
11243 + version "14.7.77"
11244 + resolved "https://registry.yarnpkg.com/tone/-/tone-14.7.77.tgz#12a2a9f033952ccdb552275a6384ca5d36d4b5ed"
11245 + integrity sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==
11246 + dependencies:
11247 + standardized-audio-context "^25.1.8"
11248 + tslib "^2.0.1"
11249 +
11250 tough-cookie@^4.0.0:
11251 version "4.0.0"
11252 resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
@@ -11263,7 +11288,7 @@ tslib@^1.8.1:
11288 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
11289 integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
11290
11266 -tslib@^2.0.3, tslib@^2.1.0:
11291 +tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1:
11292 version "2.3.1"
11293 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
11294 integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==