Microphone recorder and wave format save

Share your advanced PureBasic knowledge/code with the community.
dige
Addict
Addict
Posts: 1432
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: 675
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
dige
Addict
Addict
Posts: 1432
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Microphone recorder and wave format save

Post by dige »

miso wrote: Sat Dec 13, 2025 8:34 pm 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
🤔 hmm, not sure what you mean?

Below is a further developed version..

Code: Select all

EnableExplicit

; ==========================================================
;  Microphone Recorder (WAV) – PureBasic 6.30 (WinMM waveIn*)
;  Features:
;   - Microphone selection
;   - Level meter
;   - Software gain (0..4x)
;   - Auto-pause after 5s silence, resume on voice + 300ms pre-roll
; ==========================================================

CompilerIf #PB_Compiler_OS <> #PB_OS_Windows
  MessageRequester("Not supported", "This example is Windows-only (WinMM waveIn*).", 0)
  End
CompilerEndIf

#SAMPLE_RATE = 44100
#CHANNELS    = 1
#BITS        = 16

#NUM_BUFFERS   = 4
#BUFFER_BYTES  = 8192

#SILENCE_MAX_MS = 5000
#PREROLL_MS     = 300

#QUEUE_SLOTS = 128

#WAVE_MAPPER        = -1
#CALLBACK_WINDOW    = $00010000
#WAVE_FORMAT_PCM    = 1

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

#MAXPNAMELEN = 32

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

  waveInGetNumDevs.l()
  waveInGetDevCapsW.l(uDeviceID.i, *pwic.WAVEINCAPS, cbwic.l)
EndImport

; ---------- Globals ----------
Global gWaveIn.i
Global gCapturing.i
Global gShuttingDown.i
Global gSelectedDeviceID.i = #WAVE_MAPPER

Global gGainPermille.l     = 1000 ; 1000 = 1.0x
Global gVoiceThreshold.l   = 600
Global gMeterPermille.l    = 0

Global gAutoPaused.l       = 0
Global gDropped.l          = 0

Global gFile.i = -1
Global gDataBytes.q
Global gFileName.s
Global gStatus.s = "Ready"

Global gHwndMain.i

Global Dim gHdr.WAVEHDR(#NUM_BUFFERS-1)
Global Dim *gBuf(#NUM_BUFFERS-1)

; ---------- Queue (Window thread -> Worker thread) ----------
Global *gQueuePool = AllocateMemory(#QUEUE_SLOTS * #BUFFER_BYTES)
Global Dim gQueueBytes.i(#QUEUE_SLOTS-1)
Global gQRead.i, gQWrite.i, gQCount.i
Global gQMutex.i = CreateMutex()
Global gQSem.i   = CreateSemaphore()
Global gWorkerThread.i
Global gWorkerQuit.i

; ---------- Worker buffers ----------
Global gSilenceBufBytes.i = (#SAMPLE_RATE * #CHANNELS * (#BITS/8) * #SILENCE_MAX_MS) / 1000
Global *gSilenceBuf = AllocateMemory(gSilenceBufBytes)

Global gPreRollBytes.i = (#SAMPLE_RATE * #CHANNELS * (#BITS/8) * #PREROLL_MS) / 1000
Global *gPreRoll = AllocateMemory(gPreRollBytes)

; ---------- UI ----------
Enumeration
  #WinMain
  #cmbDevice
  #btnRefresh
  #btnStart
  #btnStop
  #txtFile
  #trkGain
  #lblGain
  #trkThresh
  #lblThresh
  #prgLevel
  #lblState
  #tmrUI
EndEnumeration

; ---------- WAV header ----------
Procedure WriteWavHeader(File.i, DataBytes.q)
  Protected channels = #CHANNELS
  Protected sr = #SAMPLE_RATE
  Protected bps = #BITS
  Protected byteRate = sr * channels * (bps/8)
  Protected blockAlign = channels * (bps/8)

  FileSeek(File, 0)
  WriteString(File, "RIFF", #PB_Ascii)
  WriteLong(File, 36 + DataBytes)
  WriteString(File, "WAVE", #PB_Ascii)

  WriteString(File, "fmt ", #PB_Ascii)
  WriteLong(File, 16)
  WriteWord(File, #WAVE_FORMAT_PCM)
  WriteWord(File, channels)
  WriteLong(File, sr)
  WriteLong(File, byteRate)
  WriteWord(File, blockAlign)
  WriteWord(File, bps)

  WriteString(File, "data", #PB_Ascii)
  WriteLong(File, DataBytes)
EndProcedure

; ---------- Audio helpers ----------
Procedure Clamp16(v.l)
  If v > 32767  : ProcedureReturn 32767  : EndIf
  If v < -32768 : ProcedureReturn -32768 : EndIf
  ProcedureReturn v
EndProcedure

Procedure ApplyGainAndMeter(*buf, bytes.i, gainPermille.i, *avgAbs.Integer, *peak.Integer)
  Protected samples.i = bytes / 2
  Protected i.i, s.l, a.l
  Protected sumAbs.q
  Protected peakLocal.l

  If samples <= 0
    *avgAbs\i = 0 : *peak\i = 0 : ProcedureReturn
  EndIf

  If gainPermille = 1000
    For i = 0 To samples - 1
      s = PeekW(*buf + i*2)
      a = s : If a < 0 : a = -a : EndIf
      sumAbs + a
      If a > peakLocal : peakLocal = a : EndIf
    Next
  Else
    For i = 0 To samples - 1
      s = PeekW(*buf + i*2)
      s = Clamp16((s * gainPermille) / 1000)
      PokeW(*buf + i*2, s)

      a = s : If a < 0 : a = -a : EndIf
      sumAbs + a
      If a > peakLocal : peakLocal = a : EndIf
    Next
  EndIf

  *avgAbs\i = sumAbs / samples
  *peak\i = peakLocal
EndProcedure

; ---------- Pre-roll ring (Worker only) ----------
Procedure PreRollPush_Worker(*pre, preBytes.i, *buf, bytes.i, *pos.Integer, *filled.Integer)
  Protected first.i

  If bytes <= 0 Or preBytes <= 0 : ProcedureReturn : EndIf

  If bytes >= preBytes
    CopyMemory(*buf + bytes - preBytes, *pre, preBytes)
    *pos\i = 0
    *filled\i = preBytes
    ProcedureReturn
  EndIf

  first = preBytes - *pos\i
  If bytes <= first
    CopyMemory(*buf, *pre + *pos\i, bytes)
    *pos\i + bytes
    If *pos\i = preBytes : *pos\i = 0 : EndIf
  Else
    CopyMemory(*buf, *pre + *pos\i, first)
    CopyMemory(*buf + first, *pre, bytes - first)
    *pos\i = bytes - first
  EndIf

  *filled\i + bytes
  If *filled\i > preBytes : *filled\i = preBytes : EndIf
EndProcedure

Procedure PreRollWrite_Worker(File.i, *pre, preBytes.i, pos.i, filled.i)
  Protected tail.i
  If filled <= 0 Or File < 0 : ProcedureReturn : EndIf

  If filled < preBytes
    WriteData(File, *pre, filled)
    gDataBytes + filled
  Else
    tail = preBytes - pos
    WriteData(File, *pre + pos, tail) : gDataBytes + tail
    If pos > 0
      WriteData(File, *pre, pos) : gDataBytes + pos
    EndIf
  EndIf
EndProcedure

; ---------- Worker thread ----------
Procedure WorkerThread(*dummy)
  Protected *work = AllocateMemory(#BUFFER_BYTES)
  Protected bytes.i, idx.i, avgAbs.Integer, peak.Integer
  Protected gainP.i, thresh.i, meter.i

  Protected autoPaused.i = 0
  Protected silenceUsed.i = 0
  Protected prePos.Integer : prePos\i = 0
  Protected preFilled.Integer : preFilled\i = 0

  While #True
    WaitSemaphore(gQSem)

    While #True
      If gWorkerQuit And gQCount = 0
        FreeMemory(*work)
        ProcedureReturn
      EndIf

      LockMutex(gQMutex)
      If gQCount = 0
        UnlockMutex(gQMutex)
        Break
      EndIf

      idx = gQRead
      bytes = gQueueBytes(idx)
      If bytes > #BUFFER_BYTES : bytes = #BUFFER_BYTES : EndIf

      CopyMemory(*gQueuePool + idx * #BUFFER_BYTES, *work, bytes)

      gQRead + 1 : If gQRead = #QUEUE_SLOTS : gQRead = 0 : EndIf
      gQCount - 1
      UnlockMutex(gQMutex)

      gainP  = gGainPermille
      thresh = gVoiceThreshold

      ApplyGainAndMeter(*work, bytes, gainP, @avgAbs, @peak)
      PreRollPush_Worker(*gPreRoll, gPreRollBytes, *work, bytes, @prePos, @preFilled)

      meter = (peak\i * 1000) / 32767
      gMeterPermille = meter

      If gFile >= 0
        If avgAbs\i >= thresh
          If autoPaused
            autoPaused = 0
            silenceUsed = 0
            PreRollWrite_Worker(gFile, *gPreRoll, gPreRollBytes, prePos\i, preFilled\i)
          ElseIf silenceUsed > 0
            WriteData(gFile, *gSilenceBuf, silenceUsed) : gDataBytes + silenceUsed
            silenceUsed = 0
          EndIf

          WriteData(gFile, *work, bytes) : gDataBytes + bytes

        Else
          If autoPaused = 0
            If silenceUsed + bytes < gSilenceBufBytes
              CopyMemory(*work, *gSilenceBuf + silenceUsed, bytes)
              silenceUsed + bytes
            Else
              silenceUsed = 0
              autoPaused = 1
            EndIf
          EndIf
        EndIf
      EndIf

      gAutoPaused = autoPaused
    Wend
  Wend
EndProcedure

; ---------- Window callback: receive MM_WIM_* messages ----------
Procedure WinMsgCB(WindowID, Message, wParam, lParam)
  Protected result = #PB_ProcessPureBasicEvents
  Protected *hdr.WAVEHDR
  Protected bytes.i

  Select Message
    Case #MM_WIM_OPEN
      gStatus = "Input device opened"
      ProcedureReturn result

    Case #MM_WIM_CLOSE
      gStatus = "Input device closed"
      ProcedureReturn result

    Case #MM_WIM_DATA
      If gShuttingDown Or gCapturing = 0
        ProcedureReturn result
      EndIf

      *hdr = lParam
      bytes = *hdr\dwBytesRecorded

      ; Safety clamps
      If bytes < 0 : bytes = 0 : EndIf
      If bytes > *hdr\dwBufferLength : bytes = *hdr\dwBufferLength : EndIf
      If bytes > #BUFFER_BYTES : bytes = #BUFFER_BYTES : EndIf

      If bytes > 0
        LockMutex(gQMutex)
        If gQCount < #QUEUE_SLOTS
          CopyMemory(*hdr\lpData, *gQueuePool + gQWrite * #BUFFER_BYTES, bytes)
          gQueueBytes(gQWrite) = bytes
          gQWrite + 1 : If gQWrite = #QUEUE_SLOTS : gQWrite = 0 : EndIf
          gQCount + 1
          UnlockMutex(gQMutex)
          SignalSemaphore(gQSem)
        Else
          UnlockMutex(gQMutex)
          gDropped + 1
        EndIf
      EndIf

      ; Return buffer to driver
      waveInAddBuffer(wParam, *hdr, SizeOf(WAVEHDR))
      ProcedureReturn result
  EndSelect

  ProcedureReturn result
EndProcedure

; ---------- Devices ----------
Procedure PopulateDevices()
  Protected n.i, i.i, caps.WAVEINCAPS, name.s

  ClearGadgetItems(#cmbDevice)
  AddGadgetItem(#cmbDevice, -1, "Default device (Windows)")
  SetGadgetItemData(#cmbDevice, 0, #WAVE_MAPPER)

  n = waveInGetNumDevs()
  For i = 0 To n - 1
    If waveInGetDevCapsW(i, @caps, SizeOf(WAVEINCAPS)) = 0
      name = PeekS(@caps\szPname[0], -1, #PB_Unicode)
      AddGadgetItem(#cmbDevice, -1, name)
      SetGadgetItemData(#cmbDevice, CountGadgetItems(#cmbDevice)-1, i)
    EndIf
  Next

  SetGadgetState(#cmbDevice, 0)
  DisableGadget(#btnStart, Bool(CountGadgetItems(#cmbDevice) = 1))
EndProcedure

; ---------- Start/Stop ----------
Procedure StartRecording()
  Protected fmt.WAVEFORMATEX
  Protected i.i, res.l
  Protected sel.i, devId.i

  If gCapturing : ProcedureReturn : EndIf

  sel = GetGadgetState(#cmbDevice)
  If sel < 0 : sel = 0 : EndIf
  devId = GetGadgetItemData(#cmbDevice, sel)
  gSelectedDeviceID = devId

  gFileName = SaveFileRequester("Save WAV", "recording.wav", "WAV (*.wav)|*.wav", 0)
  If gFileName = "" : ProcedureReturn : EndIf

  gFile = CreateFile(#PB_Any, gFileName)
  If gFile = 0
    gFile = -1
    MessageRequester("Error", "Could not create output file.", 0)
    ProcedureReturn
  EndIf

  gDataBytes = 0
  WriteWavHeader(gFile, 0)

  ; Queue reset
  LockMutex(gQMutex)
  gQRead = 0 : gQWrite = 0 : gQCount = 0
  UnlockMutex(gQMutex)
  gDropped = 0
  gAutoPaused = 0
  gMeterPermille = 0

  ; Worker start
  gWorkerQuit = 0
  gWorkerThread = CreateThread(@WorkerThread(), 0)

  ; waveIn open (CALLBACK_WINDOW -> messages to our window)
  fmt\wFormatTag = #WAVE_FORMAT_PCM
  fmt\nChannels = #CHANNELS
  fmt\nSamplesPerSec = #SAMPLE_RATE
  fmt\wBitsPerSample = #BITS
  fmt\nBlockAlign = (#CHANNELS * (#BITS/8))
  fmt\nAvgBytesPerSec = fmt\nSamplesPerSec * fmt\nBlockAlign
  fmt\cbSize = 0

  gShuttingDown = 0
  res = waveInOpen(@gWaveIn, devId, @fmt, gHwndMain, 0, #CALLBACK_WINDOW)
  If res <> 0
    gWorkerQuit = 1 : SignalSemaphore(gQSem) : WaitThread(gWorkerThread)
    CloseFile(gFile) : gFile = -1
    MessageRequester("Error", "waveInOpen() failed (code: " + Str(res) + ")", 0)
    ProcedureReturn
  EndIf

  For i = 0 To #NUM_BUFFERS-1
    *gBuf(i) = AllocateMemory(#BUFFER_BYTES)
    FillMemory(*gBuf(i), #BUFFER_BYTES, 0)

    gHdr(i)\lpData = *gBuf(i)
    gHdr(i)\dwBufferLength = #BUFFER_BYTES
    gHdr(i)\dwBytesRecorded = 0
    gHdr(i)\dwFlags = 0
    gHdr(i)\dwLoops = 0

    waveInPrepareHeader(gWaveIn, @gHdr(i), SizeOf(WAVEHDR))
    waveInAddBuffer(gWaveIn, @gHdr(i), SizeOf(WAVEHDR))
  Next

  gCapturing = #True
  waveInStart(gWaveIn)

  gStatus = "Recording..."
  DisableGadget(#btnStart, #True)
  DisableGadget(#btnStop, #False)
  DisableGadget(#cmbDevice, #True)
  DisableGadget(#btnRefresh, #True)
EndProcedure

Procedure StopRecording()
  Protected i.i

  If gCapturing = 0 : ProcedureReturn : EndIf

  gShuttingDown = 1
  gCapturing = 0

  waveInReset(gWaveIn)
  waveInStop(gWaveIn)

  For i = 0 To #NUM_BUFFERS-1
    waveInUnprepareHeader(gWaveIn, @gHdr(i), SizeOf(WAVEHDR))
  Next

  waveInClose(gWaveIn)
  gWaveIn = 0

  For i = 0 To #NUM_BUFFERS-1
    If *gBuf(i) : FreeMemory(*gBuf(i)) : *gBuf(i) = 0 : EndIf
  Next

  gWorkerQuit = 1
  SignalSemaphore(gQSem)
  WaitThread(gWorkerThread)

  If gFile >= 0
    WriteWavHeader(gFile, gDataBytes)
    CloseFile(gFile)
    gFile = -1
  EndIf

  gStatus = "Saved: " + gFileName + "  (Dropped: " + Str(gDropped) + ")"

  DisableGadget(#btnStart, #False)
  DisableGadget(#btnStop, #True)
  DisableGadget(#cmbDevice, #False)
  DisableGadget(#btnRefresh, #False)
EndProcedure

Procedure UpdateUI()
  SetGadgetState(#prgLevel, gMeterPermille)
  SetGadgetText(#lblGain,   "Input volume (software gain): " + StrF(gGainPermille/1000.0, 2) + "x")
  SetGadgetText(#lblThresh, "Voice threshold: " + Str(gVoiceThreshold))

  If gFileName = "" : SetGadgetText(#txtFile, "File: -") : Else : SetGadgetText(#txtFile, "File: " + gFileName) : EndIf

  If gCapturing
    If gAutoPaused
      SetGadgetText(#lblState, "Status: AUTO-PAUSE (silence ≥ " + Str(#SILENCE_MAX_MS/1000) + "s)  (Queue=" + Str(gQCount) + ", Dropped=" + Str(gDropped) + ")")
    Else
      SetGadgetText(#lblState, "Status: Recording...  (Queue=" + Str(gQCount) + ", Dropped=" + Str(gDropped) + ")")
    EndIf
  Else
    SetGadgetText(#lblState, "Status: " + gStatus)
  EndIf
EndProcedure

; ---------- Main ----------
OpenWindow(#WinMain, 0, 0, 580, 300, "Microphone Recorder (WAV) – Auto Pause", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
gHwndMain = WindowID(#WinMain)

SetWindowCallback(@WinMsgCB(), #WinMain)

ComboBoxGadget(#cmbDevice, 20, 16, 390, 24)
ButtonGadget(#btnRefresh, 422, 14, 138, 28, "Rescan mics")

ButtonGadget(#btnStart, 20, 50, 120, 30, "Start")
ButtonGadget(#btnStop,  160, 50, 120, 30, "Stop")
DisableGadget(#btnStop, #True)

TextGadget(#txtFile, 20, 85, 540, 20, "File: -")

TextGadget(#lblGain, 20, 110, 540, 20, "Input volume (software gain): 1.00x")
TrackBarGadget(#trkGain, 20, 130, 540, 20, 0, 400)
SetGadgetState(#trkGain, 100)

TextGadget(#lblThresh, 20, 160, 540, 20, "Voice threshold: 600")
TrackBarGadget(#trkThresh, 20, 180, 540, 20, 0, 4000)
SetGadgetState(#trkThresh, gVoiceThreshold)

ProgressBarGadget(#prgLevel, 20, 210, 540, 18, 0, 1000)
TextGadget(#lblState, 20, 232, 540, 50, "Status: Ready")

PopulateDevices()
AddWindowTimer(#WinMain, #tmrUI, 50)

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      If gCapturing : StopRecording() : EndIf
      Break

    Case #PB_Event_Timer
      If EventTimer() = #tmrUI : UpdateUI() : EndIf

    Case #PB_Event_Gadget
      Select EventGadget()
        Case #btnRefresh
          If gCapturing = 0 : PopulateDevices() : EndIf

        Case #btnStart
          StartRecording()

        Case #btnStop
          StopRecording()

        Case #trkGain
          gGainPermille = GetGadgetState(#trkGain) * 10 ; 0..4000

        Case #trkThresh
          gVoiceThreshold = GetGadgetState(#trkThresh)
      EndSelect
  EndSelect
ForEver

"Daddy, I'll run faster, then it is not so far..."
User avatar
minimy
Addict
Addict
Posts: 845
Joined: Mon Jul 08, 2013 8:43 pm
Location: off world

Re: Microphone recorder and wave format save

Post by minimy »

Hello, this was created with NA (natural intelligence) minimy 1.0. :mrgreen:
When is just for record in windows, i think the easy way is using API, like this:

Code: Select all

Procedure   startRecord(alias.s="record1", bits=16, channels=2, frequency=44100)
  mciSendString_("open new type waveaudio alias "+alias, #Null, 0, 0);
  mciSendString_("set "+alias+" bitspersample "+Str(bits)+" channels "+Str(channels)+" samplespersec "+Str(frequency), #Null, 0, 0);
  mciSendString_("record "+alias, #Null, 0, 0);
EndProcedure
Procedure   stopRecord(file.s="myrecord.wav", alias.s="record1")
  mciSendString_("stop "+alias, #Null, 0, 0);
  mciSendString_("save "+alias+" "+Chr(34)+file+Chr(34), #Null, 0, 0);
  mciSendString_("close "+alias, #Null, 0, 0);
EndProcedure

startRecord()
Delay(5000) ;record 5 seconds
stopRecord("D:\mi_wave.wav")
Chat GPT said 'Ohh yeaaahh!' :lol:
If translation=Error: reply="Sorry, Im Spanish": Endif
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5624
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Microphone recorder and wave format save

Post by Kwai chang caine »

Thanks at you two for shared your nices codes 8)
I not have microphone on my PC, so can't really test it :|
Believe you, it's possible to record the sound of the speakers, with your code little bit modified, or it's impossible :oops:
ImageThe happiness is a road...
Not a destination

PureBasic French Forum
User avatar
minimy
Addict
Addict
Posts: 845
Joined: Mon Jul 08, 2013 8:43 pm
Location: off world

Re: Microphone recorder and wave format save

Post by minimy »

May be. I think you need have enable sound mixer for it. Depend of your sound card and drivers too. (I think, im not sure)

1) Open control panel / Sound:
Look for Stereo Mix in your PC, put it enable. And now you have another device like microphone. (Realtek can have)

2) Other way is using WASAPI.

3) 3rd part programs like Vcable. This create a virtual device.

4) NOT RECOMMENDED: If the moon is in sagitarius, and taurus in venus, open a coconut when the moon is full. With this no work but is fun :lol:
If translation=Error: reply="Sorry, Im Spanish": Endif
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5624
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Microphone recorder and wave format save

Post by Kwai chang caine »

Thanks for your answer 8)
I belive i try the 4e method... I love adventure :lol:
ImageThe happiness is a road...
Not a destination

PureBasic French Forum
Post Reply