Microphone recorder and wave format save

Share your advanced PureBasic knowledge/code with the community.
dige
Addict
Addict
Posts: 1429
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Microphone recorder and wave format save

Post by dige »

A lightweight microphone recorder that captures live audio and saves it straight to a clean PCM WAV file.
With a simple Start/Stop UI, it’s perfect for quick voice notes, testing, or grabbing raw mic takes for further processing.
Very nice: created with ChatGPT 5.2, it worked now out of the box—code in, compile, record. :D :D

Code: Select all

EnableExplicit

CompilerIf #PB_Compiler_OS <> #PB_OS_Windows
  CompilerError "Dieses Beispiel nutzt WinMM (waveIn*) und ist Windows-only."
CompilerEndIf

; ===== WinMM Imports =====
Import "winmm.lib"
  waveInOpen(*phwi, uDeviceID.l, *pwfx, dwCallback.i, dwInstance.i, fdwOpen.l)
  waveInPrepareHeader(hwi.i, *pwh, cbwh.l)
  waveInUnprepareHeader(hwi.i, *pwh, cbwh.l)
  waveInAddBuffer(hwi.i, *pwh, cbwh.l)
  waveInStart(hwi.i)
  waveInStop(hwi.i)
  waveInReset(hwi.i)
  waveInClose(hwi.i)
EndImport

; ===== Constants =====
#WAVE_MAPPER        = -1
#CALLBACK_FUNCTION  = $00030000

#MM_WIM_OPEN  = $3BE
#MM_WIM_CLOSE = $3BF
#MM_WIM_DATA  = $3C0

; ===== Globals =====
Global g_hWaveIn.i
Global g_isRecording.i
Global g_file.i
Global g_outPath$
Global g_bytesWritten.q
Global g_mutex.i

Global g_sampleRate.l  = 44100
Global g_bits.w        = 16
Global g_channels.w    = 1

Global g_bufferCount.i = 4
Global g_bufferSize.i  = 16384 ; bytes pro Buffer (kleiner => weniger Latenz, höher => weniger Overhead)

Global Dim g_hdr.WAVEHDR(g_bufferCount - 1)
Global Dim g_bufPtr.i(g_bufferCount - 1)

; ===== WAV Header =====
Procedure WriteWavHeader(file.i, sampleRate.l, bits.w, channels.w, dataSize.q)
  Protected byteRate.l  = sampleRate * channels * (bits / 8)
  Protected blockAlign.w = channels * (bits / 8)

  FileSeek(file, 0)
  WriteString(file, "RIFF", #PB_Ascii)
  WriteLong(file, 36 + dataSize)
  WriteString(file, "WAVE", #PB_Ascii)

  WriteString(file, "fmt ", #PB_Ascii)
  WriteLong(file, 16)          ; PCM
  WriteWord(file, 1)           ; AudioFormat = 1 (PCM)
  WriteWord(file, channels)
  WriteLong(file, sampleRate)
  WriteLong(file, byteRate)
  WriteWord(file, blockAlign)
  WriteWord(file, bits)

  WriteString(file, "data", #PB_Ascii)
  WriteLong(file, dataSize)
EndProcedure

; ===== waveIn Callback =====
Procedure waveInProc(hWaveIn.i, uMsg.l, dwInstance.i, dwParam1.i, dwParam2.i)
  If uMsg = #MM_WIM_DATA And g_isRecording
    Protected *wh.WAVEHDR = dwParam1
    If *wh And *wh\dwBytesRecorded > 0 And g_file
      LockMutex(g_mutex)
        WriteData(g_file, *wh\lpData, *wh\dwBytesRecorded)
        g_bytesWritten + *wh\dwBytesRecorded
      UnlockMutex(g_mutex)
    EndIf

    ; Buffer wieder in die Queue
    *wh\dwBytesRecorded = 0
    waveInAddBuffer(g_hWaveIn, *wh, SizeOf(WAVEHDR))
  EndIf
EndProcedure

; ===== Recording Control =====
Procedure.i SetupAndStartRecording(outPath$)
  Protected fmt.WAVEFORMATEX
  Protected i, mmr.l

  g_outPath$ = outPath$
  g_bytesWritten = 0

  g_file = CreateFile(#PB_Any, g_outPath$)
  If g_file = 0
    MessageRequester("Fehler", "Datei kann nicht erstellt werden:" + #CRLF$ + g_outPath$)
    ProcedureReturn #False
  EndIf

  ; Placeholder Header (wird beim Stop aktualisiert)
  WriteWavHeader(g_file, g_sampleRate, g_bits, g_channels, 0)

  If g_mutex = 0 : g_mutex = CreateMutex() : EndIf

  fmt\wFormatTag      = 1
  fmt\nChannels       = g_channels
  fmt\nSamplesPerSec  = g_sampleRate
  fmt\wBitsPerSample  = g_bits
  fmt\nBlockAlign     = g_channels * (g_bits / 8)
  fmt\nAvgBytesPerSec = fmt\nSamplesPerSec * fmt\nBlockAlign
  fmt\cbSize          = 0

  mmr = waveInOpen(@g_hWaveIn, #WAVE_MAPPER, @fmt, @waveInProc(), 0, #CALLBACK_FUNCTION)
  If mmr <> 0 Or g_hWaveIn = 0
    CloseFile(g_file) : g_file = 0
    MessageRequester("Fehler", "waveInOpen fehlgeschlagen (MMRESULT=" + Str(mmr) + ")")
    ProcedureReturn #False
  EndIf

  ; Buffers vorbereiten und hinzufügen
  For i = 0 To g_bufferCount - 1
    g_bufPtr(i) = AllocateMemory(g_bufferSize)
    If g_bufPtr(i) = 0
      MessageRequester("Fehler", "AllocateMemory fehlgeschlagen.")
      ProcedureReturn #False
    EndIf

    g_hdr(i)\lpData         = g_bufPtr(i)
    g_hdr(i)\dwBufferLength = g_bufferSize
    g_hdr(i)\dwBytesRecorded = 0
    g_hdr(i)\dwFlags        = 0
    g_hdr(i)\dwLoops        = 0

    waveInPrepareHeader(g_hWaveIn, @g_hdr(i), SizeOf(WAVEHDR))
    waveInAddBuffer(g_hWaveIn, @g_hdr(i), SizeOf(WAVEHDR))
  Next

  g_isRecording = #True
  waveInStart(g_hWaveIn)
  ProcedureReturn #True
EndProcedure

Procedure StopRecording()
  Protected i

  If g_isRecording = 0
    ProcedureReturn
  EndIf

  g_isRecording = #False

  If g_hWaveIn
    waveInStop(g_hWaveIn)
    waveInReset(g_hWaveIn)

    For i = 0 To g_bufferCount - 1
      waveInUnprepareHeader(g_hWaveIn, @g_hdr(i), SizeOf(WAVEHDR))
      If g_bufPtr(i)
        FreeMemory(g_bufPtr(i))
        g_bufPtr(i) = 0
      EndIf
    Next

    waveInClose(g_hWaveIn)
    g_hWaveIn = 0
  EndIf

  If g_file
    ; Header mit korrekter Datenlänge überschreiben
    WriteWavHeader(g_file, g_sampleRate, g_bits, g_channels, g_bytesWritten)
    CloseFile(g_file)
    g_file = 0
  EndIf
EndProcedure

; ===== Simple GUI =====
Enumeration
  #Win
  #BtnStart
  #BtnStop
  #TxtStatus
EndEnumeration

Procedure SetStatus(text$)
  SetGadgetText(#TxtStatus, text$)
EndProcedure

OpenWindow(#Win, 0, 0, 520, 180, "Mikrofon aufnehmen -> WAV", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ButtonGadget(#BtnStart, 20, 20, 150, 35, "Start")
ButtonGadget(#BtnStop,  190, 20, 150, 35, "Stop")
TextGadget(#TxtStatus, 20, 75, 480, 80, "Bereit. Klick auf Start.", #PB_Text_Border)

DisableGadget(#BtnStop, #True)

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break

    Case #PB_Event_Gadget
      Select EventGadget()
        Case #BtnStart
          Define save$ = SaveFileRequester("Speichern als ...", "aufnahme.wav", "WAV (*.wav)|*.wav", 0)
          If save$
            If SetupAndStartRecording(save$)
              SetStatus("Aufnahme läuft..." + #CRLF$ + save$)
              DisableGadget(#BtnStart, #True)
              DisableGadget(#BtnStop,  #False)
            EndIf
          EndIf

        Case #BtnStop
          StopRecording()
          SetStatus("Gespeichert:" + #CRLF$ + g_outPath$ + #CRLF$ + "Bytes Audio: " + Str(g_bytesWritten))
          DisableGadget(#BtnStart, #False)
          DisableGadget(#BtnStop,  #True)
      EndSelect
  EndSelect
ForEver

; Sauber beenden, falls Fenster während Aufnahme geschlossen wird
StopRecording()
End

"Daddy, I'll run faster, then it is not so far..."
miso
Enthusiast
Enthusiast
Posts: 580
Joined: Sat Oct 21, 2023 4:06 pm
Location: Hungary

Re: Microphone recorder and wave format save

Post by miso »

Thanks for the code, tested, works under windows.
dige wrote: Fri Dec 12, 2025 4:16 pm ChatGPT 5.2, it worked now out of the box—code in, compile, record. :D :D
Ensure me, that line is sarcasm.

Miso
Post Reply