OpenAL-Ada CLI Playing 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 25 2025
Confirmed 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 playing back an audio file

Setup Build Space “play_wav”
~/ada/oal $ alr init play_wav –bin (note: dash, dash)
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: ''
✓ play_wav initialized successfully.
~/ada/oal$ cd play_wav
~/ada/oal/play_wav$ ls -al
total 36
drwxrwxr-x 6 mm mm 4096 Jul 14 10:09 .
drwxrwxr-x 3 mm mm 4096 Jul 14 10:09 ..
drwxrwxr-x 2 mm mm 4096 Jul 14 10:09 alire
-rw-rw-r-- 1 mm mm 257 Jul 14 10:09 alire.toml
drwxrwxr-x 2 mm mm 4096 Jul 14 10:09 config
-rw-rw-r-- 1 mm mm 29 Jul 14 10:09 .gitignore
-rw-rw-r-- 1 mm mm 576 Jul 14 10:09 play_wav.gpr
drwxrwxr-x 3 mm mm 4096 Jul 14 10:09 share
drwxrwxr-x 2 mm mm 4096 Jul 14 10:09 src
~/ada/oal/play_wav$ gedit sourcefiles.txt
Edit Source File List
NOTE: The first file name is project-specific – modify it for your project.
All other files are common OpenAL library files (do not change).
play_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/play_wav$ alr edit

Edit – ”Project Properties”
Sources – Directories – press “+”
Before modification
Edit – ”Project Properties”
Sources – Directories – press “+”
Browse: Other Locations – Computer – usr – local – include – coreland – openal-ada “OK”
After:
Browse: Other Locations – Computer – usr – local – include – coreland – openal-ada “OK”
After:
Sources—Files tab:
—Choose “Source list file”, “Browse”, choose “sourcefiles.text” in the “play_wav” directory and “OK”.
Build—Switches—”Ada Linker” tab: Enter this line into the bottom rectangular box.
-lopenal -lalut
“Save” to close the Project-Properties window.
Press “Reload”.
Updated file play_wav.gpr
with "config/play_wav_config.gpr";
project Play_Wav is
for Source_Dirs use ("src", "config", "../../../../../usr/local/include/coreland/openal-ada");
for Object_Dir use "obj/" & Play_Wav_Config.Build_Profile;
for Create_Missing_Dirs use "True";
for Exec_Dir use "bin";
for Main use ("play_wav.adb");
package Compiler is
for Default_Switches ("ada") use Play_Wav_Config.Ada_Compiler_Switches;
end Compiler;
package Binder is
for Switches ("Ada") use ("-Es"); -- Symbolic traceback
end Binder;
package Install is
for Artifacts (".") use ("share");
end Install;
for Source_List_File use "sourcefiles.txt";
package Linker is
for Switches ("ada") use ("-lopenal", "-lalut");
end Linker;
end Play_Wav;
Browse and double click the file “play_wav.adb”
Overwrite the new code:
Build
On the first build, numerous (style) warnings will appear for the OpenAL files.
You can safely ignore all of these warnings.
They won’t show up in future builds.
As shown in the screenshot above, when “Bind” and “Link” appear at the end, it indicates that the executable has been successfully created in the bin directory.
Copy a WAV file into the directory.
In “bin” directory for the Terminal Run mode.
In the application root “play_wav” directory for the GnatStudio Run mode.
Execution
Terminal Execution Messages:
WAV file Header Information is displayed
~/ada/oal/play_wav/bin$ ./play_wav
OpenAL WAV Player starting…
Skipping chunk: LIST (size: 26)
ChunkID= RIFF
ChunkSize= 2939974
Format= WAVE
Subchunk1ID= fmt
Subchunk1Size= 16
Audio Format= 1
NumChannels= 1
SampleRate= 44100
ByteRate= 88200
BlockAlign= 2
BitsPerSample= 16
Subchunk2ID= data
Subchunk2Size= 2939904
Sample Count= 1469952
WAV file loaded successfully
Using format: AL_FORMAT_MONO16
Size => WAV_File.Data.all’Length, 1469952
Starting audio playback…
Playback completed
Source File
---------------------------------------------------------
-- Playing WAV file with OpenAL and Ada
-- Ada application
-- Playback WAV file (with JUNK chunk support)
-- File size should be less than 7.8MByte
---------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces; use Interfaces;
with OpenAL.Context; use OpenAL.Context;
with OpenAL.Buffer; use OpenAL.Buffer;
with OpenAL.Source; use OpenAL.Source;
with OpenAL.Thin; -- use OpenAL.Thin;
with OpenAL.Types; use OpenAL.Types;
with Ada.Unchecked_Deallocation;
with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO;
with Ada.Streams; use Ada.Streams;
procedure Play_Wav is
FILE_NAME : constant String := "run-1ch-44.1k-16bit-33s.wav";
-- NOTE: the WAV file size limitation is between 7.8MB and 9.4MB
-- It will cause Memory error when the file size exceeds it.
-- WAV file structure
type WAV_Header is record
ChunkID : String (1 .. 4);
ChunkSize : Unsigned_32;
Format : String (1 .. 4);
Subchunk1ID : String (1 .. 4);
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);
Subchunk2Size : Unsigned_32;
end record;
-- 8bit for reading Header
-- type Byte is new Unsigned_8;
-- type Byte_Array is array (Positive range <>) of Byte;
-- type Byte_Array_Access is access all Byte_Array;
-- 16bit for Audio data
type Sample_16_t_Array_Access is access all Sample_Array_16_t;
-- 16bit WAV store type
type WAV_Data_Record is record
Header : WAV_Header;
Data : Sample_16_t_Array_Access;
Sample_Count : Natural; -- Number of 16-bit samples
end record;
procedure Free is new Ada.Unchecked_Deallocation
(Sample_Array_16_t, Sample_16_t_Array_Access);
-- Helper function to read 4 bytes as string
function Read_Chunk_ID
(BinF : in out Ada.Streams.Stream_IO.File_Type) return String is
Buffer : Ada.Streams.Stream_Element_Array (1 .. 4);
Last : Ada.Streams.Stream_Element_Offset;
begin
Ada.Streams.Stream_IO.Read (BinF, Buffer, Last);
return Character'Val (Buffer (1)) &
Character'Val (Buffer (2)) &
Character'Val (Buffer (3)) &
Character'Val (Buffer (4));
end Read_Chunk_ID;
-- Helper function to read 4 bytes as Unsigned_32 (little endian)
function Read_Uint32
(BinF : in out Ada.Streams.Stream_IO.File_Type) return Unsigned_32 is
Buffer : Ada.Streams.Stream_Elemen---------------------------------------------------------
-- Playing WAV file with OpenAL and Ada
-- Ada application
-- Playback WAV file (with JUNK chunk support)
-- File size should be less than 7.8MByte
---------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces; use Interfaces;
with OpenAL.Context; use OpenAL.Context;
with OpenAL.Buffer; use t_Array (1 .. 4);
Last : Ada.Streams.Stream_Element_Offset;
begin
Ada.Streams.Stream_IO.Read (BinF, Buffer, Last);
return Unsigned_32 (Buffer (1)) +
Unsigned_32 (Buffer (2)) * 2**8 +
Unsigned_32 (Buffer (3)) * 2**16 +
Unsigned_32 (Buffer (4)) * 2**24;
end Read_Uint32;
-- Helper function to read 2 bytes as Unsigned_16 (little endian)
function Read_Uint16
(BinF : in out Ada.Streams.Stream_IO.File_Type) return Unsigned_16 is
Buffer : Ada.Streams.Stream_Element_Array (1 .. 2);
Last : Ada.Streams.Stream_Element_Offset;
begin
Ada.Streams.Stream_IO.Read (BinF, Buffer, Last);
return Unsigned_16 (Buffer (1)) +
Unsigned_16 (Buffer (2)) * 2**8;
end Read_Uint16;
-- Helper function to skip bytes
procedure Skip_Bytes
(BinF : in out Ada.Streams.Stream_IO.File_Type; Count : Positive) is
Current_Index : Ada.Streams.Stream_IO.Positive_Count;
begin
Current_Index := Ada.Streams.Stream_IO.Index (BinF);
Ada.Streams.Stream_IO.Set_Index
(BinF, Current_Index + Ada.Streams.Stream_IO.Positive_Count (Count));
end Skip_Bytes;
--
--
--
function Load_WAV_File (File_Name : String) return WAV_Data_Record is
BinF : Ada.Streams.Stream_IO.File_Type;
Header : WAV_Header;
Data_Size : Natural := 0; -- Data size in bytes
Sample_Count : Sample_Size_t; -- Number of 16-bit samples
Data : Sample_16_t_Array_Access;
Result : WAV_Data_Record;
Chunk_ID : String (1 .. 4);
Chunk_Size : Unsigned_32;
Fmt_Found : Boolean := False;
Data_Found : Boolean := False;
begin
-- Open file as binary stream
Ada.Streams.Stream_IO.Open
(BinF, Ada.Streams.Stream_IO.In_File, File_Name);
-- Read RIFF header
Header.ChunkID := Read_Chunk_ID (BinF); -- Read 4 char "RIFF"
Header.ChunkSize := Read_Uint32 (BinF); -- Read 4 Bytes Unsigned_32
Header.Format := Read_Chunk_ID (BinF); -- "WAVE"
-- Verify this is a WAV file
if Header.ChunkID /= "RIFF" or else Header.Format /= "WAVE" then
Put_Line ("Error: Not a valid WAV file");
Ada.Streams.Stream_IO.Close (BinF);
Result.Header := Header;
Result.Data := null;
Result.Sample_Count := 0;
return Result;
end if;
-- Parse chunks until we find fmt and data
while
not (Fmt_Found and Data_Found) and then
not Ada.Streams.Stream_IO.End_Of_File (BinF) loop
--
Chunk_ID := Read_Chunk_ID (BinF);
Chunk_Size := Read_Uint32 (BinF);
if Chunk_ID = "fmt " then
-- Read format chunk
Header.Subchunk1ID := Chunk_ID;
Header.Subchunk1Size := Chunk_Size;
Header.AudioFormat := Read_Uint16 (BinF);
Header.NumChannels := Read_Uint16 (BinF);
Header.SampleRate := Read_Uint32 (BinF);
Header.ByteRate := Read_Uint32 (BinF);
Header.BlockAlign := Read_Uint16 (BinF);
Header.BitsPerSample := Read_Uint16 (BinF);
-- Verify this is 16-bit audio
if Header.BitsPerSample /= 16 then
Put_Line ("Error: Only 16-bit audio is supported, found " &
Unsigned_16'Image (Header.BitsPerSample) & "-bit");
Ada.Streams.Stream_IO.Close (BinF);
Result.Header := Header;
Result.Data := null;
Result.Sample_Count := 0;
return Result;
end if;
-- Skip any remaining bytes in the fmt chunk
if Chunk_Size > 16 then
Skip_Bytes (BinF, Natural (Chunk_Size - 16));
end if;
Fmt_Found := True;
elsif Chunk_ID = "data" then
-- Found data chunk
Header.Subchunk2ID := Chunk_ID;
Header.Subchunk2Size := Chunk_Size;
Data_Size := Natural (Chunk_Size);
-- Calculate number of 16-bit samples
Sample_Count := Sample_Size_t (Data_Size / 2);
-- allocated memory dynamically
Data := new Sample_Array_16_t (1 .. Sample_Size_t (Data_Size / 2));
-- Read audio data
-- Treat Data array directly as Stream_Element_Array (overlay technique)
-- This overlays the same memory area with different types.
-- Safe because Byte and Stream_Element are the same size (8 bits).
declare
Data_Stream_View : Stream_Element_Array
(1 .. Stream_Element_Offset (Data_Size));
for Data_Stream_View'Address use Data.all'Address;
pragma Import (Ada, Data_Stream_View);
Last : Stream_Element_Offset;
begin
-- Note: WAV files use little-endian format
-- If your system is big-endian, you would need to swap bytes here
-- For most modern systems (x86, x64), no conversion is needed
Stream_IO.Read (BinF, Data_Stream_View, Last);
end;
Data_Found := True;
else
-- Skip unknown chunks (like JUNK, LIST, etc.)
Put_Line ("Skipping chunk: " & Chunk_ID & " (size:" &
Unsigned_32'Image (Chunk_Size) & ")");
Skip_Bytes (BinF, Natural (Chunk_Size));
-- Handle odd-sized chunks (WAV chunks must be word-aligned)
if Chunk_Size mod 2 = 1 then
Skip_Bytes (BinF, 1);
end if;
end if;
end loop;
-- Close file
Ada.Streams.Stream_IO.Close (BinF);
-- Check if we found both required chunks
if not Fmt_Found then
Put_Line ("Error: fmt chunk not found");
Free (Data);
Data := null;
end if;
if not Data_Found then
Put_Line ("Error: data chunk not found");
if Data /= null then
Free (Data);
end if;
-- **** Sample_Count := 0;
Data_Size := 0;
end if;
-- Print header information
Put_Line ("ChunkID= " & Header.ChunkID);
Put_Line ("ChunkSize= " & Unsigned_32'Image (Header.ChunkSize));
Put_Line ("Format= " & Header.Format);
Put_Line ("Subchunk1ID= " & Header.Subchunk1ID);
Put_Line ("Subchunk1Size= " & Unsigned_32'Image (Header.Subchunk1Size));
Put_Line ("Audio Format= " & Unsigned_16'Image (Header.AudioFormat));
Put_Line ("NumChannels= " & Unsigned_16'Image (Header.NumChannels));
Put_Line ("SampleRate= " & Unsigned_32'Image (Header.SampleRate));
Put_Line ("ByteRate= " & Unsigned_32'Image (Header.ByteRate));
Put_Line ("BlockAlign= " & Unsigned_16'Image (Header.BlockAlign));
Put_Line ("BitsPerSample= " & Unsigned_16'Image (Header.BitsPerSample));
Put_Line ("Subchunk2ID= " & Header.Subchunk2ID);
Put_Line ("Subchunk2Size= " & Unsigned_32'Image (Header.Subchunk2Size));
Put_Line ("Sample Count= " & Sample_Size_t'Image (Sample_Count));
-- Return result
Result.Header := Header;
Result.Data := Data;
Result.Sample_Count := Integer (Sample_Count);
return Result;
end Load_WAV_File;
--
--
------------------------------------------------------
-- GLOBAL VARIABLES
------------------------------------------------------
--
Device : OpenAL.Context.Device_t;
Context : Context_t;
Buffer : Buffer_t;
Source : Source_t;
WAV_File : WAV_Data_Record;
Set_Active_Context : Boolean;
Buffers : Buffer_Array_t (1 .. 1);
Sources : Source_Array_t (1 .. 1);
EndFlag : Integer := 0;
ProcessedNr : Natural := 0;
--
---------------------------------------------------------
---------------------------------------------------------
-- MAIN
---------------------------------------------------------
---------------------------------------------------------
begin
--
Put_Line ("OpenAL WAV Player starting...");
-- Open_Device works only with "OpenAL_Soft". Choose OS Setting individually
Device := OpenAL.Context.Open_Device ("OpenAL Soft");
if Device = Invalid_Device then
Put_Line ("Error: Failed to open OpenAL device");
return;
end if;
-- Context works for 3D, Listner and Environment settings
Context := Create_Context (Device);
if Context = Null_Context then
Put_Line ("Error: Failed to create OpenAL context");
Close_Device (Device);
return;
end if;
-- Choose the working Context
Set_Active_Context := Make_Context_Current (Context);
if Set_Active_Context = False then
Put_Line ("Error: Failed, cannot Set Active Context");
Destroy_Context (Context);
Close_Device (Device);
return;
end if;
Generate_Buffers (Buffers);
Generate_Sources (Sources);
Buffer := Buffers (1);
Source := Sources (1);
WAV_File := Load_WAV_File (FILE_NAME);
if WAV_File.Data = null then
Put_Line ("Error: Failed to load WAV file");
Destroy_Context (Context);
Close_Device (Device);
return;
end if;
Put_Line ("WAV file loaded successfully");
-- Decide OpenAL format following WAV file format
declare
OpenAL_Format : OpenAL.Types.Enumeration_t;
begin
case WAV_File.Header.NumChannels is
when 1 => -- MONO
case WAV_File.Header.BitsPerSample is
when 8 =>
OpenAL_Format := OpenAL.Thin.AL_FORMAT_MONO8;
Put_Line ("Using format: AL_FORMAT_MONO8");
when 16 =>
OpenAL_Format := OpenAL.Thin.AL_FORMAT_MONO16;
Put_Line ("Using format: AL_FORMAT_MONO16");
when others =>
Put_Line ("Error: Unsupported bits per sample for mono: " &
Unsigned_16'Image (WAV_File.Header.BitsPerSample));
Destroy_Context (Context);
Close_Device (Device);
if WAV_File.Data /= null then
Free (WAV_File.Data);
end if;
return;
end case;
when 2 => -- Stereo
case WAV_File.Header.BitsPerSample is
when 8 =>
OpenAL_Format := OpenAL.Thin.AL_FORMAT_STEREO8;
Put_Line ("Using format: AL_FORMAT_STEREO8");
when 16 =>
OpenAL_Format := OpenAL.Thin.AL_FORMAT_STEREO16;
Put_Line ("Using format: AL_FORMAT_STEREO16");
when others =>
Put_Line ("Error: Unsupported bits per sample for stereo: " &
Unsigned_16'Image (WAV_File.Header.BitsPerSample));
Destroy_Context (Context);
Close_Device (Device);
if WAV_File.Data /= null then
Free (WAV_File.Data);
end if;
return;
end case;
when others =>
Put_Line ("Error: Unsupported number of channels: " &
Unsigned_16'Image (WAV_File.Header.NumChannels));
Destroy_Context (Context);
Close_Device (Device);
if WAV_File.Data /= null then
Free (WAV_File.Data);
end if;
return;
end case;
-- Set buffer data with determined format
OpenAL.Thin.Buffer_Data (
Buffer_ID => 1,
Format => OpenAL_Format,
Data => WAV_File.Data (1)'Address,
Size => WAV_File.Data.all'Length,
Frequency => OpenAL.Types.Size_t (WAV_File.Header.SampleRate)
);
end;
-- Configure source
OpenAL.Source.Set_Current_Buffer (Source, Buffer);
OpenAL.Source.Set_Pitch (Source, 1.0);
OpenAL.Source.Set_Gain (Source, 1.0);
OpenAL.Source.Set_Position_Float (Source, 0.0, 0.0, 0.0);
OpenAL.Source.Set_Velocity_Float (Source, 0.0, 0.0, 0.0);
OpenAL.Source.Set_Looping (Source, False);
Put_Line ("Size => WAV_File.Data.all'Length," &
Integer'Image (WAV_File.Data.all'Length));
Put_Line ("Starting audio playback...");
-- Start playback
OpenAL.Source.Play (Source);
-- Wait until playback finishes
loop
OpenAL.Thin.Get_Sourcei
(Source_ID => OpenAL.Types.Unsigned_Integer_t (1),
Parameter => OpenAL.Thin.AL_SOURCE_STATE,
Value => EndFlag'Address);
exit when EndFlag /= OpenAL.Thin.AL_PLAYING;
delay 0.1;
end loop;
Put_Line ("Playback completed");
-- Clean up resources
if WAV_File.Data /= null then
Free (WAV_File.Data);
end if;
OpenAL.Source.Get_Buffers_Queued (Source => Source,
Buffers => ProcessedNr); -- Natural
if ProcessedNr /= 0 then
OpenAL.Source.Unqueue_Buffers (Source => Source,
Buffers => Buffers); -- Buffer_Array_t
end if;
OpenAL.Buffer.Delete_Buffers (Buffers);
OpenAL.Context.Close_Device (Device); -- "OpenAL Soft")
OpenAL.Context.Destroy_Context (Context);
OpenAL.Source.Delete_Sources (Sources);
--
--
end Play_Wav;