Enum / Change audio output device? (Windows)

Just starting out? Need help? Post your questions and find answers here.
JustinJack
User
User
Posts: 89
Joined: Thu Feb 04, 2010 7:34 am
Location: Decatur, TX
Contact:

Enum / Change audio output device? (Windows)

Post by JustinJack »

Hello, everybody. Does anyone know of an easy way to enumerate, and change the audio device that playsound() uses. I want to ONLY redirect the audio that my PB program is playing and leave system sounds wherever they may be.

I am hoping that there will be an easy fix, and I wont have to go all API, so I don't have to rewrite a bunch.

Any help would be appreciated!
JustinJack
User
User
Posts: 89
Joined: Thu Feb 04, 2010 7:34 am
Location: Decatur, TX
Contact:

Re: Enum / Change audio output device? (Windows)

Post by JustinJack »

Hello, Justin. I've been watching this thread, and since no one seems to have a quick and easy answer for you, I figured I'd give you some help. Here is the solution to your problem. A small library that for your purposes, works like the PB sound library, BUT it allows you to select an audio output device on a PER SOUND basis. Yes! It's your dream come true. I'm hope you find this library I made for you very helpful. And good luck on your future endeavors!

Code: Select all


Structure SOUND_DEVICE_LIST
  szName.s
  iDevID.i
  nChannels.i
  iMfgrID.i
  iProdID.i
  szInterface.s
EndStructure


Structure RIFF_HEADER
  tag.c[4]
  iSize.l
  *pData.String
EndStructure

Structure PRIFF_HEADER
  *lpRIFF.RIFF_HEADER
EndStructure


Structure SOUND_STRUCT
  *pRawSound
  dwRawDataSize.l
  List pRiffs.PRIFF_HEADER()
  bIsLoop.b
  bIsPlaying.b
  wavefmt.WAVEFORMATEX
  whdr.WAVEHDR
  hwo.i
  nAudioDevice.i
  iMonitorThread.i
  iSignalByte.i
  iSemaphore.i
EndStructure

Global NewList active_sounds.SOUND_STRUCT()
Global SoundMgrMutex = CreateMutex()
Global defWaveOutput = #WAVE_MAPPER

Declare Get_Sound_Devices( List sd.SOUND_DEVICE_LIST() )

Procedure is_sound( *soundNumber )
  retVal = #False
  LockMutex(SoundMgrMutex)
  ForEach active_sounds()
    If @active_sounds() = *soundNumber
      retVal = #True
      Break
    EndIf
  Next
  UnlockMutex(SoundMgrMutex)
  ProcedureReturn retVal
EndProcedure

Procedure Prep_Sound( *lpSoundStruct.SOUND_STRUCT )
  retVal = #False
  *ptr = *lpSoundStruct\pRawSound + 12
  While Not(ok_to_stop)
    *lpRIFF.RIFF_HEADER = *ptr
    Select PeekS(@*lpRIFF\tag[0], 4)
      Case "fmt "
        CopyStructure(@*lpRIFF\pData, @*lpSoundStruct\wavefmt, WAVEFORMATEX)
        *lpSoundStruct\wavefmt\cbSize = SizeOf(WAVEFORMATEX)
      Case "data"
        AddElement(*lpSoundStruct\pRiffs())
        *lpSoundStruct\pRiffs()\lpRIFF = *lpRIFF
      Case ""
        ok_to_stop = #True
    EndSelect
    *ptr = @*lpRIFF\pData + *lpRIFF\iSize
  Wend
  If ListSize(*lpSoundStruct\pRiffs()) > 0
    retVal = #True
  EndIf
  ProcedureReturn retVal
EndProcedure

Procedure Load_Sound( szFileName.s )
  *retVal = 0
  myFile = OpenFile(#PB_Any, szFileName)
  If Not(IsFile(myFile))
    ProcedureReturn *retVal
  EndIf
  myLof = Lof(myFile)
  If mylof = 0
    CloseFile(myFile)
    ProcedureReturn *retVal
  EndIf
  LockMutex(SoundMgrMutex)
  AddElement(active_sounds())
  active_sounds()\pRawSound = AllocateMemory(mylof)
  ReadData(myFile, active_sounds()\pRawSound, mylof)
  CloseFile(myFile)
  If Not(Prep_Sound(@active_sounds()))
    FreeMemory(active_sounds()\pRawSound)
    DeleteElement(active_sounds())
  Else
    active_sounds()\nAudioDevice = defWaveOutput
    *retVal = @active_sounds()
  EndIf
  UnlockMutex(SoundMgrMutex)
  ProcedureReturn *retVal
EndProcedure

Procedure Set_Default_Audio_Device( nDefaultDevice ) ; Returns previous audio device, or NULL on failure.
  retVal = defWaveOutput
  defWaveOutput = nDefaultDevice
  ProcedureReturn retVal
EndProcedure

  
Procedure Is_Sound_Device_Good( nSoundDevice )
  retVal = #False
  NewList sd.SOUND_DEVICE_LIST()
  If Get_Sound_Devices(sd()) > 0
    ForEach sd()
      If sd()\iDevID = nSoundDevice
        retVal = #True
        Break
      EndIf
    Next
  EndIf
  ProcedureReturn retVal
EndProcedure


Procedure Catch_Sound( *lpAddr )
EndProcedure

Procedure Free_Sound( *soundNumber )
  If is_sound(*soundNumber)
    LockMutex(SoundMgrMutex)
    ChangeCurrentElement(active_sounds(), *soundNumber)
    FreeMemory(active_sounds()\pRawSound)
    ClearList(active_sounds()\pRiffs())
    If active_sounds()\bIsLoop = #True
      active_sounds()\iSignalByte = -1
      SignalSemaphore(active_sounds()\iSemaphore)
    EndIf
    While IsThread(active_sounds()\iMonitorThread)
      Delay(100)
    Wend
    DeleteElement(active_sounds())
    UnlockMutex(SoundMgrMutex)
  Else
    ProcedureReturn #False
  EndIf
  ProcedureReturn #True
EndProcedure


Procedure Wave_Output_CallBack( hwo, uMsg, *lpSoundStruct.SOUND_STRUCT, dwParam1, dwParam2 )
  Static whdr.WAVEHDR

  Select uMsg
    Case #WOM_OPEN
    Case #WOM_DONE
      If *lpSoundStruct\bIsPlaying = #True
        If *lpSoundStruct\bIsLoop = #True
          *lpSoundStruct\iSignalByte = 1
          SignalSemaphore(*lpSoundStruct\iSemaphore)
        EndIf
      EndIf
    Case #WOM_CLOSE
      *lpSoundStruct\bIsPlaying = #False
      
      
  EndSelect
  
EndProcedure

Procedure Is_Sound_Playing( *soundNumber )
  If Not(is_sound(*soundNumber))
    ProcedureReturn #False
  EndIf
  *lpSoundStruct.SOUND_STRUCT = *soundNumber
  retVal = *lpSoundStruct\bIsPlaying
  ProcedureReturn retVal
EndProcedure


Procedure stop_sound( *soundNumber )
  If Not(is_sound(*soundNumber))
    ProcedureReturn #False
  EndIf
  *lpSoundStruct.SOUND_STRUCT = *soundNumber
  *lpSoundStruct\bIsPlaying = #False
  waveOutReset_(*lpSoundStruct\hwo)
  waveOutClose_(*lpSoundStruct\hwo)
  If *lpSoundStruct\bIsLoop = #True
    *lpSoundStruct\iSignalByte = -1
    SignalSemaphore(*lpSoundStruct\iSemaphore)
  EndIf
  While IsThread(*lpSoundStruct\iMonitorThread)
    Delay(100)
  Wend
  ProcedureReturn 0
EndProcedure

Procedure Monitor_Sound_Thread( *lpSoundStruct.SOUND_STRUCT )
  While *lpSoundStruct\iSignalByte <> -1
    WaitSemaphore(*lpSoundStruct\iSemaphore)
    Select *lpSoundStruct\iSignalByte
      Case 1
        Debug "Repeat sound!"
      Case -1
        
        Break
    EndSelect
  Wend
  ProcedureReturn #Null
EndProcedure


Procedure Play_Sound( *soundNumber, bLoopSound = #False, nSoundDevice = -1 ) ;Returns -1 for bad sound device.
  retVal = #False
  If Not(is_sound(*soundNumber))
    ProcedureReturn retVal
  EndIf
  *lpSoundStruct.SOUND_STRUCT = *soundNumber
  ChangeCurrentElement(active_sounds(), *soundNumber)
  If nSoundDevice = -1
    nSoundDevice = *lpSoundStruct\nAudioDevice
  EndIf
  
  If (nSoundDevice = #WAVE_MAPPER Or Is_Sound_Device_Good(nSoundDevice))
    mmResult = waveOutOpen_(@*lpSoundStruct\hwo, nSoundDevice, @*lpSoundStruct\wavefmt, @Wave_Output_CallBack(), *soundNumber, #CALLBACK_FUNCTION)
    If mmResult = #MMSYSERR_NOERROR
      If bLoopSound = #True
        *lpSoundStruct\bIsLoop = #True
        *lpSoundStruct\iSignalByte = 0
        *lpSoundStruct\iSemaphore = CreateSemaphore(0)
        *lpSoundStruct\iMonitorThread = CreateThread(@Monitor_Sound_Thread(), *lpSoundStruct)
      EndIf
      
      If FirstElement(*lpSoundStruct\pRiffs())
        *lpSoundStruct\whdr\dwBufferLength = *lpSoundStruct\pRiffs()\lpRIFF\iSize
        *lpSoundStruct\whdr\lpData = @*lpSoundStruct\pRiffs()\lpRIFF\pData
        waveOutPrepareHeader_(*lpSoundStruct\hwo, @*lpSoundStruct\whdr, SizeOf(WAVEHDR))
        waveOutWrite_(*lpSoundStruct\hwo, @*lpSoundStruct\whdr, SizeOf(WAVEHDR))
        *lpSoundStruct\bIsPlaying = #True
      EndIf
      retVal = #True
    Else
      Debug "Failed!"
    EndIf
  Else
    retVal = -1
  EndIf
  ProcedureReturn retVal
EndProcedure

Enumeration
  #VOLUME_MUTE = 0
  #VOLUME_LOW = 50
  #VOLUME_MID = $7FFF
  #VOLUME_HIGH = 200
  #VOLUME_LOUD = $FFFF
EndEnumeration





Procedure Get_Sound_Devices( List sd.SOUND_DEVICE_LIST() )
  numDevs = waveOutGetNumDevs_()
  ClearList(sd())
  If numDevs > 0
    For i = 0 To (numDevs - 1)
      If waveOutGetDevCaps_(i, @woc.WAVEOUTCAPS, SizeOf(WAVEOUTCAPS)) = #MMSYSERR_NOERROR
        AddElement(sd())
        sd()\szName = PeekS(@woc\szPname[0])
        sd()\iDevID = i
        sd()\nChannels = woc\wChannels
        sd()\iMfgrID = woc\wMid
        sd()\iProdID = woc\wPid
        mySize = 400
        myValue.s = Space(mySize)
        myString.s = ""
        If waveOutMessage_(i, #DRV_RESERVED + 12, @myValue, mySize) = #MMSYSERR_NOERROR
          
          ;- Change unicode string to Ascii
          
          For j = 0 To 399
            mychar = PeekB(@myValue + j)
            If mychar = 0
              If PeekB(@myValue + j + 1) = 0
                Break
              EndIf
            Else
              myString.s + Chr(mychar)
            EndIf
          Next
          
        EndIf
        sd()\szInterface = myString
      EndIf
    Next
  EndIf
  ProcedureReturn ListSize(sd())
EndProcedure


  

Procedure set_sound_volume( *soundNumber, volume )
  If Not(is_sound(*soundNumber))
    ProcedureReturn #False
  EndIf
  *lpSoundStruct.SOUND_STRUCT = *soundNumber
  iVolume = 0
  waveOutGetVolume_(*lpSoundStruct\hwo, @iVolume)
  waveOutSetVolume_(*lpSoundStruct\hwo, volume)
  ProcedureReturn iVolume
EndProcedure


mySound = Load_Sound("c:\users\justin\desktop\dialtone.wav")
If is_sound(mySound)
  OpenWindow(1, 0, 0, 500, 500, "Waiting", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
  ButtonGadget(1, 10, 10, 100, 20, "Stop")
  NewList sdevs.SOUND_DEVICE_LIST()
  Play_Sound(mySound, #True, 1)
  set_sound_volume(mySound, #VOLUME_LOUD)
  Repeat
    dwEvent = WaitWindowEvent() 
    Select dwEvent
      Case #PB_Event_Gadget
        If EventGadget() = 1
          If EventType() = #PB_EventType_LeftClick
            If Is_Sound_Playing(mySound)
              stop_sound(mySound)
            EndIf
          EndIf
        EndIf
    EndSelect
    
  Until dwEvent = #PB_Event_CloseWindow
  CloseWindow(1)
  If Is_Sound_Playing(mySound)
    stop_sound(mySound)
  EndIf
  
  Free_Sound(mySound)
Else
  Debug "Sound Bad!"
EndIf


JustinJack
User
User
Posts: 89
Joined: Thu Feb 04, 2010 7:34 am
Location: Decatur, TX
Contact:

Re: Enum / Change audio output device? (Windows)

Post by JustinJack »

Why...THANK YOU JUSTIN! :D
JustinJack
User
User
Posts: 89
Joined: Thu Feb 04, 2010 7:34 am
Location: Decatur, TX
Contact:

Re: Enum / Change audio output device? (Windows)

Post by JustinJack »

By the way, I didn't spend a lot more time on this library before posting it on making sure it loops or splitting the RIFF 'data' into multiple buffers, or making my volume constants accurate for that matter, but if anyone wants to tinker with it and re-post for others, that would be great.
morosh
Enthusiast
Enthusiast
Posts: 329
Joined: Wed Aug 03, 2011 4:52 am
Location: Beirut, Lebanon

Re: Enum / Change audio output device? (Windows)

Post by morosh »

Hi:
I was looking for that also, thank you very much
PureBasic: Surprisingly simple, diabolically powerful
JustinJack
User
User
Posts: 89
Joined: Thu Feb 04, 2010 7:34 am
Location: Decatur, TX
Contact:

Re: Enum / Change audio output device? (Windows)

Post by JustinJack »

Hey, man, NO problem. I'm glad it helps you!
Post Reply