Audio library for FreeBasic - Features

For other topics related to the FreeBASIC project or its community.
angros47
Posts: 1635
Joined: Jun 21, 2005 19:04

Re: Audio library for FreeBasic - Features

Postby angros47 » Nov 28, 2019 16:18

My library supports mixing two sound channels, actually (and it also allows to play two different sounds on right and left channel). It does not support 3d sound, because 3d sound is actually an abstraction, that is rendered in different ways depending on the available hardware: if the audio output is mono, 3D sounds is nothing more than distance attenuation, if the audio output is stereo, panning can be adjusted according to the sound source relative position. But if the audio is played using a surround system, a good 3d sound system must be able to detect it, and use the most appropriate speaker to simulate the right direction sound will come from. On the other hand, if headphones are used, a 3d sound system should use Head Related Transfer Function (HTRF), to produce a real binaural sound. So, the right method to produce a sound should be decided according on the available hardware, and for that reason 3d sound should use the integrated driver (just as 3d graphic could be rendered from FreeBasic, but it's better to use drivers like Direct3D or OpenGL)

About the suggestion to use two or more buffers, and swap them, to provide seamless audio output (in a way similar of how the "flip" command works for graphic), I would invite you to look at the DMA.BAS file in the dos subdirectory, and at the DSP.BAS file in the windows subdirectory: you will see that it's exactly what my library already does.
coderJeff
Site Admin
Posts: 3192
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Audio library for FreeBasic - Features

Postby coderJeff » Nov 30, 2019 13:48

angros47 wrote:Designing an API is likely harder than implementing it, since any future change will break a lot of code.


I looked through some of my codes. I regret not having finished and published more. I think I have tried fbsound, sdl, and fmod sound libraries. My API tends to look something like this:

Code: Select all

#ifndef __SOUND_BI__INCLUDE__
#define __SOUND_BI__INCLUDE__

declare function AUDIO_Init() as integer
declare function AUDIO_Exit() as integer

declare sub SOUND_Stop( byval channel as const integer = -1 )
declare sub SOUND_Pause( byval channel as const integer = -1 )
declare sub SOUND_Resume( byval channel as const integer = -1 )
declare function SOUND_Load( byref filename as const string ) as integer
declare function SOUND_Play _
   ( _
      byref filename as const string, _
      byval channel as const integer = -1, _
      byval loops as const integer = 0, _
      byval volume as integer = -1 _
   ) as integer
declare sub SOUND_Volume( byval volume as integer, byval channel as integer = -1 )

declare sub MUSIC_Stop()
declare sub MUSIC_Pause()
declare sub MUSIC_Resume()
declare function MUSIC_Load( byref filename as const string ) as integer
declare function MUSIC_Play _
   ( _
      byref filename as const string _
   ) as integer
declare sub MUSIC_Volume( byval volume as integer )

#endif


The major commonality is:
1) loading resources from files
2) channels for each sound

For the simple stuff I have tried, this works for me. I have not bothered with namespaces as I am only consumer (currently) of the code. The idea though is that the API stays the same and some module handles the interaction with the actual sound library to be used.
paul doe
Posts: 1244
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Audio library for FreeBasic - Features

Postby paul doe » Nov 30, 2019 16:01

For what is worth, I abstracted the SDL2 mixer like this, when I was coding my entry for Lachies' compo:

Code: Select all

#include once "engine/engine.bi"

namespace SDL2
  /'
    Music sample resource
  '/
  class _
    MusicSample _
    implements Engine.IResource
   
    public:
      declare constructor( _
        byref as const Engine.Identifier, _
        byref as const string )
      declare destructor() override
     
      declare operator _
        cast() as mix_music ptr
     
      declare function _
        load() as MusicSample ptr override
     
    protected:
      declare constructor()
     
      as mix_music ptr _
        m_sample
      as string _
        m_fileName
      as boolean _
        m_loadFromFile
  end class
 
  constructor _
    MusicSample()
  end constructor
 
  constructor _
    MusicSample( _
      byref anID as const Engine.Identifier, _
      byref aFileName as const string )
   
    base( anID )
   
    if( fileExists( aFileName ) ) then
      isAvailable()
     
      m_fileName = aFileName
      m_loadFromFile = true
    end if
  end constructor
 
  destructor _
    MusicSample()
   
    mix_FreeMusic( m_sample )
  end destructor
 
  operator _
    MusicSample.cast() _
    as mix_music ptr
   
    return( m_sample )
  end operator
 
  function _
    MusicSample.load() _
    as MusicSample ptr
   
    if( available ) then
      if( m_loadFromFile ) then
        '' Load from file
        m_sample = mix_LoadMUS( m_fileName )
       
        if( m_sample = Null ) then
          '' Sample not available
          isUnavailable()
        end if
      else
        '' Load from memory
      end if
    else
      '' Sample is available for playing
      isAvailable()
    end if
   
    return( @this )
  end function
 
  /'
    Sound sample resource
  '/
  class _
    SoundSample _
    implements Engine.IResource
   
    public:
      declare constructor( _
        byref as const Engine.Identifier, _
        byref as const string )
      declare destructor() override
     
      declare operator _
        cast() as mix_chunk ptr
     
      declare function _
        load() as SoundSample ptr override
     
    protected:
      declare constructor()
     
      as mix_chunk ptr _
        m_sample
      as string _
        m_fileName
      as boolean _
        m_loadFromFile
  end class
 
  constructor _
    SoundSample()
  end constructor
 
  constructor _
    SoundSample( _
      byref anID as const Engine.Identifier, _
      byref aFileName as const string )
   
    base( anID )
   
    if( fileExists( aFileName ) ) then
      isAvailable()
     
      m_fileName = aFileName
      m_loadFromFile = true
    else
      isUnavailable()
    end if
  end constructor
 
  destructor _
    SoundSample()
   
    mix_freeChunk( m_sample )
  end destructor
 
  operator _
    SoundSample.cast() _
    as mix_chunk ptr
   
    return( m_sample )
  end operator
 
  function _
    SoundSample.load() _
    as SoundSample ptr
   
    if( available ) then
      if( m_loadFromFile ) then
        '' Load from file
        m_sample = mix_loadWAV( m_fileName )
       
        if( m_sample = Null ) then
          '' Could not load sound
          isUnavailable()
        end if
      else
        '' TODO: Load from memory
      end if
    else
      '' Sound is unavailable to play
      isUnavailable()
    end if
   
    return( @this )
  end function
 
  class _
    SoundManager _
    implements Engine.IObject
   
    public:
      declare constructor()
      declare destructor()
   
      declare property _
        available() as boolean
      declare property _
        samplingRate() as long
      declare property _
        channels() as long
      declare property _
        audioFormat() as Uint16
      declare property _
        bufferSize() as long
      declare property _
        musicVolume() as Math.float
      declare property _
        channelVolume( _
          byval as integer ) _
        as Math.float
       
      declare function _
        play( _
          byval as MusicSample ptr ) _
        as SoundManager ptr
      declare function _
        play( _
          byval as SoundSample ptr ) _
        as SoundManager ptr
       
      declare function _
        setMusicVolume( _
          byval as Math.float ) _
        as SoundManager ptr
      declare function _
        setChannelVolume( _
          byval as integer, _
          byval as Math.float ) _
        as SoundManager ptr
      declare function _
        setAllChannelsVolume( _
          byval as Math.float ) _
        as SoundManager ptr
       
    private:
      as long _
        m_samplingRate, _
        m_channels, _
        m_bufferSize
      as Uint16 _
        m_audioFormat
      as boolean _
        m_available
      as Math.float _
        m_musicVolume, _
        m_channelVolume( any )
      as integer _
        m_soundChannels, _
        m_currentSoundChannel, _
        m_reservedChannels, _
        m_channelGroups, _
        m_currentChannelGroup
  end class
 
  constructor _
    SoundManager()
   
    m_samplingRate = 44100
    m_audioFormat = MIX_DEFAULT_FORMAT
    m_channels = 2
    m_bufferSize = 4096
   
    if( mix_OpenAudio( _
      m_samplingRate, _
      m_audioFormat, _
      m_channels, _
      m_bufferSize ) = Null ) then
     
      m_available = false
    else
      mix_QuerySpec( _
        @m_samplingRate, _
        @m_audioFormat, _
        @m_channels )
     
      m_available = true
    end if
   
    m_soundChannels = 32
    mix_allocateChannels( m_soundChannels )
   
    redim _
      m_channelVolume( 0 to m_soundChannels - 1 )
   
    m_reservedChannels = m_soundChannels
    mix_reserveChannels( m_reservedChannels )
   
    m_channelGroups = 24
   
    '' Interleave the channels in different channel groups to
    '' have the maximum possible sound effects playing at once
    for _
      i as integer = 0 _
      to m_soundChannels - 1
     
      mix_groupChannel( i, i mod m_channelGroups )
      m_channelVolume( i ) = 1.0
    next
   
    setMusicVolume( 1.0 )
  end constructor
 
  destructor _
    SoundManager()
   
    mix_haltMusic()
    mix_AllocateChannels( 0 )
  end destructor
 
  property _
    SoundManager.available() _
    as boolean
   
    return( m_available )
  end property
 
  property _
    SoundManager.samplingRate() _
    as long
   
    return( m_samplingRate )
  end property
 
  property _
    SoundManager.audioFormat() _
    as Uint16
   
    return( m_audioFormat )
  end property
 
  property _
    SoundManager.channels() _
    as long
   
    return( m_channels )
  end property
 
  property _
    SoundManager.bufferSize() _
    as long
   
    return( m_bufferSize )
  end property
 
  property _
    SoundManager.musicVolume() _
    as Math.float
   
    return( m_musicVolume )
  end property
 
  property _
    SoundManager.channelVolume( _
      byval index as integer ) _
    as Math.float
   
    return( m_channelVolume( index ) )
  end property
 
  function _
    SoundManager.play( _
      byval aMusicSample as MusicSample ptr ) _
    as SoundManager ptr
   
    if( aMusicSample->available ) then
      mix_playMusic( *aMusicSample, -1 )
    end if
   
    return( @this )
  end function
 
  function _
    SoundManager.play( _
      byval aSoundSample as SoundSample ptr ) _
    as SoundManager ptr
   
    if( aSoundSample->available ) then
      if( _
        not cbool( mix_playing( m_currentSoundChannel ) ) ) then     
        mix_playChannel( _
          m_currentSoundChannel, _
          *aSoundSample, _
          0 )
      end if
     
      m_currentSoundChannel = _
        ( m_currentSoundChannel + 1 ) mod m_soundChannels
    else
      '? "Not available!"
    end if
   
    return( @this )
  end function
 
  function _
    SoundManager.setMusicVolume( _
      byval volume as Math.float ) _
    as SoundManager ptr
   
    m_musicVolume = Math.clamp( _
      volume, 0.0, 1.0 )
   
    mix_volumeMusic( m_musicVolume * MIX_MAX_VOLUME )
   
    return( @this )
  end function

  function _
    SoundManager.setChannelVolume( _
      byval index as integer, _
      byval volume as Math.float ) _
    as SoundManager ptr
   
    volume = Math.clamp( _
      volume, 0.0, 1.0 )
   
    m_channelVolume( index ) = volume
   
    mix_volume( _
      m_channelVolume( index ), _
      volume * MIX_MAX_VOLUME )
   
    return( @this )
  end function
 
  function _
    SoundManager.setAllChannelsVolume( _
      byval volume as Math.float ) _
    as SoundManager ptr
   
    volume = Math.clamp( _
      volume, 0.0, 1.0 )
   
    for _
      i as integer = 0 _
      to m_soundChannels - 1
     
      m_channelVolume( i ) = volume
    next
   
    mix_volume( _
      -1, _
      volume * MIX_MAX_VOLUME )
   
    return( @this )
  end function 
end namespace

/'
  Main test
'/
using Engine
using SDL2

SDL_Init( SDL_INIT_AUDIO )

/'
  To load samples, a SoundManager needs to be created first, and the
  SoundManager assumes that all the setup needed has been previously
  done when initializing the program.
'/
var _
  sm = new SoundManager()

'' Create and load samples
var _
  ms1 = new MusicSample( _
    "AngryAngus", _
    "Angry_Angus.mp3" )->load(), _
  ms2 = new MusicSample( _
    "BarStoolsHurt", _
    "Bar_Stools_Hurt_Man.mp3" )->load(), _
  ss1 = new SoundSample( _
    "bumperhit", _
    "data/snd/bumper.wav" )->load(), _
  ss2 = new SoundSample( _
    "flipperactivate", _
    "data/snd/flipper.wav" )->load()

dim as boolean _
  done = false
dim as string _
  k

do while( not done )
  k = inkey()
 
  if( k = chr( 27 ) ) then
    done = true
  end if
 
  if( k = "1" ) then
    sm->play( ms1 )
  end if
 
  if( k = "2" ) then
    sm->play( ms2 )
  end if
 
  if( k = "3" ) then
    sm->setMusicVolume( sm->musicVolume + 0.1 )
  end if
 
  if( k = "4" ) then
    sm->setMusicVolume( sm->musicVolume - 0.1 ) 
  end if
 
  if( k = "5" ) then
    sm->play( ss1 )
  end if
 
  if( k = "6" ) then
    sm->play( ss2 )
  end if
 
  if( k = "7" ) then
    sm->setAllChannelsVolume( 0.2 )
  end if
 
  if( k = "8" ) then
    sm->setAllChannelsVolume( 1.0 )
  end if
 
  sleep( 1, 1 )
loop

delete( ms1 )
delete( ms2 )
delete( ss1 )
delete( ss2 )
delete( sm )

SDL_Quit()

Needless to say, angros47 lib can also be easily accomodated ;)

Return to “Community Discussion”

Who is online

Users browsing this forum: Bing [Bot] and 4 guests