-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix: refactored Examples mapping * feat: implemented custom slider, props does not work yet * feat: added changing oscillator type button * feat: implemented custom slider * refactor: fixed custom slider and added button to switch oscillator type * chore: remove unused packages * chore: cleanup pod lockfile * fix: applied changes requested in PR to Oscillator and App.tsx * fix: fixed slider and applied changes requested in PR * feat: implemented metronome * fix: removed react-native-community/slider from deps * fix: fixed constant name typo * fix: applied changes requested in PR * refactor: changed theme color and refecatored metronome play pause system --------- Co-authored-by: Maciej Makowski <[email protected]> Co-authored-by: Michal Sek <[email protected]>
- Loading branch information
1 parent
6d62cd2
commit 10a0cef
Showing
5 changed files
with
161 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,161 @@ | ||
import { FC } from 'react'; | ||
import React, { useState, useEffect, useRef, FC } from 'react'; | ||
import { Text, Button } from 'react-native'; | ||
import { AudioContext } from 'react-native-audio-api'; | ||
|
||
import Slider from '../../components/Slider'; | ||
import Container from '../../components/Container'; | ||
import { colors } from '../../styles'; | ||
|
||
const SCHEDULE_INTERVAL_MS = 25; | ||
const SCHEDULE_AHEAD_TIME = 0.1; | ||
|
||
const DOWN_BEAT_FREQUENCY = 1000; | ||
const REGULAR_BEAT_FREQUENCY = 500; | ||
|
||
const INITIAL_GAIN = 1; | ||
const FADE_OUT_START_GAIN = 0.5; | ||
const FADE_OUT_END_GAIN = 0.001; | ||
|
||
const FADE_OUT_START_TIME = 0.01; | ||
const FADE_OUT_END_TIME = 0.03; | ||
|
||
const Metronome: FC = () => { | ||
return <Container centered={true} />; | ||
const [bpm, setBpm] = useState(120); | ||
const [beatsPerBar, setBeatsPerBar] = useState(4); | ||
const [isPlaying, setIsPlaying] = useState(false); | ||
|
||
const audioContextRef = useRef<null | AudioContext>(null); | ||
const intervalRef = useRef<null | NodeJS.Timeout>(null); | ||
const nextNoteTimeRef = useRef(0.0); | ||
const currentBeatRef = useRef(0); | ||
|
||
const handlePlay = () => { | ||
if (!audioContextRef.current) { | ||
audioContextRef.current = new AudioContext(); | ||
} | ||
setIsPlaying(true); | ||
currentBeatRef.current = 0; | ||
nextNoteTimeRef.current = | ||
audioContextRef.current.currentTime + SCHEDULE_INTERVAL_MS / 1000; | ||
intervalRef.current = setInterval(scheduler, SCHEDULE_INTERVAL_MS); | ||
}; | ||
|
||
const handlePause = () => { | ||
setIsPlaying(false); | ||
|
||
if (intervalRef.current) { | ||
clearInterval(intervalRef.current); | ||
intervalRef.current = null; | ||
} | ||
}; | ||
|
||
const handlePlayPause = () => { | ||
if (isPlaying) { | ||
handlePause(); | ||
|
||
return; | ||
} | ||
|
||
handlePlay(); | ||
}; | ||
|
||
const handleBpmChange = (newBpm: number) => { | ||
setBpm(newBpm); | ||
handlePause(); | ||
}; | ||
|
||
const handleBeatsPerBarChange = (newBeatsPerBar: number) => { | ||
setBeatsPerBar(newBeatsPerBar); | ||
handlePause(); | ||
}; | ||
|
||
const scheduler = () => { | ||
if (!audioContextRef.current) { | ||
return; | ||
} | ||
|
||
while ( | ||
nextNoteTimeRef.current < | ||
audioContextRef.current.currentTime + SCHEDULE_AHEAD_TIME | ||
) { | ||
playNote(currentBeatRef.current, nextNoteTimeRef.current); | ||
nextNote(); | ||
} | ||
}; | ||
|
||
const nextNote = () => { | ||
const secondsPerBeat = 60.0 / bpm; | ||
nextNoteTimeRef.current += secondsPerBeat; | ||
|
||
currentBeatRef.current += 1; | ||
if (currentBeatRef.current === beatsPerBar) { | ||
currentBeatRef.current = 0; | ||
} | ||
}; | ||
|
||
const playNote = (beatNumber: number, time: number) => { | ||
if (!audioContextRef.current) { | ||
return; | ||
} | ||
|
||
const oscillator = audioContextRef.current.createOscillator(); | ||
const gain = audioContextRef.current.createGain(); | ||
|
||
oscillator.frequency.value = | ||
beatNumber % beatsPerBar === 0 | ||
? DOWN_BEAT_FREQUENCY | ||
: REGULAR_BEAT_FREQUENCY; | ||
|
||
gain.gain.setValueAtTime(INITIAL_GAIN, time); | ||
gain.gain.linearRampToValueAtTime( | ||
FADE_OUT_START_GAIN, | ||
time + FADE_OUT_START_TIME | ||
); | ||
gain.gain.linearRampToValueAtTime( | ||
FADE_OUT_END_GAIN, | ||
time + FADE_OUT_END_TIME | ||
); | ||
|
||
oscillator.connect(gain); | ||
gain.connect(audioContextRef.current.destination); | ||
|
||
oscillator.start(time); | ||
oscillator.stop(time + FADE_OUT_END_TIME); | ||
}; | ||
|
||
useEffect(() => { | ||
return () => { | ||
if (intervalRef.current) { | ||
clearInterval(intervalRef.current); | ||
} | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<Container centered> | ||
<Button | ||
color={colors.main} | ||
onPress={handlePlayPause} | ||
title={isPlaying ? 'Pause' : 'Play'} | ||
/> | ||
<Text>BPM: {bpm}</Text> | ||
<Slider | ||
value={bpm} | ||
onValueChange={handleBpmChange} | ||
minimumValue={30} | ||
maximumValue={240} | ||
step={1} | ||
/> | ||
<Text>Beats per bar: {beatsPerBar}</Text> | ||
<Slider | ||
value={beatsPerBar} | ||
onValueChange={handleBeatsPerBarChange} | ||
minimumValue={1} | ||
maximumValue={8} | ||
step={1} | ||
/> | ||
</Container> | ||
); | ||
}; | ||
|
||
export default Metronome; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters