diff --git a/src/bundles/sound/functions.ts b/src/bundles/sound/functions.ts index d8b1d20cb..f1d296e73 100644 --- a/src/bundles/sound/functions.ts +++ b/src/bundles/sound/functions.ts @@ -1,19 +1,19 @@ /** - * The sounds library provides functions for constructing and playing sounds. + * The `sound` module provides functions for constructing and playing sounds. * * A wave is a function that takes in a number `t` and returns * a number representing the amplitude at time `t`. * The amplitude should fall within the range of [-1, 1]. * - * A Sound is a pair(wave, duration) where duration is the length of the sound in seconds. + * A Sound is a pair(wave, duration) where duration is the length of the Sound in seconds. * The constructor make_sound and accessors get_wave and get_duration are provided. * * Sound Discipline: - * For all sounds, the wave function applied to and time `t` beyond its duration returns 0, that is: + * For all Sounds, the wave function applied to and time `t` beyond its duration returns 0, that is: * `(get_wave(sound))(get_duration(sound) + x) === 0` for any x >= 0. * * Two functions which combine Sounds, `consecutively` and `simultaneously` are given. - * Additionally, we provide sound transformation functions `adsr` and `phase_mod` + * Additionally, we provide Sound transformation functions `adsr` and `phase_mod` * which take in a Sound and return a Sound. * * Finally, the provided `play` function takes in a Sound and plays it using your @@ -127,7 +127,7 @@ const recording_signal_ms = 100; const pre_recording_signal_pause_ms = 200; function play_recording_signal() { - play_concurrently(sine_sound(1200, recording_signal_ms / 1000)); + play(sine_sound(1200, recording_signal_ms / 1000)); } // eslint-disable-next-line @typescript-eslint/no-shadow @@ -175,10 +175,11 @@ export function init_record(): string { } /** - * takes a buffer duration (in seconds) as argument, and + * Records a sound until the returned stop function is called. + * Takes a buffer duration (in seconds) as argument, and * returns a nullary stop function stop. A call - * stop() returns a sound promise: a nullary function - * that returns a sound. Example:
init_record();
+ * stop() returns a Sound promise: a nullary function
+ * that returns a Sound. Example: 
init_record();
  * const stop = record(0.5);
  * // record after 0.5 seconds. Then in next query:
  * const promise = stop();
@@ -188,7 +189,7 @@ export function init_record(): string {
  * @param buffer - pause before recording, in seconds
  * @returns nullary stop function;
  * stop() stops the recording and
- * returns a sound promise: a nullary function that returns the recorded sound
+ * returns a Sound promise: a nullary function that returns the recorded Sound
  */
 export function record(buffer: number): () => () => Sound {
   check_permission();
@@ -213,15 +214,15 @@ export function record(buffer: number): () => () => Sound {
 /**
  * Records a sound of given duration in seconds, after
  * a buffer also in seconds, and
- * returns a sound promise: a nullary function
- * that returns a sound. Example: 
init_record();
+ * returns a Sound promise: a nullary function
+ * that returns a Sound. Example: 
init_record();
  * const promise = record_for(2, 0.5);
- * // In next query, you can play the promised sound, by
+ * // In next query, you can play the promised Sound, by
  * // applying the promise:
  * play(promise());
* @param duration duration in seconds * @param buffer pause before recording, in seconds - * @return promise: nullary function which returns recorded sound + * @return promise: nullary function which returns recorded Sound */ export function record_for(duration: number, buffer: number): () => Sound { recorded_sound = undefined; @@ -270,8 +271,8 @@ export function record_for(duration: number, buffer: number): () => Sound { * that takes in a non-negative input time and returns an amplitude * between -1 and 1. * - * @param wave wave function of the sound - * @param duration duration of the sound + * @param wave wave function of the Sound + * @param duration duration of the Sound * @return with wave as wave function and duration as duration * @example const s = make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5); */ @@ -319,33 +320,33 @@ export function is_sound(x: any): x is Sound { /** * Plays the given Wave using the computer’s sound device, for the duration * given in seconds. - * The sound is only played if no other sounds are currently being played. * * @param wave the wave function to play, starting at 0 - * @return the given sound + * @return the resulting Sound * @example play_wave(t => math_sin(t * 3000), 5); */ -export function play_wave(wave: Wave, duration: number): AudioPlayed { +export function play_wave(wave: Wave, duration: number): Sound { return play(make_sound(wave, duration)); } /** * Plays the given Sound using the computer’s sound device. - * The sound is only played if no other sounds are currently being played. + * The sound is added to a list of sounds to be played one-at-a-time + * in a Source Academy tab. * - * @param sound the sound to play - * @return the given sound - * @example play(sine_sound(440, 5)); + * @param sound the Sound to play + * @return the given Sound + * @example play_in_tab(sine_sound(440, 5)); */ -export function play(sound: Sound): AudioPlayed { +export function play_in_tab(sound: Sound): Sound { // Type-check sound if (!is_sound(sound)) { - throw new Error(`${play.name} is expecting sound, but encountered ${sound}`); + throw new Error(`${play_in_tab.name} is expecting sound, but encountered ${sound}`); // If a sound is already playing, terminate execution. } else if (isPlaying) { - throw new Error(`${play.name}: audio system still playing previous sound`); + throw new Error(`${play_in_tab.name}: audio system still playing previous sound`); } else if (get_duration(sound) < 0) { - throw new Error(`${play.name}: duration of sound is negative`); + throw new Error(`${play_in_tab.name}: duration of sound is negative`); } else { // Instantiate audio context if it has not been instantiated. if (!audioplayer) { @@ -391,43 +392,31 @@ export function play(sound: Sound): AudioPlayed { riffwave.header.bitsPerSample = 16; riffwave.Make(channel); - /* - const audio = new Audio(riffwave.dataURI); - const source2 = audioplayer.createMediaElementSource(audio); - source2.connect(audioplayer.destination); - - // Connect data to output destination - isPlaying = true; - audio.play(); - audio.onended = () => { - source2.disconnect(audioplayer.destination); - isPlaying = false; - }; */ - const soundToPlay = { toReplString: () => '', dataUri: riffwave.dataURI, }; audioPlayed.push(soundToPlay); - return soundToPlay; + return sound; } } /** * Plays the given Sound using the computer’s sound device - * on top of any sounds that are currently playing. + * on top of any Sounds that are currently playing. * - * @param sound the sound to play - * @example play_concurrently(sine_sound(440, 5)); + * @param sound the Sound to play + * @return the given Sound + * @example play(sine_sound(440, 5)); */ -export function play_concurrently(sound: Sound): void { +export function play(sound: Sound): Sound { // Type-check sound if (!is_sound(sound)) { throw new Error( - `${play_concurrently.name} is expecting sound, but encountered ${sound}`, + `${play.name} is expecting sound, but encountered ${sound}`, ); - } else if (get_duration(sound) <= 0) { - // Do nothing + } else if (get_duration(sound) < 0) { + throw new Error(`${play.name}: duration of sound is negative`); } else { // Instantiate audio context if it has not been instantiated. if (!audioplayer) { @@ -475,6 +464,7 @@ export function play_concurrently(sound: Sound): void { source.disconnect(audioplayer.destination); isPlaying = false; }; + return sound; } } @@ -489,10 +479,10 @@ export function stop(): void { // Primitive sounds /** - * Makes a noise sound with given duration + * Makes a noise Sound with given duration * * @param duration the duration of the noise sound - * @return resulting noise sound + * @return resulting noise Sound * @example noise_sound(5); */ export function noise_sound(duration: number): Sound { @@ -500,10 +490,10 @@ export function noise_sound(duration: number): Sound { } /** - * Makes a silence sound with given duration + * Makes a silence Sound with given duration * - * @param duration the duration of the silence sound - * @return resulting silence sound + * @param duration the duration of the silence Sound + * @return resulting silence Sound * @example silence_sound(5); */ export function silence_sound(duration: number): Sound { @@ -511,11 +501,11 @@ export function silence_sound(duration: number): Sound { } /** - * Makes a sine wave sound with given frequency and duration + * Makes a sine wave Sound with given frequency and duration * - * @param freq the frequency of the sine wave sound - * @param duration the duration of the sine wave sound - * @return resulting sine wave sound + * @param freq the frequency of the sine wave Sound + * @param duration the duration of the sine wave Sound + * @return resulting sine wave Sound * @example sine_sound(440, 5); */ export function sine_sound(freq: number, duration: number): Sound { @@ -523,11 +513,11 @@ export function sine_sound(freq: number, duration: number): Sound { } /** - * Makes a square wave sound with given frequency and duration + * Makes a square wave Sound with given frequency and duration * - * @param freq the frequency of the square wave sound - * @param duration the duration of the square wave sound - * @return resulting square wave sound + * @param freq the frequency of the square wave Sound + * @param duration the duration of the square wave Sound + * @return resulting square wave Sound * @example square_sound(440, 5); */ export function square_sound(f: number, duration: number): Sound { @@ -545,11 +535,11 @@ export function square_sound(f: number, duration: number): Sound { } /** - * Makes a triangle wave sound with given frequency and duration + * Makes a triangle wave Sound with given frequency and duration * - * @param freq the frequency of the triangle wave sound - * @param duration the duration of the triangle wave sound - * @return resulting triangle wave sound + * @param freq the frequency of the triangle wave Sound + * @param duration the duration of the triangle wave Sound + * @return resulting triangle wave Sound * @example triangle_sound(440, 5); */ export function triangle_sound(freq: number, duration: number): Sound { @@ -569,11 +559,11 @@ export function triangle_sound(freq: number, duration: number): Sound { } /** - * Makes a sawtooth wave sound with given frequency and duration + * Makes a sawtooth wave Sound with given frequency and duration * - * @param freq the frequency of the sawtooth wave sound - * @param duration the duration of the sawtooth wave sound - * @return resulting sawtooth wave sound + * @param freq the frequency of the sawtooth wave Sound + * @param duration the duration of the sawtooth wave Sound + * @return resulting sawtooth wave Sound * @example sawtooth_sound(440, 5); */ export function sawtooth_sound(freq: number, duration: number): Sound { @@ -594,11 +584,11 @@ export function sawtooth_sound(freq: number, duration: number): Sound { /** * Makes a new Sound by combining the sounds in a given list - * where the second sound is appended to the end of the first sound, - * the third sound is appended to the end of the second sound, and - * so on. The effect is that the sounds in the list are joined end-to-end + * where the second Sound is appended to the end of the first Sound, + * the third Sound is appended to the end of the second Sound, and + * so on. The effect is that the Sounds in the list are joined end-to-end * - * @param list_of_sounds given list of sounds + * @param list_of_sounds given list of Sounds * @return the combined Sound * @example consecutively(list(sine_sound(200, 2), sine_sound(400, 3))); */ @@ -615,10 +605,10 @@ export function consecutively(list_of_sounds: List): Sound { } /** - * Makes a new Sound by combining the sounds in a given list - * where all the sounds are overlapped on top of each other. + * Makes a new Sound by combining the Sounds in a given list + * where all the Sounds are overlapped on top of each other. * - * @param list_of_sounds given list of sounds + * @param list_of_sounds given list of Sounds * @return the combined Sound * @example simultaneously(list(sine_sound(200, 2), sine_sound(400, 3))) */ @@ -730,7 +720,7 @@ export function stacking_adsr( } /** - * Returns a SoundTransformer which uses its argument + * Returns a Sound transformer which uses its argument * to modulate the phase of a (carrier) sine wave * of given frequency and duration with a given Sound. * Modulating with a low frequency Sound results in a vibrato effect. @@ -738,7 +728,7 @@ export function stacking_adsr( * the sine wave frequency results in more complex wave forms. * * @param freq the frequency of the sine wave to be modulated - * @param duration the duration of the output soud + * @param duration the duration of the output Sound * @param amount the amount of modulation to apply to the carrier sine wave * @return function which takes in a Sound and returns a Sound * @example phase_mod(440, 5, 1)(sine_sound(220, 5)); diff --git a/src/bundles/sound/index.ts b/src/bundles/sound/index.ts index 7b128739f..9d0b2c7cb 100644 --- a/src/bundles/sound/index.ts +++ b/src/bundles/sound/index.ts @@ -21,8 +21,8 @@ export { phase_mod, piano, // Play-related + play_in_tab, play, - play_concurrently, play_wave, record, record_for, diff --git a/src/bundles/stereo_sound/functions.ts b/src/bundles/stereo_sound/functions.ts index 64ee4fa0d..84ddb23ca 100644 --- a/src/bundles/stereo_sound/functions.ts +++ b/src/bundles/stereo_sound/functions.ts @@ -1,9 +1,10 @@ /** * - * The stereo sounds library build on the sounds library by accommodating stereo sounds. - * Within this library, all sounds are represented in stereo, with two waves, left and right. + * The `stereo_sound` module build on the `sound` module by accommodating stereo sounds. + * Within this module, all Sounds are represented in stereo, with two waves, left and right. * - * A Stereo Sound is a `pair(pair(left_wave, right_wave), duration)` where duration is the length of the sound in seconds. + * A Stereo Sound (just denoted as "Sound" in this document) is + a `pair(pair(left_wave, right_wave), duration)` where duration is the length of the Sound in seconds. * The constructor `make_stereo_sound` and accessors `get_left_wave`, `get_right_wave`, and `get_duration` are provided. * The `make_sound` constructor from sounds is syntatic sugar for `make_stereo_sounds` with equal waves. * @@ -114,7 +115,7 @@ function start_recording(mediaRecorder: MediaRecorder) { const recording_signal_duration_ms = 100; function play_recording_signal() { - play_concurrently(sine_sound(1200, recording_signal_duration_ms / 1000)); + play(sine_sound(1200, recording_signal_duration_ms / 1000)); } // eslint-disable-next-line @typescript-eslint/no-shadow @@ -162,20 +163,21 @@ export function init_record(): string { } /** - * takes a buffer duration (in seconds) as argument, and + * Records a sound until the returned stop function is called. + * Takes a buffer duration (in seconds) as argument, and * returns a nullary stop function stop. A call - * stop() returns a sound promise: a nullary function - * that returns a sound. Example:
init_record();
+ * stop() returns a Sound promise: a nullary function
+ * that returns a Sound. Example: 
init_record();
  * const stop = record(0.5);
  * // record after 0.5 seconds. Then in next query:
  * const promise = stop();
- * // In next query, you can play the promised sound, by
+ * // In next query, you can play the promised Sound, by
  * // applying the promise:
  * play(promise());
* @param buffer - pause before recording, in seconds * @returns nullary stop function; * stop() stops the recording and - * returns a sound promise: a nullary function that returns the recorded sound + * returns a sound promise: a nullary function that returns the recorded Sound */ export function record(buffer: number): () => () => Sound { check_permission(); @@ -200,8 +202,8 @@ export function record(buffer: number): () => () => Sound { /** * Records a sound of given duration in seconds, after * a buffer also in seconds, and - * returns a sound promise: a nullary function - * that returns a sound. Example:
init_record();
+ * returns a Sound promise: a nullary function
+ * that returns a Sound. Example: 
init_record();
  * const promise = record_for(2, 0.5);
  * // In next query, you can play the promised sound, by
  * // applying the promise:
@@ -248,11 +250,11 @@ export function record_for(duration: number, buffer: number): () => Sound {
  * that takes in a non-negative input time and returns an amplitude
  * between -1 and 1.
  *
- * @param left_wave wave function of the left channel of the sound
- * @param right_wave wave function of the right channel of the sound
- * @param duration duration of the sound
- * @return resulting stereo sound
- * @example const s = make_stereo_sound(t => Math_sin(2 * Math_PI * 440 * t), t => Math_sin(2 * Math_PI * 300 * t), 5);
+ * @param left_wave wave function of the left channel of the Sound
+ * @param right_wave wave function of the right channel of the Sound
+ * @param duration duration of the Sound
+ * @return resulting stereo Sound
+ * @example const s = make_stereo_sound(t => math_sin(2 * math_PI * 440 * t), t => math_sin(2 * math_PI * 300 * t), 5);
  */
 export function make_stereo_sound(
   left_wave: Wave,
@@ -274,10 +276,10 @@ export function make_stereo_sound(
  * that takes in a non-negative input time and returns an amplitude
  * between -1 and 1.
  *
- * @param wave wave function of the sound
- * @param duration duration of the sound
- * @return with wave as wave function and duration as duration
- * @example const s = make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5);
+ * @param wave wave function of the Sound
+ * @param duration duration of the Sound
+ * @return Sound with the given `wave` function for both channels and `duration` as duration
+ * @example const s = make_sound(t => math_sin(2 * math_PI * 440 * t), 5);
  */
 export function make_sound(wave: Wave, duration: number): Sound {
   return make_stereo_sound(wave, wave, duration);
@@ -288,7 +290,7 @@ export function make_sound(wave: Wave, duration: number): Sound {
  *
  * @param sound given Sound
  * @return the wave function of the Sound
- * @example get_wave(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns t => Math_sin(2 * Math_PI * 440 * t)
+ * @example get_wave(make_sound(t => math_sin(2 * math_PI * 440 * t), 5)); // Returns t => math_sin(2 * math_PI * 440 * t)
  */
 export function get_left_wave(sound: Sound): Wave {
   return head(head(sound));
@@ -299,7 +301,7 @@ export function get_left_wave(sound: Sound): Wave {
  *
  * @param sound given Sound
  * @return the wave function of the Sound
- * @example get_wave(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns t => Math_sin(2 * Math_PI * 440 * t)
+ * @example get_wave(make_sound(t => math_sin(2 * math_PI * 440 * t), 5)); // Returns t => math_sin(2 * math_PI * 440 * t)
  */
 export function get_right_wave(sound: Sound): Wave {
   return tail(head(sound));
@@ -310,7 +312,7 @@ export function get_right_wave(sound: Sound): Wave {
  *
  * @param sound given Sound
  * @return the duration of the Sound
- * @example get_duration(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns 5
+ * @example get_duration(make_sound(t => math_sin(2 * math_PI * 440 * t), 5)); // Returns 5
  */
 export function get_duration(sound: Sound): number {
   return tail(sound);
@@ -335,13 +337,12 @@ export function is_sound(x: any): boolean {
 /**
  * Plays the given Wave using the computer’s sound device, for the duration
  * given in seconds.
- * The sound is only played if no other sounds are currently being played.
  *
  * @param wave the wave function to play, starting at 0
- * @return the given sound
+ * @return the given Sound
  * @example play_wave(t => math_sin(t * 3000), 5);
  */
-export function play_wave(wave: Wave, duration: number): AudioPlayed {
+export function play_wave(wave: Wave, duration: number): Sound {
   return play(make_sound(wave, duration));
 }
 
@@ -349,38 +350,38 @@ export function play_wave(wave: Wave, duration: number): AudioPlayed {
  * Plays the given two Waves using the computer’s sound device, for the duration
  * given in seconds. The first Wave is for the left channel, the second for the
  * right channel.
- * The sound is only played if no other sounds are currently being played.
  *
  * @param wave1 the wave function to play on the left channel, starting at 0
  * @param wave2 the wave function to play on the right channel, starting at 0
- * @return the given sound
+ * @return the given Sound
  * @example play_waves(t => math_sin(t * 3000), t => math_sin(t * 6000), 5);
  */
 export function play_waves(
   wave1: Wave,
   wave2: Wave,
   duration: number,
-): AudioPlayed {
+): Sound {
   return play(make_stereo_sound(wave1, wave2, duration));
 }
 
 /**
  * Plays the given Sound using the computer’s sound device.
- * The sound is only played if no other sounds are currently being played.
+ * The Sound is added to a list of Sounds to be played one-at-a-time
+ * in a Source Academy tab.
  *
- * @param sound the sound to play
- * @return the given sound
- * @example play(sine_sound(440, 5));
+ * @param sound the Sound to play
+ * @return the given Sound
+ * @example play_in_tab(sine_sound(440, 5));
  */
-export function play(sound: Sound): AudioPlayed {
+export function play_in_tab(sound: Sound): Sound {
   // Type-check sound
   if (!is_sound(sound)) {
-    throw new Error(`${play.name} is expecting sound, but encountered ${sound}`);
+    throw new Error(`${play_in_tab.name} is expecting sound, but encountered ${sound}`);
     // If a sound is already playing, terminate execution.
   } else if (isPlaying) {
-    throw new Error(`${play.name}: audio system still playing previous sound`);
+    throw new Error(`${play_in_tab.name}: audio system still playing previous sound`);
   } else if (get_duration(sound) < 0) {
-    throw new Error(`${play.name}: duration of sound is negative`);
+    throw new Error(`${play_in_tab.name}: duration of sound is negative`);
   } else {
     // Instantiate audio context if it has not been instantiated.
     if (!audioplayer) {
@@ -450,96 +451,89 @@ export function play(sound: Sound): AudioPlayed {
     riffwave.header.bitsPerSample = 16;
     riffwave.Make(channel);
 
-    /*
-    const audio = new Audio(riffwave.dataURI);
-    const source2 = audioplayer.createMediaElementSource(audio);
-    source2.connect(audioplayer.destination);
-
-    // Connect data to output destination
-    isPlaying = true;
-    audio.play();
-    audio.onended = () => {
-      source2.disconnect(audioplayer.destination);
-      isPlaying = false;
-    }; */
-
     const audio = {
       toReplString: () => '',
       dataUri: riffwave.dataURI,
     };
 
     audioPlayed.push(audio);
-    return audio;
+    return sound;
   }
 }
 
 /**
  * Plays the given Sound using the computer’s sound device
- * on top of any sounds that are currently playing.
+ * on top of any Sounds that are currently playing.
  *
- * @param sound the sound to play
- * @example play_concurrently(sine_sound(440, 5));
+ * @param sound the Sound to play
+ * @return the given Sound
+ * @example play(sine_sound(440, 5));
  */
-export function play_concurrently(sound: Sound): void {
+export function play(sound: Sound): Sound {
   // Type-check sound
   if (!is_sound(sound)) {
-    throw new Error(
-      `${play_concurrently.name} is expecting sound, but encountered ${sound}`,
-    );
-  } else if (get_duration(sound) <= 0) {
-    // Do nothing
+    throw new Error(`${play.name} is expecting sound, but encountered ${sound}`);
+    // If a sound is already playing, terminate execution.
+  } else if (isPlaying) {
+    throw new Error(`${play.name}: audio system still playing previous sound`);
+  } else if (get_duration(sound) < 0) {
+    throw new Error(`${play.name}: duration of sound is negative`);
   } else {
     // Instantiate audio context if it has not been instantiated.
     if (!audioplayer) {
       init_audioCtx();
     }
 
-    const channel: number[] = Array[2 * Math.ceil(FS * get_duration(sound))];
+    const channel: number[] = [];
+    const len = Math.ceil(FS * get_duration(sound));
 
     let Ltemp: number;
     let Rtemp: number;
-    let prev_value = 0;
+    let Lprev_value = 0;
+    let Rprev_value = 0;
 
     const left_wave = get_left_wave(sound);
-
-    for (let i = 0; i < channel.length; i += 2) {
+    const right_wave = get_right_wave(sound);
+    for (let i = 0; i < len; i += 1) {
       Ltemp = left_wave(i / FS);
       // clip amplitude
       if (Ltemp > 1) {
-        channel[i] = 1;
+        channel[2 * i] = 1;
       } else if (Ltemp < -1) {
-        channel[i] = -1;
+        channel[2 * i] = -1;
       } else {
-        channel[i] = Ltemp;
+        channel[2 * i] = Ltemp;
       }
 
       // smoothen out sudden cut-outs
-      if (channel[i] === 0 && Math.abs(channel[i] - prev_value) > 0.01) {
-        channel[i] = prev_value * 0.999;
+      if (
+        channel[2 * i] === 0
+        && Math.abs(channel[2 * i] - Lprev_value) > 0.01
+      ) {
+        channel[2 * i] = Lprev_value * 0.999;
       }
 
-      prev_value = channel[i];
-    }
+      Lprev_value = channel[2 * i];
 
-    prev_value = 0;
-    const right_wave = get_right_wave(sound);
-    for (let i = 1; i < channel.length; i += 2) {
       Rtemp = right_wave(i / FS);
       // clip amplitude
       if (Rtemp > 1) {
-        channel[i] = 1;
+        channel[2 * i + 1] = 1;
       } else if (Rtemp < -1) {
-        channel[i] = -1;
+        channel[2 * i + 1] = -1;
       } else {
-        channel[i] = Rtemp;
+        channel[2 * i + 1] = Rtemp;
       }
 
       // smoothen out sudden cut-outs
-      if (channel[i] === 0 && Math.abs(channel[i] - prev_value) > 0.01) {
-        channel[i] = prev_value * 0.999;
+      if (
+        channel[2 * i + 1] === 0
+        && Math.abs(channel[2 * i] - Rprev_value) > 0.01
+      ) {
+        channel[2 * i + 1] = Rprev_value * 0.999;
       }
 
-      prev_value = channel[i];
+      Rprev_value = channel[2 * i + 1];
     }
 
     // quantize
@@ -563,6 +557,7 @@ export function play_concurrently(sound: Sound): void {
       source2.disconnect(audioplayer.destination);
       isPlaying = false;
     };
+    return sound;
   }
 }
 
diff --git a/src/bundles/stereo_sound/index.ts b/src/bundles/stereo_sound/index.ts
index abd2dd07b..40f02ec2a 100644
--- a/src/bundles/stereo_sound/index.ts
+++ b/src/bundles/stereo_sound/index.ts
@@ -10,10 +10,10 @@ export {
   pan,
   pan_mod,
   // Play-related
-  play,
+  play_in_tab,
   play_wave,
   play_waves,
-  play_concurrently,
+  play,
   stop,
   // Recording
   init_record,