Sound Recorder to wav miniaudio

Bare metal programming in PureBasic, for experienced users
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Sound Recorder to wav miniaudio

Post by idle »

WIP of miniaudio device IO records plays back live and save to wav file
PB 6.03b2 windows x64 c backend

Prerequisites :
a gcc install eg mingw64 with Environment variables set so gcc can find the includes folder for typedef resolutions

source and dll windows x64
https://atomicwebserver.com/SoundRecorder.zip

chipmunk speed test
https://atomicwebserver.com/tempwav.wav

supported format
#ma_format_u8
#ma_format_s16
#ma_format_s24
#ma_format_s32
#ma_format_f32

modes

#ma_device_type_capture ;records from an input microphone or stereo mix
#ma_device_type_duplex ;records from input sources mic or stereo mix and plays back
#ma_device_type_loopback ;windows only captures what's currently playing

Code: Select all

;Test of miniaudio records to wav file and playbacks live 
;note it buffers to memory, then saves to wave at StopSoundRecorder  

;Records from default input to default output 

;*This requires that you have a full gcc toolchain eg mingw64 windows with env paths set to find #include <stdio.h> ... 
;add your gcc include paths to your PATH environment variable  
;eg 
;C:\mingw64\lib\gcc\x86_64-w64-mingw32\13.2.0\include\ 
;C:\mingw64\x86_64-w64-mingw32\include

;Download the header and adjust the #include path to suit 
;https://atomicwebserver.com/miniaudio_11_23.h

HeaderSection 
 
#include "E:\andrews\pbstuff\miniaudio\tminiaudio_11_23.h"; 

EndHeaderSection 

InitSound() 

Global gRecording 

#ma_format_u8      = 1
#ma_format_s16     = 2     
#ma_format_s24     = 3     
#ma_format_s32     = 4
#ma_format_f32     = 5

Structure RIFFStructure
  Riff.a[4]
  Length.l
  Wave.a[4]
EndStructure

Structure fmtStructure
  fmt.a[4]
  Length.l
  Format.u
  Channels.u
  SampleRate.l
  BytesPerSecond.l
  BlockAlign.u
  BitsPerSample.u
EndStructure

Structure dataStructure
  Signature.a[4]
  Length.l
EndStructure

Global *WAVBuffer 

Procedure.i ResetSoundRecorder(device) 
  
  Protected.i Result, HeaderSize, DataSize
  Protected *WAVBuffer, *RiffPtr.RIFFStructure, *fmtPtr.fmtStructure, *dataPtr.dataStructure, *audioPtr.word
  
  
  If *WAVBuffer 
    FreeMemory(*WAVBuffer) 
  EndIf   
  
  HeaderSize = SizeOf(RIFFStructure)
  HeaderSize + SizeOf(fmtStructure)
  HeaderSize + SizeOf(dataStructure)
  
  *WAVBuffer = AllocateMemory(HeaderSize)
  
  If *WAVBuffer 
    ProcedureReturn *WAVBuffer 
  EndIf 
EndProcedure    

Procedure AppendSoundRecorder(*data,len) 
  If *WAVBuffer 
    pos = MemorySize(*WAVBuffer) 
    *WAVBuffer = ReAllocateMemory(*WAVBuffer,pos+len) 
    CopyMemory(*data,*WAVBuffer+pos,len) 
    ProcedureReturn *WAVBuffer 
  EndIf 
EndProcedure 

ProcedureDLL StartSoundRecorder(*device) 
  
  gRecording = 1  
  !ma_device_start(p_device);
  
EndProcedure  

ProcedureDLL StopSoundRecorder(*device,file.s="",samplerate=44100) 
  
  Protected.i Result, HeaderSize, DataSize,channels,format,depth  
  Protected *RiffPtr.RIFFStructure, *fmtPtr.fmtStructure, *dataPtr.dataStructure, *audioPtr.word
  
  gRecording = 0 
  !ma_device_stop(p_device);
  
  !ma_device *tpdevice = p_device; 
      
  If *WAVBuffer 
    
    If file <> ""
      
      !v_format = tpdevice->capture.format;
      !v_channels = tpdevice->capture.channels; 
      
      HeaderSize = SizeOf(RIFFStructure)
      HeaderSize + SizeOf(fmtStructure)
      HeaderSize + SizeOf(dataStructure)
      
      DataSize = MemorySize(*WAVBuffer) - (HeaderSize)
      
      *RiffPtr = *WAVBuffer
      PokeS(@*RiffPtr\Riff, "RIFF", 4, #PB_Ascii|#PB_String_NoZero)
      *RiffPtr\Length = HeaderSize + DataSize - 8
      PokeS(@*RiffPtr\Wave, "WAVE", 4, #PB_Ascii|#PB_String_NoZero)
      
      *fmtPtr = *WAVBuffer + SizeOf(RIFFStructure)
      PokeS(@*fmtPtr\fmt, "fmt ", 4, #PB_Ascii|#PB_String_NoZero)
      *fmtPtr\Length = SizeOf(fmtStructure) - 8
      If format = #ma_format_f32
        *fmtPtr\Format = 3
         depth = 32
      Else
        *fmtPtr\Format = 1
        Select format 
            Case #ma_format_u8 
              depth = 8   
            Case #ma_format_s16     
              depth = 16    
            Case #ma_format_s24   
              depth = 24   
            Case #ma_format_s32
              depth = 32 
        EndSelect        
      EndIf   
      
      *fmtPtr\Channels = channels 
      *fmtPtr\SampleRate = samplerate   
      *fmtPtr\BitsPerSample = depth           
      *fmtPtr\BlockAlign =  (depth>>3) * 2   
      *fmtPtr\BytesPerSecond = samplerate * (depth>>3) 
      
      *dataPtr = *WAVBuffer + SizeOf(RIFFStructure) + SizeOf(fmtStructure)
      PokeS(@*dataPtr\Signature, "data", 4, #PB_Ascii|#PB_String_NoZero)
      *dataPtr\Length = DataSize
      
      fn = CreateFile(#PB_Any,file) 
      If fn 
        x = WriteData(fn,*WAVBuffer,MemorySize(*WAVBuffer)) 
        PrintN(Str(x) + " bytes written") 
        CloseFile(fn) 
      EndIf
      
    EndIf 
    
    FreeMemory(*WAVBuffer)
    
  EndIf  
  
  ProcedureReturn #True
  
EndProcedure

ProcedureC data_callback(*Device,*Output,*Input,frameCount)
  Protected amount
  
  If frameCount  
    !ma_device *pdevice = p_device; 
    !v_amount = v_framecount * ma_get_bytes_per_frame(pdevice->capture.format, pdevice->capture.channels);
    If amount <> 0  
      If (*input And *output)  
        CopyMemory(*Input,*Output,amount)
      EndIf 
      If (*input And gRecording)
        If *WAVBuffer = 0 
          *WAVBuffer = ResetSoundRecorder(*device)
        EndIf   
        If *WAVBuffer 
          *WAVBuffer = AppendSoundRecorder(*input,amount)
        EndIf 
      EndIf   
    EndIf
  EndIf 
EndProcedure 

Structure nativeDataFormats
   format.i;       /* Sample format. If set to 0, all sample formats are supported. */
   channels.l;     /* If set to 0, all channels are supported. */
   sampleRate.l;   /* If set to 0, all sample rates are supported. */
   flags.l;        /* A combination of MA_DATA_FORMAT_FLAG_* flags. */
EndStructure 
  
Structure devices
  *device
  id.i 
  isDefault.i
  name.s
  nativeDataFormatCount.l
  format.nativeDataFormats[64] 
EndStructure   

Global temp.i,*ptemp,pcb = @data_callback() ;need to reference the function or PB won't include it  
Global Dim CaptureDevices.devices(0) 
Global Dim PlayBackDevices.devices(0)

ProcedureDLL EnumSoundDevices() 
  Protected ind,temp,ta  
  !ma_context context;
  !ma_device_info *pCaptureInfos;// = (ma_device_info *) malloc(sizeof(ma_device_info));
  !ma_device_info *pPlaybackInfos;// = (ma_device_info *) malloc(sizeof(ma_device_info));
  
  !if (ma_context_init(NULL, 0, NULL, &context) == MA_SUCCESS) {
    !ma_uint32 captureCount; 
    !ma_uint32 playCount;
    !if (ma_context_get_devices(&context,&pPlaybackInfos,&playCount,&pCaptureInfos,&captureCount) == MA_SUCCESS) {
      !v_temp = captureCount-1;
      ReDim CaptureDevices.devices(temp) 
      !v_temp = playCount-1;
      ReDim PlayBackDevices.devices(temp)
  
     !for (ma_uint32 iDevice = 0; iDevice < captureCount; iDevice += 1) {
       !v_ind = iDevice;  
       !v_temp = &pCaptureInfos[iDevice];
       CaptureDevices(ind)\device = temp 
       !v_temp = &pCaptureInfos[iDevice].id; 
       CaptureDevices(ind)\id = temp 
       !v_temp = pCaptureInfos[iDevice].name;
       CaptureDevices(ind)\name = PeekS(temp,-1,#PB_Ascii)
       !v_temp = pCaptureInfos[iDevice].isDefault;
       CaptureDevices(ind)\isDefault = temp 
       !v_temp = pCaptureInfos[iDevice].nativeDataFormatCount; 
       CaptureDevices(ind)\nativeDataFormatCount=temp 
       
       !for (ma_uint32 a = 0; a < pCaptureInfos[iDevice].nativeDataFormatCount; a += 1) { 
          !v_ta = a;
          !v_temp = pCaptureInfos[iDevice].nativeDataFormats[a].format;  
          CaptureDevices(ind)\format[ta]\format = temp 
          !v_temp = pCaptureInfos[iDevice].nativeDataFormats[a].channels;  
          CaptureDevices(ind)\format[ta]\channels = temp 
          !v_temp = pCaptureInfos[iDevice].nativeDataFormats[a].sampleRate;  
          CaptureDevices(ind)\format[ta]\sampleRate = temp 
          !}
          
              
     !}
     !for (ma_uint32 iDevice = 0; iDevice < playCount; iDevice += 1) {
       !v_ind = iDevice;  
       !v_temp = &pPlaybackInfos[iDevice];
       PlayBackDevices(ind)\device = temp 
       !v_temp = &pPlaybackInfos[iDevice].id;
       PlayBackDevices(ind)\id = temp 
       !v_temp = pPlaybackInfos[iDevice].name; 
       PlayBackDevices(ind)\name = PeekS(temp,-1,#PB_Ascii)
        !v_temp = pPlaybackInfos[iDevice].isDefault;
       PlayBackDevices(ind)\isDefault = temp 
       
       !v_temp = pPlaybackInfos[iDevice].nativeDataFormatCount; 
       PlayBackDevices(ind)\nativeDataFormatCount=temp 
       
       !for (ma_uint32 a = 0; a < pPlaybackInfos[iDevice].nativeDataFormatCount; a += 1) { 
          !v_ta = a;
          !v_temp = pPlaybackInfos[iDevice].nativeDataFormats[a].format;  
          PlayBackDevices(ind)\format[ta]\format = temp 
          !v_temp = pPlaybackInfos[iDevice].nativeDataFormats[a].channels;  
          PlayBackDevices(ind)\format[ta]\channels = temp 
          !v_temp = pPlaybackInfos[iDevice].nativeDataFormats[a].sampleRate;  
          PlayBackDevices(ind)\format[ta]\sampleRate = temp 
          !}              
     !}
    !}
  !}
  
EndProcedure 

#ma_device_type_capture =2   ;records from an input microphone or stereo mix
#ma_device_type_duplex = 3   ;records from input sources mic or stereo mix and plays back     
#ma_device_type_loopback = 4 ;windows only captures what's currently playing  

;if input is 0 it will record from the default device 

ProcedureDLL InitSoundRecorder(input,format=#ma_format_s16,sampleRate=44100,mode=#ma_device_type_capture) 
Protected dev,result  
  
!ma_device_config deviceConfig;
!ma_device *device = (ma_device*) malloc(sizeof(ma_device));
Select mode 
   Case #ma_device_type_capture 
     !deviceConfig = ma_device_config_init(ma_device_type_capture);
     !deviceConfig.sampleRate = v_samplerate;
     !deviceConfig.capture.pDeviceID  = v_input;  //NULL;
     !deviceConfig.capture.format     = v_format;
     !deviceConfig.capture.channels   = 2;
     !deviceConfig.capture.shareMode  = ma_share_mode_shared;
     !deviceConfig.dataCallback       = &f_data_callback; 
   Case #ma_device_type_loopback 
     CompilerIf #PB_Compiler_OS = #PB_OS_Windows 
     !deviceConfig = ma_device_config_init(ma_device_type_loopback);
     !deviceConfig.sampleRate = v_samplerate;
     !deviceConfig.capture.pDeviceID  = NULL;
     !deviceConfig.capture.format     = v_format;
     !deviceConfig.capture.channels   = 2;
     !deviceConfig.capture.shareMode  = ma_share_mode_shared;
     !deviceConfig.dataCallback       = &f_data_callback; 
   CompilerElse 
     CompilerError "loopback only supported on windows"  
   CompilerEndIf 
   Case #ma_device_type_duplex 
     !deviceConfig = ma_device_config_init(ma_device_type_duplex);
     !deviceConfig.sampleRate = v_samplerate;
     !deviceConfig.capture.pDeviceID  = v_input;  //NULL;
     !deviceConfig.capture.format     = v_format;
     !deviceConfig.capture.channels   = 2;
     !deviceConfig.capture.shareMode  = ma_share_mode_shared;
     !deviceConfig.playback.pDeviceID = NULL;
     !deviceConfig.playback.format    = v_format; //ma_format_s16;
     !deviceConfig.playback.channels  = 2;
     !deviceConfig.dataCallback       = &f_data_callback; 
EndSelect       

!v_result = ma_device_init(NULL, &deviceConfig, &*device);

If result <> 0
  MessageRequester("error","failed to config")
  ProcedureReturn 0
EndIf  

!v_dev = device; 

ProcedureReturn dev 

EndProcedure 

ProcedureDLL FreeSoundRecorder(recorder) 
  !ma_device_uninit(v_recorder);  
  !free(v_recorder);
EndProcedure   

CompilerIf #PB_Compiler_IsMainFile ;-test 
  
OpenConsole()   
  
EnumSoundDevices()

For a = 0 To ArraySize(CaptureDevices()) 
  Debug CaptureDevices(a)\name 
  If CaptureDevices(a)\isDefault 
     Debug "Is default Capture device index = " + Str(a)  
  EndIf 
Next 

PrintN("Init Recorder") 

;Global recorder = InitSoundRecorder(CaptureDevices(1)\id,#ma_format_s16,44100)      ;record on selected input 
Global recorder = InitSoundRecorder(0,#ma_format_s16,44100,#ma_device_type_duplex)   ;record on default input and playback 
;Global recorder = InitSoundRecorder(0,#ma_format_s16,44100,#ma_device_type_loopback) ;record on whats being currently played on speaker 

PrintN("Press enter tostart recording") 

StartSoundRecorder(recorder)

PrintN("Recording press enter to stop") 
Input();

StopSoundRecorder(recorder,GetTemporaryDirectory()+"tempwav.wav",44100) ;save at chipmunk speed normal speed 44100 

snd = LoadSound(-1,GetTemporaryDirectory()+"tempwav.wav") 
If snd 
  PlaySound(snd) 
EndIf   

PrintN("Stopped press enter to end") 
Input();

FreeSoundRecorder(recorder)

PrintN("free") 

CloseConsole()

CompilerEndIf 


dige
Addict
Addict
Posts: 1413
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Sound Recorder to wav miniaudio

Post by dige »

Hey Idle, thanks for the code.

I have installed mingw32 and added the environment variables.
The “gcc --version” test was successful.

What else do I need to configure in PB so that I can compile the source code?
So far, I always get an ASM error. The same one I got before installing mingw32.

Code: Select all

purebasic.asm (411]: if
(ma_context_get_devices&context, &PlaybackInfos, &playCou
nt, &p Capturelnfos, &captureCount) == MA_SUCCESS) {
processed:
if (ma_context_get_
_devices&context, &p Playbackinfos, &playCo
unt, &p Captureinfos, &captureCount)==MA_SUCCESS error: missing end directive.

So it seems that something is still missing?
"Daddy, I'll run faster, then it is not so far..."
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sound Recorder to wav miniaudio

Post by idle »

dige wrote: Mon Sep 15, 2025 9:07 am Hey Idle, thanks for the code.

I have installed mingw32 and added the environment variables.
The “gcc --version” test was successful.

What else do I need to configure in PB so that I can compile the source code?
So far, I always get an ASM error. The same one I got before installing mingw32.

Code: Select all

purebasic.asm (411]: if
(ma_context_get_devices&context, &PlaybackInfos, &playCou
nt, &p Capturelnfos, &captureCount) == MA_SUCCESS) {
processed:
if (ma_context_get_
_devices&context, &p Playbackinfos, &playCo
unt, &p Captureinfos, &captureCount)==MA_SUCCESS error: missing end directive.

So it seems that something is still missing?
I've reset the mime type on the server, to plain txt, maybe it was corrupting the file.
https://atomicwebserver.com/miniaudio_11_23.h

and it needs to be compiled with pb6.30b2 c backend
User_Russian
Addict
Addict
Posts: 1556
Joined: Wed Nov 12, 2008 5:01 pm
Location: Russia

Re: Sound Recorder to wav miniaudio

Post by User_Russian »

dige wrote: Mon Sep 15, 2025 9:07 amSo it seems that something is still missing?
purebasic.asm this assembly backend.
To compile this code you need to use C backend.
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sound Recorder to wav miniaudio

Post by idle »

added additional info on the environment variables add to PATH like
C:\mingw64\x86_64-w64-mingw32\include\
C:\mingw64\lib\gcc\x86_64-w64-mingw32\13.2.0\include\
dige
Addict
Addict
Posts: 1413
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Sound Recorder to wav miniaudio

Post by dige »

✅ miniaudio_11_23.h reloaded
✅ Compiler set to PureBasic 6.30 Beta 2 C-Backend (Windows x64)
✅ mingw32 include pathes added

Now I have got this error:

Code: Select all

error: no include path in which to search for stddef.h
3773 | #include < stddef.h> /* For size_t. */
C:\Temp\miniaudio_11_23.h:4061:9: error: unknown type name wchar t
4061 | typedef wchar_t ma_wchar_wi
If I run "where stddef.h" in a dos shell, it tells me found in 2 loactions:

Code: Select all

\mingw32\i686-w64-mingw32\include\stddef.h
\mingw32\lib\gcc\i686-w64-mingw32\15.2.0\include\stddef.h
"Daddy, I'll run faster, then it is not so far..."
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sound Recorder to wav miniaudio

Post by idle »

There's also CPATH environment var I thought it was using path only and you also need to restart the IDE after editing the environment variables.
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sound Recorder to wav miniaudio

Post by idle »

Added a windows x64 dll build
https://atomicwebserver.com/SoundRecorder.zip
User avatar
CDXbow
Enthusiast
Enthusiast
Posts: 103
Joined: Mon Aug 12, 2019 5:32 am
Location: Oz

Re: Sound Recorder to wav miniaudio

Post by CDXbow »

idle wrote: Tue Sep 16, 2025 8:29 am Added a windows x64 dll build
https://atomicwebserver.com/SoundRecorder.zip
Hi Idle,
This wont download for me because I get asked for a certificate and when I select any of the ones offered they are denied. Is this the same code as the in the git repo?
Cheers
CD
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sound Recorder to wav miniaudio

Post by idle »

CDXbow wrote: Tue Sep 16, 2025 9:44 pm
idle wrote: Tue Sep 16, 2025 8:29 am Added a windows x64 dll build
https://atomicwebserver.com/SoundRecorder.zip
Hi Idle,
This wont download for me because I get asked for a certificate and when I select any of the ones offered they are denied. Is this the same code as the in the git repo?
Cheers
CD
I thought I had turned that off. I was testing MTL mutual TLS, I've restarted it but my powers going off in 10 mins so website will be down for the day and no its not on git repo yet

Try again in 8 hours. My solar regulator blew up recently and I have no back up power. I also found my battery bank got harvested by the kids who thought it was handy for their trucks leaving me with two substandard batteries in the array. :evil:
User avatar
CDXbow
Enthusiast
Enthusiast
Posts: 103
Joined: Mon Aug 12, 2019 5:32 am
Location: Oz

Re: Sound Recorder to wav miniaudio

Post by CDXbow »

idle wrote: Tue Sep 16, 2025 9:52 pm
CDXbow wrote: Tue Sep 16, 2025 9:44 pm
idle wrote: Tue Sep 16, 2025 8:29 am Added a windows x64 dll build
https://atomicwebserver.com/SoundRecorder.zip
Hi Idle,
This wont download for me because I get asked for a certificate and when I select any of the ones offered they are denied. Is this the same code as the in the git repo?
Cheers
CD
I thought I had turned that off. I was testing MTL mutual TLS, I've restarted it but my powers going off in 10 mins so website will be down for the day and no its not on git repo yet

Try again in 8 hours. My solar regulator blew up recently and I have no back up power. I also found my battery bank got harvested by the kids who thought it was handy for their trucks leaving me with two substandard batteries in the array. :evil:
Civilised countries have electrical grids! :wink:
I'll try later as you suggest.
User avatar
idle
Always Here
Always Here
Posts: 5928
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sound Recorder to wav miniaudio

Post by idle »

CDXbow wrote: Tue Sep 16, 2025 10:32 pm
idle wrote: Tue Sep 16, 2025 9:52 pm
CDXbow wrote: Tue Sep 16, 2025 9:44 pm
idle wrote: Tue Sep 16, 2025 8:29 am Added a windows x64 dll build
https://atomicwebserver.com/SoundRecorder.zip
Hi Idle,
This wont download for me because I get asked for a certificate and when I select any of the ones offered they are denied. Is this the same code as the in the git repo?
Cheers
CD
I thought I had turned that off. I was testing MTL mutual TLS, I've restarted it but my powers going off in 10 mins so website will be down for the day and no its not on git repo yet

Try again in 8 hours. My solar regulator blew up recently and I have no back up power. I also found my battery bank got harvested by the kids who thought it was handy for their trucks leaving me with two substandard batteries in the array. :evil:
Civilised countries have electrical grids! :wink:
I'll try later as you suggest.
unfortunately or fortunately for me, they decided to replace a transformer, better than the old one going kaboom, they sound like a meteor exploding!
Post Reply