11// Import necessary modules and components from the Expo and React Native libraries
22import { StatusBar } from "expo-status-bar" ;
3- import { StyleSheet , Text , View , SafeAreaView } from "react-native" ;
4- import React , { useEffect , useReducer } from "react" ;
3+ import { StyleSheet , Text , View , Image , SafeAreaView } from "react-native" ;
4+ import React , { useEffect , useReducer , useRef , useState } from "react" ;
55import { startSession , synchronize } from "./components/Utils" ;
66import { Score_Select } from "./components/ScoreSelect" ;
7+ import { Return_Button } from "./components/ReturnButton" ;
78import { Start_Stop_Button } from "./components/StartButton" ;
89import { MeasureSetBox } from "./components/MeasureSetter" ;
910import { TempoBox } from "./components/TempoBox" ;
1011import { Fraction } from "opensheetmusicdisplay" ;
11- import AudioRecorder from "./components/AudioRecorder " ;
12+ import AudioRecorder from "./components/AudioRecorderStateVersion " ;
1213import { AudioPlayer } from "./components/AudioPlayer" ;
1314import reducer_function from "./Dispatch" ;
1415import ScoreDisplay from "./components/ScoreDisplay" ;
@@ -28,6 +29,7 @@ export default function App() {
2829 const [ state , dispatch ] = useReducer (
2930 reducer_function , // The reducer function is found in Dispatch.ts
3031 {
32+ inPlayMode : false , // whether we are in play mode (and not score selection mode)
3133 playing : false , // whether the audio is playing
3234 resetMeasure : 1 , // the measure to reset to
3335 playRate : 1.0 , // the rate at which the audio is playing
@@ -40,7 +42,7 @@ export default function App() {
4042 synth_tempo : 100 , // the tempo of the synthesized audio
4143 tempo : 100 , // the tempo in the tempo box (even if changed more recently)
4244 score_tempo : 100 , // the tempo in the musical score
43- scores : [ ] , // the list of scores to choose from
45+ scores : [ ] // the list of scores to choose from
4446 } ,
4547 ) ;
4648
@@ -61,83 +63,163 @@ export default function App() {
6163 fetchSessionToken ( ) ;
6264 } , [ ] ) ;
6365
64- console . log ( state ) ;
66+ ////////////////////////////////////////////////////////////////////////////////
67+ // The lines below were modified, copied and pasted out of the audio recorder object
68+ // (which never really needed a UI).
69+ // *** THE ACT OF MOVING THEM FROM A COMPONENT TO APP.TSX MADE THE INTERFACE WORK ***
70+ // *** Probably has to do with parent/child component state something idk ***
71+ ////////////////////////////////////////////////////////////////////////////////
6572
73+ // Audio-related states and refs
74+ // State for whether we have microphone permissions - is set to true on first trip to playmode
75+ const [ permission , setPermission ] = useState ( false ) ;
76+ // Assorted audio-related objects in need of reference
77+ // Tend to be re-created upon starting a recording
78+ const mediaRecorder = useRef < MediaRecorder > (
79+ new MediaRecorder ( new MediaStream ( ) ) ,
80+ ) ;
81+ const [ stream , setStream ] = useState < MediaStream > ( new MediaStream ( ) ) ;
82+ const [ audioChunks , setAudioChunks ] = useState < Blob [ ] > ( [ ] ) ;
83+
84+ const audioContextRef = useRef < any > ( null ) ;
85+ const analyserRef = useRef < any > ( null ) ;
86+ const dataArrayRef = useRef < any > ( null ) ;
87+ const startTimeRef = useRef < any > ( null ) ;
88+
89+ // Audio-related functions
6690 /////////////////////////////////////////////////////////
67- // The code below updates the timestamp but is not yet tied to the API
68- // This could be moved to any sub-component (e.g., ScoreDisplay, AudioPlayer)
69- // or made its own invisible component - OR,
70- // we will re-synchronize whenever the audiorecorder posts, in which case this should
71- // be handled there
72- const UPDATE_INTERVAL = 500 ; // milliseconds between updates to timestamp and rate
91+ // This function sends a synchronization request and updates the state with the result
92+ const UPDATE_INTERVAL = 100 ;
7393
74- useEffect ( ( ) => {
75- const getAPIData = async ( ) => {
76- const {
77- playback_rate : newPlayRate ,
78- estimated_position : estimated_position ,
79- } = await synchronize ( state . sessionToken , [ 0 ] , state . timestamp ) ;
80- console . log ( "New play rate:" , newPlayRate ) ;
81- console . log ( "New timestamp:" , estimated_position ) ;
94+ const getAPIData = async ( ) => {
95+ analyserRef . current ?. getByteTimeDomainData ( dataArrayRef . current ) ;
96+ const {
97+ playback_rate : newPlayRate ,
98+ estimated_position : estimated_position ,
99+ } = await synchronize ( state . sessionToken , Array . from ( dataArrayRef . current ) , state . timestamp ) ;
82100
83- dispatch ( {
84- type : "increment" ,
85- time : estimated_position ,
86- rate : 1 ,
87- } ) ;
101+ dispatch ( {
102+ type : "increment" ,
103+ time : estimated_position ,
104+ rate : newPlayRate ,
105+ } ) ;
106+ }
107+
108+ // This function established new recording instances when re-entering play mode
109+ const startRecording = async ( ) => {
110+ // It's possible some of these can be removed; not sure which relate to the
111+ // making of the recorded object we don't need and which relate to the
112+ // buffer we send to the backend.
113+ startTimeRef . current = Date . now ( ) ;
114+ //create new Media recorder instance using the stream
115+ const media = new MediaRecorder ( stream , { mimeType : "audio/webm" } ) ;
116+ //set the MediaRecorder instance to the mediaRecorder ref
117+ mediaRecorder . current = media ;
118+ //invokes the start method to start the recording process
119+ mediaRecorder . current . start ( ) ;
120+ let localAudioChunks : Blob [ ] = [ ] ;
121+ mediaRecorder . current . ondataavailable = ( event ) => {
122+ if ( typeof event . data === "undefined" ) return ;
123+ if ( event . data . size === 0 ) return ;
124+ localAudioChunks . push ( event . data ) ;
88125 } ;
126+ setAudioChunks ( localAudioChunks ) ;
89127
90- // Start polling
91- setInterval ( ( ) => {
92- if ( state . playing ) {
93- getAPIData ( ) ;
128+ audioContextRef . current = new window . AudioContext ( ) ;
129+ const source = audioContextRef . current . createMediaStreamSource ( stream ) ;
130+ analyserRef . current = audioContextRef . current . createAnalyser ( ) ;
131+ analyserRef . current . fftSize = 2048 ;
132+ source . connect ( analyserRef . current ) ;
133+
134+ const bufferLength = analyserRef . current . frequencyBinCount ;
135+ dataArrayRef . current = new Uint8Array ( bufferLength ) ;
136+
137+ getAPIData ( ) ; // run the first call
138+ } ;
139+
140+ //stops the recording instance
141+ const stopRecording = ( ) => {
142+ mediaRecorder . current . stop ( ) ;
143+ audioContextRef . current ?. close ( ) ;
144+ } ;
145+
146+ // Function to get permission to use browser microphone
147+ const getMicrophonePermission = async ( ) => {
148+ if ( "MediaRecorder" in window ) {
149+ try {
150+ const streamData = await navigator . mediaDevices . getUserMedia ( {
151+ audio : true ,
152+ video : false ,
153+ } ) ;
154+ setPermission ( true ) ;
155+ setStream ( streamData ) ;
156+ } catch ( err ) {
157+ alert ( ( err as Error ) . message ) ;
94158 }
95- } , UPDATE_INTERVAL ) ;
96- } , [ state . playing , state . timestamp , state . sessionToken ] ) ;
97- // The "could be moved into any subcomponent" comment refers to the above
98- ///////////////////////////////////////////////////////////////////////////////
159+ } else {
160+ alert ( "The MediaRecorder API is not supported in your browser." ) ;
161+ }
162+ } ;
163+
164+ /////////////////////////////////////////////
165+ // Audio-related effects
166+ // Get microphone permission on first time entering play state
167+ useEffect ( ( ) => {
168+ if ( ! permission ) getMicrophonePermission ( ) ;
169+ } , [ state . inPlayMode ] ) ;
170+
171+ // Start and stop recording when player is or isn't playing
172+ useEffect ( ( ) => {
173+ if ( state . playing ) startRecording ( ) ;
174+ else stopRecording ( ) ;
175+ } , [ state . playing ] ) ;
176+
177+ // Keep synchronizing while playing
178+ useEffect ( ( ) => {
179+ if ( state . playing ) setTimeout ( getAPIData , UPDATE_INTERVAL ) ;
180+ } , [ state . timestamp ] )
99181
100182 ////////////////////////////////////////////////////////////////////////////////
101183 // Render the component's UI
102184 ////////////////////////////////////////////////////////////////////////////////
103185 return (
104186 < SafeAreaView style = { styles . container } >
105187 { /* Provides safe area insets for mobile devices */ }
106- < AudioRecorder state = { state } dispatch = { dispatch } />
107- < Text style = { styles . title } > Companion, the digital accompanist</ Text >
108-
109- < View style = { styles . button_wrapper } >
110- < Score_Select state = { state } dispatch = { dispatch } />
111- < TempoBox
188+ < View style = { styles . menu_bar } >
189+ < Image source = { { uri : './assets/companion.png' } } style = { styles . logo } />
190+ < Return_Button
112191 state = { state }
113192 dispatch = { dispatch }
114- wrapper_style = { styles . tempo_box }
115- text_input_style = { styles . text_input }
116- label_text_style = { styles . label }
117- />
118- < SynthesizeButton
119- state = { state }
120- dispatch = { dispatch }
121- button_style = { styles . synthesize_button }
193+ button_format = { styles . button_format }
122194 text_style = { styles . button_text }
123195 />
124196 < Start_Stop_Button
125197 state = { state }
126198 dispatch = { dispatch }
127- button_style = { styles . play_button }
199+ button_format = { styles . button_format }
128200 text_style = { styles . button_text }
129201 />
130- < MeasureSetBox
131- state = { state }
132- dispatch = { dispatch }
133- wrapper_style = { styles . measure_box }
134- text_input_style = { styles . text_input }
135- button_style = { styles . reset_button }
136- button_text_style = { styles . button_text }
137- label_text_style = { styles . label }
138- />
202+ {
203+ state . inPlayMode ?
204+ < MeasureSetBox
205+ state = { state }
206+ dispatch = { dispatch }
207+ button_style = { styles . button_format }
208+ button_text_style = { styles . button_text }
209+ />
210+ :
211+ < TempoBox
212+ state = { state }
213+ dispatch = { dispatch }
214+ label_text_style = { styles . button_text }
215+ />
216+ }
217+ </ View >
218+ < View style = { styles . main_area } >
219+ { // List of scores, show when not in play mode
220+ state . inPlayMode || < Score_Select state = { state } dispatch = { dispatch } /> }
221+ < ScoreDisplay state = { state } dispatch = { dispatch } />
139222 </ View >
140- < ScoreDisplay state = { state } dispatch = { dispatch } />
141223 < StatusBar style = "auto" />
142224 { /* Automatically adjusts the status bar style */ }
143225 < AudioPlayer state = { state } />
@@ -154,65 +236,37 @@ const styles = StyleSheet.create({
154236 justifyContent : "center" , // Center children vertically
155237 padding : 16 , // Add padding around the container
156238 } ,
157- title : {
158- fontSize : 20 , // Set the font size for the title
159- marginBottom : 20 , // Add space below the title
160- } ,
161- label : {
162- fontSize : 16 ,
163- } ,
164- synthesize_button : {
165- flex : 0.2 ,
166- borderColor : "black" ,
167- borderRadius : 15 ,
168- backgroundColor : "lightblue" ,
169- justifyContent : "center" ,
170- } ,
171- play_button : {
172- flex : 0.2 ,
173- borderColor : "black" ,
174- borderRadius : 15 ,
175- backgroundColor : "lightblue" ,
176- justifyContent : "center" ,
177- } ,
178- reset_button : {
179- flex : 0.8 ,
239+ button_format : {
180240 borderColor : "black" ,
181241 borderRadius : 15 ,
182242 backgroundColor : "lightblue" ,
243+ justifyContent : "center"
183244 } ,
184245 button_text : {
185- fontSize : 20 ,
246+ fontSize : 24 ,
186247 textAlign : "center" ,
187248 } ,
188- button_wrapper : {
189- flex : 1 ,
249+ menu_bar : {
250+ flex : 0 ,
190251 flexDirection : "row" ,
191252 justifyContent : "space-between" ,
192- padding : 10 ,
193253 backgroundColor : "lightgray" ,
194254 width : "100%" ,
195- minHeight : 72 ,
255+ minHeight : 100 ,
196256 } ,
197- measure_box : {
198- flexDirection : "row" ,
199- padding : 10 ,
200- justifyContent : "space-between" ,
201- width : "40%" ,
202- flex : 0.4 ,
203- height : "80%" ,
204- } ,
205- tempo_box : {
257+ main_area : {
258+ flex : 1 ,
206259 flexDirection : "row" ,
207- padding : 10 ,
208260 justifyContent : "space-between" ,
209- width : "20% " ,
210- flex : 0.2 ,
261+ backgroundColor : "white " ,
262+ width : "100%" ,
211263 height : "80%" ,
212264 } ,
213- text_input : {
265+ logo : {
214266 backgroundColor : "white" ,
215- flex : 0.3 ,
267+ flex : 0.25 ,
268+ width : "25%" ,
216269 height : "100%" ,
270+ resizeMode : 'contain'
217271 } ,
218272} ) ;
0 commit comments