Recording the Output of Oscillators

We wanted simple sound files for our experiments with audio in Smart Pascal. This example is based on the JavaScript in the vital Mozilla page and must be run in Firefox.

This is a form-based application with just a button on the form. The caption of the button is changed to Record using the Property Inspector. The caption changes to Stop when the button is pressed. The new page that opens automatically on pressing the button a second time has an audio player to play the recorded sound. You might be able save the page to obtain a sound file such as Test.oga. After many experiments we were unable to find the reason why the sound file saved successfully only on some occasions, even with the same HTML file in Firefox. Possibly the precise length of the recording was critical. We supply also a JavaScript version, which consistently saved the sound file for us.

The Smart Pascal code of the unit follows.

unit Form1;

interface

uses
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button,
  SmartCL.MediaElements, W3C.WebAudio, W3C.MediaStreamRecording;

type
  TForm1 = class(TW3Form)
    procedure W3Button1Click(Sender: TObject);
  private
    {$I 'Form1:intf'}
    FAudioContext: JAudioContext;
    Oscillator1, Oscillator2: JOscillatorNode;
    mediaRecorder: JMediaRecorder;
    chunks: array of variant;
    blob: variant;
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
  end;

implementation

{ TForm1 }

procedure TForm1.W3Button1Click(Sender: TObject);
begin
  if W3Button1.Caption = 'Record' then
    begin
      asm
        var dest = (@FAudioContext).createMediaStreamDestination();
        @mediaRecorder = new MediaRecorder(dest.stream);
        (@Oscillator1).frequency.value = 523.25;
        (@Oscillator2).frequency.value = 510.25;
        (@Oscillator1).connect(dest);
        (@Oscillator2).connect(dest);
        (@mediaRecorder).start();
        (@Oscillator1).start(0);
        (@Oscillator2).start(0);
        (@mediaRecorder).onstop = function(evt) {
           // Make Blob out of blobs and open it.
           @blob = new Blob(@chunks, { 'type' : 'audio/ogg; codecs=opus' });
           window.location.href = URL.createObjectURL(@blob);
        };
        (@mediaRecorder).ondataavailable = function(evt) {
          // push each chunk (blobs) in an array
          (@chunks).push(evt.data);
        };
      end;
      W3Button1.Caption := 'Stop';
    end
  else
    begin
      asm
        (@mediaRecorder).stop();
        (@mediaRecorder).requestData();
      end;
      Oscillator1.stop(0);
      Oscillator2.stop(0);
    end;
end;

procedure TForm1.InitializeForm;
begin
  inherited;
  FAudioContext := new JAudioContext;
  Oscillator1 := FAudioContext.createOscillator;
  Oscillator2 := FAudioContext.createOscillator;
end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
end;

initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.    

JavaScript Version

This example is a modification of the JavaScript in the Mozilla page and must be run in Firefox.

<!DOCTYPE html>
<html>
  <head>
    <title>Recording Oscillators</title>
  </head>
  <body>
    <h1>Recording Oscillators</h1>
    <p>Encoding the output of two oscillators to an Opus file</p>
    <button>Record</button>
    <script>
      var b = document.querySelector("button");
      var clicked = false;
      var chunks = [];
      var ac = new AudioContext();
      var oscillator1 = ac.createOscillator();
      var oscillator2 = ac.createOscillator();
      oscillator1.frequency.value = 523.25;
      oscillator2.frequency.value = 510.25;
      var dest = ac.createMediaStreamDestination();
      var mediaRecorder = new MediaRecorder(dest.stream);
      oscillator1.connect(dest);
      oscillator2.connect(dest);
    
      b.addEventListener("click", function(e) {
        if (!clicked) {
          mediaRecorder.start();
          oscillator1.start(0);
          oscillator2.start(0);
          e.target.innerHTML = "Stop recording";
          clicked = true;
        } 
        else {
          mediaRecorder.stop();
          mediaRecorder.requestData();
          oscillator1.stop(0);
          oscillator2.stop(0);
          e.target.disabled = true;
        }
      });

      mediaRecorder.ondataavailable = function(evt) {
        // push each chunk (blobs) in an array
        chunks.push(evt.data);
      };

      mediaRecorder.onstop = function(evt) {
        // Make Blob out of blobs and open it.
        var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
        window.location.href = URL.createObjectURL(blob);
      };
    </script>
  </body>
</html>

Programming - a skill for life!

Using Audio in Smart Pascal