N7SD logo

HOME      INDEX

OpenAL-Ada CLI Recording WAV File


Implementation with Alire and GnatStudio Environment

Ada application

Playback WAV file, support JUNK chunk.

File size should be less than 7.8MByte.

Date: July 19 2025Confirmed versions:
Ubuntu: 24.04.2 LTS, Gnat: 14.2.1, Gtkada:25.0.1
PC: OMEN 17-ck2095cl, i9-13900HX, RTX-4080

OpenAL 1.1.1 was installed

The source code and project files are available on GitHub

Application screen when recording and after

Recording screen


Setup Build Space “record_wav”

~/ada/oal$ alr init record_wav –bin

Alire needs some user information to initialize the crate author and maintainer,
for eventual submission to the Alire community index. This information will be 
interactively requested now.

You can edit this information at any time with 'alr config'

Enter a short description of the crate: (default: '')
>  Enter
Using default: ''
Please enter your email address: (default: 'example@example.com')
>  Enter
Using default: 'example@example.com'
Select a software license for the crate?
  1. MIT OR Apache-2.0 WITH LLVM-exception
  2. MIT
  3. Apache-2.0 WITH LLVM-exception
  4. Apache-2.0
  5. BSD-3-Clause
  6. LGPL-3.0-or-later
  7. GPL-3.0-or-later WITH GCC-exception-3.1
  8. GPL-3.0-or-later
  9. Other...
Enter your choice index (first is default): 
>  Enter
Enter a comma (',') separated list of tags to help people find your crate: (default: '')
>  Enter
Using default: ''
Enter an optional Website URL for the crate: (default: '')
>  Enter
Using default: ''
✓ record_wav initialized successfully.

~/ada/oal$ cd record_wav

~/ada/oal/record_wav$ ls -al

total 36
drwxrwxr-x 6 mm mm 4096 Jul 14 13:04 .
drwxrwxr-x 7 mm mm 4096 Jul 14 13:04 ..
drwxrwxr-x 2 mm mm 4096 Jul 14 13:04 alire
-rw-rw-r-- 1 mm mm  261 Jul 14 13:04 alire.toml
drwxrwxr-x 2 mm mm 4096 Jul 14 13:04 config
-rw-rw-r-- 1 mm mm   29 Jul 14 13:04 .gitignore
-rw-rw-r-- 1 mm mm  588 Jul 14 13:04 record_wav.gpr
drwxrwxr-x 3 mm mm 4096 Jul 14 13:04 share
drwxrwxr-x 2 mm mm 4096 Jul 14 13:04 src

Setup Source file list for 

~/ada/oal/record_wav$ gedit sourcefiles.txt

NOTE: The first file name is project-specific – modify it for your project.
All other files are common OpenAL library files (do not change).

record_wav.adb
openal.ads
openal-alc_thin.ads
openal-buffer.adb
openal-buffer.ads
openal-context.adb
openal-context.ads
openal-context-capture.adb
openal-context-capture.ads
openal-context-error.adb
openal-context-error.ads
openal-error.adb
openal-error.ads
openal-extension.ads
openal-extension-efx.adb
openal-extension-efx.ads
openal-extension-efx_thin.adb
openal-extension-efx_thin.ads
openal-extension-float32.adb
openal-extension-float32.ads
openal-extension-float32_thin.ads
openal-global.adb
openal-global.ads
openal_info.adb
openal_info.ads
openal_info_main.adb
openal-list.adb
openal-list.ads
openal-listener.adb
openal-listener.ads
openal-load.adb
openal-load.ads
openal-source.adb
openal-source.ads
openal-thin.ads
openal-types.ads

Startup GnatStudio with Alire

Setup Environment with GnatStudio

~/ada/oal/record_wav$ alr edit

GnatStudio initial screen


Setup Environment with GnatStudio

Edit – ”Project Properties”
Sources – Directories – press “+”

Browse: Other Locations – Computer – usr – local – include – coreland – openal-ada “OK”

Directories setting


Sources—Files tab:
Choose “Source list file”, “Browse”, choose “sourcefiles.text”  in the “play_wav” directory and “OK”.

Source files setting


Build—Switches—”Ada Linker” tab:
Enter this line into the bottom rectangular box.
-lopenal -lalut

OpenAL Build Switch code


Save” to close the Project-Properties window.
Press “Reload

Project file was created automatically.
file record_wav.gpr

with "config/record_wav_config.gpr";

project Record_Wav is

   for Source_Dirs use ("src", "config", "../../../../../usr/local/include/coreland/openal-ada");
   for Object_Dir use "obj/" & Record_Wav_Config.Build_Profile;
   for Create_Missing_Dirs use "True";
   for Exec_Dir use "bin";
   for Main use ("record_wav.adb");
   for Source_List_File use "sourcefiles.txt";

   package Compiler is
      for Default_Switches ("ada") use ("-Og", "-ffunction-sections", "-fdata-sections", "-g", "-gnatW8", "-gnatVa", "-gnatwa.X", "-gnatyaABbc-defhiIklM79nOprStux");
   end Compiler;

   package Binder is
      for Switches ("Ada") use ("-Es"); --  Symbolic traceback
   end Binder;

   package Install is
      for Artifacts (".") use ("share");
   end Install;

   package Linker is
      for Switches ("ada") use ("-lopenal", "-lalut");
   end Linker;

end Record_Wav;

Browse and double click the file “record_wav.adb”

GnatStudio project directories

Delete the default code and copy the new code:


Build Al_Info project

Build All button


Execution

Run button


Created WAV file location

The directory where the WAV file is created depends on the execution environment.

Running from GnatStudio: record_wav/”rec-1ch-48000-16b.wav” (project root)

Running from Terminal: record_wav/bin/r”rec-1ch-48000-16b.wav”.wav


Running Screen Messages

Terminal screen


Recording WAV file details using ffprobe command

Input #0, wav, from 'rec-1ch-48000-16b.wav':
  Duration: 00:00:05.85, bitrate: 768 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 48000 Hz, 1 channels, s16, 768 kb/s

Source file: record_wav.adb

with Ada.Text_IO;              use Ada.Text_IO;
with Ada.Real_Time;            use Ada.Real_Time;
with Ada.Characters.Latin_1;   use Ada.Characters.Latin_1;
with Ada.Streams.Stream_IO;
with Interfaces;               use Interfaces;  --  for unsigned_16,32
with Ada.Characters;
with Ada.Exceptions;           use Ada.Exceptions;
with Ada.Strings.Unbounded;    use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;  use Ada.Strings.Unbounded.Text_IO;
with OpenAL;                   use OpenAL;
with OpenAL.Context;           use OpenAL.Context;
with OpenAL.Context.Capture;
with OpenAL.Types;             use OpenAL.Types;
with OpenAL.Buffer;            use OpenAL.Buffer;
with OpenAL_Info;           --  NOTE:Underline (not Dash)

procedure Record_WAV is

   --  WAV header structure
   type WAV_Header is record
      ChunkID       : String (1 .. 4);  --  RIFF
      ChunkSize     : Unsigned_32;
      Format        : String (1 .. 4);  --  WAVE
      Subchunk1ID   : String (1 .. 4);  --  fmt(space)
      Subchunk1Size : Unsigned_32;
      AudioFormat   : Unsigned_16;
      NumChannels   : Unsigned_16;
      SampleRate    : Unsigned_32;
      ByteRate      : Unsigned_32;
      BlockAlign    : Unsigned_16;
      BitsPerSample : Unsigned_16;
      Subchunk2ID   : String (1 .. 4);  --  data
      Subchunk2Size : Unsigned_32;
   end record;
--
--
--  Constants for recording
--  *****************************************************
   SAMPLE_RATE : constant := 48000;
   CHANNELS : constant := 1;
   BITS_PER_SAMPLE : constant := 16;   --  16 bit
--  *****************************************************
   BUFFER_SIZE : constant := 4096;     --  4K sample (8kByte)
   MAX_SAMPLES : constant := 4096;     --  Capture Samples (4kByte)
   MAX_DURATION : constant Time_Span := To_Time_Span (60.0); --  Limited Time
   LOOP_PERIOD : constant Time_Span := To_Time_Span (0.01);  --  10ms Cycle

   --  Variables
   Playback_Device_t : OpenAL.Context.Device_t;
   Capture_Device_t : OpenAL.Context.Device_t;
   PLB_Context_t : OpenAL.Context.Context_t;

   Available_Samples : Natural;
   Total_Samples : Unsigned_32 := 0;

   Header : WAV_Header;
   File_Handle : Ada.Streams.Stream_IO.File_Type;
   Stream : Ada.Streams.Stream_IO.Stream_Access;

   Start_Time : Ada.Real_Time.Time;
   Next_Loop_Time : Ada.Real_Time.Time;
   Last_Progress_Second : Integer := -1;  --  Track last progress display
--
--
--
------------------------------------------------
--  Function to check if Enter key was pressed (non-blocking)
------------------------------------------------
   function Key_Pressed return Boolean is
      Available : Boolean;
      C : Character;
   begin
      --  Check if input is available without blocking
      Get_Immediate (C, Available);
      if Available and then (C = CR or else C = LF) then
         return True;
      end if;
      return False;
   exception  --  Insurance against any errors
      when others =>
         return False;
   end Key_Pressed;
--
--
-----------------------------------------------------
--  All Capture Devices List
-----------------------------------------------------
   procedure List_Capture_Device_List is
   begin
--
      OpenAL_Info.List_Playback_Devices;
      Put_Line ("");
      OpenAL_Info.List_Capture_Devices;
      Put_Line ("");
      OpenAL_Info.Defaults;  --  WORKS
      Put_Line ("");

--  Contents of "Run"
--    Init;
--    List_Playback_Devices;
--    List_Capture_Devices;
--    Defaults;
--    Open_Device;
--    Versions;
--    Finish;
--
   end List_Capture_Device_List;
--
--
-----------------------------------------------------
--  All Capture Devices Test
-----------------------------------------------------
   procedure Test_All_Formats is
      type Unbounded_String_Array is
        array (Positive range <>) of Unbounded_String;
      type Format_Test is record
         Format : OpenAL.Context.Format_t;
         Name : String (1 .. 10);
      end record;
      Format_List : constant array (1 .. 2) of Format_Test := (
--         (Mono_8, "Mono_8    "),
         (Mono_16, "Mono_16   "),
--         (Stereo_8, "Stereo_8  "),
         (Stereo_16, "Stereo_16 ")
      );

      Sample_Rates :
         constant array (1 .. 4) of Frequency_t := (8000, 22050, 44100, 48000);

      Device_Name_List : constant Unbounded_String_Array (1 .. 7) :=
        (To_Unbounded_String (""),
         To_Unbounded_String ("default"),
         To_Unbounded_String ("sof-hda-dsp Digital Microphone"),
         To_Unbounded_String ("PCM2906C Audio CODEC Analog Stereo"),
         To_Unbounded_String ("sof-hda-dsp Headphones Stereo Microphone"),
         To_Unbounded_String ("Monitor of sof-hda-dsp Speaker + Headphones"),
         To_Unbounded_String ("Monitor of HDA NVidia Digital Stereo (HDMI)")
        );
--
   begin
      Put_Line ("                        8000Hz   22050Hz  44100Hz  48000Hz");
      for Device_Name of Device_Name_List loop
         Put_Line ("Testing Capture device: '" & Device_Name & "'");
         for Format_Info of Format_List loop
            Put ("  Testing " & Format_Info.Name & "    ");
            for Rate of Sample_Rates loop

               begin
                  Capture_Device_t := OpenAL.Context.Capture.Open_Device
                    (Name => To_String (Device_Name),   --  String
                     Frequency => Rate,                 --  Types.Frequency_t
                     Format => Format_Info.Format,      --  Request_Format_t
                     Buffer_Size => BUFFER_SIZE);       --  Buffer_Size_t

                  if Capture_Device_t /= OpenAL.Context.Invalid_Device then
                     Put ("SUCCESS  ");
               --  Simple capture test
                     OpenAL.Context.Capture.Start (Capture_Device_t);
                     delay 0.01;  --  wait 10ms
                     Available_Samples :=
                        OpenAL.Context.Get_Capture_Samples (Capture_Device_t);
--                     Put_Line ("    Available samples: " &
--                               Natural'Image (Available_Samples));
                  else
                     Put ("FAILED   ");
                  end if;
                  OpenAL.Context.Capture.Stop (Capture_Device_t);
                  OpenAL.Context.Capture.Close_Device (Capture_Device_t);
                  delay 0.01;  --  wait 10ms for dummy

               exception
                  when Constraint_Error =>
                     Put_Line ("Constraint_Error-likely index range problem");
                     Put_Line ("   Available samples: " &
                                 Natural'Image (Available_Samples));
                     OpenAL.Context.Capture.Stop (Capture_Device_t);
                     OpenAL.Context.Capture.Close_Device (Capture_Device_t);
                     raise;
                  when E : others =>
                     Put_Line ("Other exception:");
                     Put_Line (Exception_Name (E) & ": " &
                               Exception_Message (E));
                     OpenAL.Context.Capture.Stop (Capture_Device_t);
                     OpenAL.Context.Capture.Close_Device (Capture_Device_t);
                     raise;
               end;
            end loop;  --  Rate
            Put_Line ("");
         end loop;   --  Format_Info
      end loop;   --  for Device_Name of
      Put_Line ("");
   end Test_All_Formats;
--
--
--
-------------------------------------------------------
-------------------------------------------------------
--  MAIN
-------------------------------------------------------
-------------------------------------------------------
begin
--
   List_Capture_Device_List;
--
   Test_All_Formats;
--
--
   Put_Line ("Starting WAV recording...");
   Put_Line ("Press Enter to stop recording (max 60 seconds)");

   --  First open playback device (required by some OpenAL implementations)
   begin
      --  alcOpenDevice
      Playback_Device_t := OpenAL.Context.Open_Device ("");
      Put_Line ("Playback device opened just for dummy");
      --  alcCreateContext
      PLB_Context_t := Create_Context (Playback_Device_t);
      Put_Line ("Playback Context opened just for dummy");
   exception
      when others =>
         Put_Line ("Dummy Open Playback Device ERROR");
         OpenAL.Context.Destroy_Context (PLB_Context_t);
         OpenAL.Context.Close_Device (Playback_Device_t);
   end;
--
-------------------------
--  Open Capture Device
-------------------------
--  alcCaptureOpenDevice
   Capture_Device_t := OpenAL.Context.Capture.Open_Device
     (Name => "",   -- "sof-hda-dsp Digital Microphone"
      Frequency => Types.Frequency_t (SAMPLE_RATE),
      Format => Mono_16,
      Buffer_Size => BUFFER_SIZE);
--
   if OpenAL.Context.Is_Valid_Device (Capture_Device_t) then
      Put_Line ("Open Valid_Device GOOD");
   else
      Put_Line ("Open ERROR Capture_Device_t BAD");
   end if;
--
------------------------
--  Create WAV output file
------------------------
   Ada.Streams.Stream_IO.Create (File_Handle,
                                Ada.Streams.Stream_IO.Out_File,
                                "rec-1ch-48000-16b.wav");
   Stream := Ada.Streams.Stream_IO.Stream (File_Handle);
--
--  Initialize WAV header
   Header.ChunkID := "RIFF";  --  (+00)
   Header.ChunkSize := 0;     --  (+04) FileSize - 8 Will be updated later
   Header.Format := "WAVE";
   Header.Subchunk1ID := "fmt ";
   Header.Subchunk1Size := 16; --  (+10H) Linear PCM = 16
   Header.AudioFormat := 1;    --  (+14H) Linear PCM=1
   Header.NumChannels := CHANNELS;    --  (+16H) Mono=1, Stereo:2
   Header.SampleRate := SAMPLE_RATE;  --  (+18H)
   Header.ByteRate := SAMPLE_RATE * CHANNELS * (BITS_PER_SAMPLE / 8); --  (+1C)
   Header.BlockAlign := CHANNELS * (BITS_PER_SAMPLE / 8);  --  (+20H)
   Header.BitsPerSample := BITS_PER_SAMPLE;                --  (+22H)
   Header.Subchunk2ID := "data";          --  (+24)
   Header.Subchunk2Size := 0; --  (+28H) Number of data. Will be updated later

--  Write header
   WAV_Header'Write (Stream, Header);

---------------------------
--  Start recording
---------------------------
   OpenAL.Context.Capture.Start (Capture_Device_t);

   Next_Loop_Time := Clock + LOOP_PERIOD;
   delay until Next_Loop_Time;
   Start_Time := Clock;
   Put_Line ("Recording started...");

   --  LOOP Main recording ---------------------------------------
   Recording_Loop : loop
      Next_Loop_Time := Next_Loop_Time + LOOP_PERIOD;
      delay until Next_Loop_Time;

      --  Check for Enter key press
      if Key_Pressed then
         Put_Line ("Recording stopped by user");
         exit Recording_Loop;
      end if;

      --  Check for timeout (60 seconds)
      if Clock - Start_Time >= MAX_DURATION then
         Put_Line ("Recording stopped - reached maximum duration " &
                   "(60 seconds)");
         exit Recording_Loop;
      end if;
--
----------------------------
--  Check Sample
----------------------------
      --  alcGetIntegerv
      --   (Capture_Device, ALC_CAPTURE_SAMPLES, 1, Available_Samples'Address);
      Available_Samples :=
        OpenAL.Context.Get_Capture_Samples (Capture_Device_t);

      if Available_Samples > 0 then
         --  Valid Samples are available
         declare
            Samples_To_Read : constant Natural :=
               Natural'Min (Available_Samples, MAX_SAMPLES);  --  Lesser value
            Buffer_t : OpenAL.Buffer.Sample_Array_16_t
              (1 .. Sample_Size_t (Samples_To_Read));
            Capture_Flag : Boolean := True;  -- Initialize Before Capture
         begin
----------------------------
--  Get Sample and copy to Buffer
----------------------------
         --  alcCaptureSamples
         --    (Capture_Device, Buffer'Address, Samples_To_Read);
            OpenAL.Context.Capture.Samples_Mono_16
              (Device => Capture_Device_t,
               Samples => Buffer_t);  --  OpenAL.Buffer.Sample_Array_16_t

            Capture_Flag := False;  --  Set After Capture
--
----------------------------
--  Write Data to File
----------------------------
            Total_Samples :=
              Total_Samples + Buffer_t'Length;

            Sample_Array_16_t'Write (Stream, Buffer_t);

         exception
            when Constraint_Error =>
               Put_Line ("Constraint_Error - likely index range problem");
               if Capture_Flag then
                  Put_Line ("Capture Process Error");
               else
                  Put_Line ("Writing File Process Error");
               end if;
               return;
            when E : others =>
               Ada.Text_IO.Put_Line ("ERROR in Samples_Mono_16: " &
                                       Ada.Exceptions.Exception_Message (E));
               Ada.Text_IO.Put_Line ("Exception Name: " &
                                       Ada.Exceptions.Exception_Name (E));
               Put_Line ("Samples_To_Read=" & Natural'Image (Samples_To_Read));
               OpenAL.Context.Capture.Stop (Capture_Device_t);
               OpenAL.Context.Capture.Close_Device (Capture_Device_t);
               if Capture_Flag then
                  Put_Line ("Capture Process Error");
               else
                  Put_Line ("Writing File Process Error");
               end if;
               return;
         end;

      end if;  --  end of Available_Samples

      --  Show progress every 5 seconds
      declare
         Current_Second : constant Integer :=
           Integer (To_Duration (Clock - Start_Time));
      begin
         if Current_Second > 0 and then
            Current_Second mod 5 = 0 and then
            Current_Second /= Last_Progress_Second and then
            Total_Samples > 0
         then
            Put_Line ("Recording..." & Integer'Image (Current_Second) &
                      " sec, Total:" & Unsigned_32'Image (Total_Samples) &
                      " Samples " & Unsigned_32'Image (Total_Samples * 2) &
                      " byte");
            Last_Progress_Second := Current_Second;
         end if;
      end;

      --  Small delay to prevent busy waiting
   end loop Recording_Loop;  --  --------------------------------
--
--
---------------------------
--  Update the WAV file info and CLOSE
--------------------------
   --  Update WAV headers with the corrected sizes
   Header.Subchunk2Size := Total_Samples * (BITS_PER_SAMPLE / 8); -- Pos41
   Header.ChunkSize := 36 + Header.Subchunk2Size;                 --  Pos5
--   Rewrite header with correct sizes
   Ada.Streams.Stream_IO.Set_Index (File_Handle, 1);
   WAV_Header'Write (Stream, Header);
   --  Close file
   Ada.Streams.Stream_IO.Close (File_Handle);
--
   Put_Line ("Total Samples=" &
             Unsigned_32'Image (Total_Samples) &
             " (" & Unsigned_32'Image (Total_Samples * 2) &
             " Byte  +44:FileSize=" &
             Unsigned_32'Image (Total_Samples * 2 + 44) &
             " Byte");
   Put_Line ("File Closed");
--
------------------------------
--  Clean up devices
------------------------------
   OpenAL.Context.Capture.Close_Device (Capture_Device_t);
   Destroy_Context (PLB_Context_t);
   OpenAL.Context.Capture.Close_Device (Playback_Device_t);
--
end Record_WAV;