@@ -24,11 +24,17 @@ import type {
2424// Global Constants and Variables
2525const FS : number = 44100 ; // Output sample rate
2626const fourier_expansion_level : number = 5 ; // fourier expansion level
27+ /**
28+ * duration of recording signal in milliseconds
29+ */
30+ const recording_signal_ms = 100 ;
31+ /**
32+ * duration of pause after "record" before recording signal is played
33+ */
34+ const pre_recording_signal_pause_ms = 200 ;
2735
2836const audioPlayed : AudioPlayed [ ] = [ ] ;
29- context . moduleContexts . sound . state = {
30- audioPlayed
31- } ;
37+ context . moduleContexts . sound . state = { audioPlayed } ;
3238
3339interface BundleGlobalVars {
3440 /**
@@ -58,7 +64,10 @@ export const globalVars: BundleGlobalVars = {
5864 audioplayer : null
5965} ;
6066
61- // Instantiates new audio context
67+ /**
68+ * Returns the current AudioContext in use for the bundle. If
69+ * none, initializes a new context and returns it.
70+ */
6271function getAudioContext ( ) {
6372 if ( ! globalVars . audioplayer ) {
6473 globalVars . audioplayer = new AudioContext ( ) ;
@@ -79,8 +88,10 @@ function linear_decay(decay_period: number): (t: number) => number {
7988// ---------------------------------------------
8089// Microphone Functionality
8190// ---------------------------------------------
82- // check_permission is called whenever we try
83- // to record a sound
91+ /**
92+ * Determine if the user has already provided permission to use the
93+ * microphone and return the provided MediaStream if they have.
94+ */
8495function check_permission ( ) {
8596 if ( globalVars . stream === null ) {
8697 throw new Error ( 'Call init_record(); to obtain permission to use microphone' ) ;
@@ -93,39 +104,41 @@ function check_permission() {
93104 return globalVars . stream ;
94105}
95106
107+ /**
108+ * Set up the provided MediaRecorder and begin the recording
109+ * process.
110+ */
96111function start_recording ( mediaRecorder : MediaRecorder ) {
97112 const data : Blob [ ] = [ ] ;
98113 mediaRecorder . ondataavailable = ( e ) => e . data . size && data . push ( e . data ) ;
99114 mediaRecorder . start ( ) ;
100115 mediaRecorder . onstop = ( ) => process ( data ) ;
101116}
102117
103- // duration of recording signal in milliseconds
104- const recording_signal_ms = 100 ;
105-
106- // duration of pause after "run" before recording signal is played
107- const pre_recording_signal_pause_ms = 200 ;
108-
109118function play_recording_signal ( ) {
110119 play ( sine_sound ( 1200 , recording_signal_ms / 1000 ) ) ;
111120}
112121
122+ /**
123+ * Converts the data received from the MediaRecorder into an AudioBuffer.
124+ */
113125function process ( data : Blob [ ] ) {
114126 const audioContext = new AudioContext ( ) ;
115127 const blob = new Blob ( data ) ;
116-
117- convertToArrayBuffer ( blob )
118- . then ( arrayBuffer => audioContext . decodeAudioData ( arrayBuffer ) )
119- . then ( save ) ;
120- }
121-
122- // Converts input microphone sound (blob) into array format.
123- async function convertToArrayBuffer ( blob : Blob ) : Promise < ArrayBuffer > {
124128 const url = URL . createObjectURL ( blob ) ;
125- const response = await fetch ( url ) ;
126- return response . arrayBuffer ( ) ;
129+ fetch ( url )
130+ . then ( async response => {
131+ const arrayBuffer = await response . arrayBuffer ( ) ;
132+ const decodedBuffer = await audioContext . decodeAudioData ( arrayBuffer ) ;
133+ save ( decodedBuffer ) ;
134+ } ) ;
127135}
128136
137+ /**
138+ * Converts the data stored in the provided AudioBuffer, converts it
139+ * into a Sound and then stores it into the `globalVars.recordedSound`
140+ * variable.
141+ */
129142function save ( audioBuffer : AudioBuffer ) {
130143 const array = audioBuffer . getChannelData ( 0 ) ;
131144 const duration = array . length / FS ;
@@ -140,29 +153,6 @@ function save(audioBuffer: AudioBuffer) {
140153 } , duration ) ;
141154}
142155
143- /**
144- * Throws an exception if duration is not a number or if
145- * number is negative
146- */
147- function validateDuration ( func_name : string , duration : unknown ) : asserts duration is number {
148- if ( typeof duration !== 'number' ) {
149- throw new Error ( `${ func_name } expects a number for duration, got ${ duration } ` ) ;
150- }
151-
152- if ( duration < 0 ) {
153- throw new Error ( `${ func_name } : Sound duration must be greater than or equal to 0` ) ;
154- }
155- }
156-
157- /**
158- * Throws an exception if wave is not a function
159- */
160- function validateWave ( func_name : string , wave : unknown ) : asserts wave is Wave {
161- if ( typeof wave !== 'function' ) {
162- throw new Error ( `${ func_name } expects a wave, got ${ wave } ` ) ;
163- }
164- }
165-
166156/**
167157 * Initialize recording by obtaining permission
168158 * to use the default device microphone
@@ -183,8 +173,15 @@ export function init_record(): string {
183173/**
184174 * Records a sound until the returned stop function is called.
185175 * Takes a buffer duration (in seconds) as argument, and
186- * returns a nullary stop. A call to the stop function returns
187- * a Sound promise:
176+ * returns a nullary stop. A call to the stop function returns a Sound promise.
177+ *
178+ * How the function behaves in detail:
179+ * 1. `record` is called.
180+ * 2. The function waits for the given buffer duration.
181+ * 3. The recording signal is played.
182+ * 4. Recording begins when the recording signal finishes.
183+ * 5. Recording stops when the returned stop function is called.
184+ *
188185 * @example
189186 * ```ts
190187 * init_record();
@@ -196,6 +193,10 @@ export function init_record(): string {
196193 * @param buffer - pause before recording, in seconds
197194 */
198195export function record ( buffer : number ) : ( ) => SoundPromise {
196+ if ( typeof buffer !== 'number' || buffer < 0 ) {
197+ throw new Error ( `${ record . name } : Expected a positive number for buffer, got ${ buffer } ` ) ;
198+ }
199+
199200 if ( globalVars . isPlaying ) {
200201 throw new Error ( `${ record . name } : Cannot record while another sound is playing!` ) ;
201202 }
@@ -229,17 +230,24 @@ export function record(buffer: number): () => SoundPromise {
229230}
230231
231232/**
232- * Records a sound of given <CODE>duration</CODE> in seconds, after
233- * a <CODE>buffer</CODE> also in seconds, and
234- * returns a Sound promise: a nullary function
235- * that returns a Sound. Example: <PRE><CODE>init_record();
236- * const promise = record_for(2, 0.5);
237- * // In next query, you can play the promised Sound, by
238- * // applying the promise:
239- * play(promise());</CODE></PRE>
233+ * Records a sound of a given duration. Returns a Sound promise.
234+ *
235+ * How the function behaves in detail:
236+ * 1. `record_for` is called.
237+ * 2. The function waits for the given buffer duration.
238+ * 3. The recording signal is played.
239+ * 4. Recording begins when the recording signal finishes.
240+ * 5. The recording signal plays to signal the end after the given duration.
241+ *
242+ * @example
243+ * ```
244+ * init_record();
245+ * const promise = record_for(2, 0.5); // begin recording after 0.5s for 2s
246+ * const sound = promise(); // retrieve the recorded sound
247+ * play(sound); // and do whatever with it
248+ * ```
240249 * @param duration duration in seconds
241250 * @param buffer pause before recording, in seconds
242- * @return <CODE>promise</CODE>: nullary function which returns recorded Sound
243251 */
244252export function record_for ( duration : number , buffer : number ) : SoundPromise {
245253 if ( globalVars . isPlaying ) {
@@ -278,15 +286,28 @@ export function record_for(duration: number, buffer: number): SoundPromise {
278286 return promise ;
279287}
280288
281- // =============================================================================
282- // Module's Exposed Functions
283- //
284- // This file only includes the implementation and documentation of exposed
285- // functions of the module. For private functions dealing with the browser's
286- // graphics library context, see './webGL_curves.ts'.
287- // =============================================================================
289+ /**
290+ * Throws an exception if duration is not a number or if
291+ * number is negative
292+ */
293+ function validateDuration ( func_name : string , duration : unknown ) : asserts duration is number {
294+ if ( typeof duration !== 'number' ) {
295+ throw new Error ( `${ func_name } expects a number for duration, got ${ duration } ` ) ;
296+ }
297+
298+ if ( duration < 0 ) {
299+ throw new Error ( `${ func_name } : Sound duration must be greater than or equal to 0` ) ;
300+ }
301+ }
288302
289- // Core functions
303+ /**
304+ * Throws an exception if wave is not a function
305+ */
306+ function validateWave ( func_name : string , wave : unknown ) : asserts wave is Wave {
307+ if ( typeof wave !== 'function' ) {
308+ throw new Error ( `${ func_name } expects a wave, got ${ wave } ` ) ;
309+ }
310+ }
290311
291312/**
292313 * Makes a Sound with given wave function and duration.
0 commit comments