Audio library for FreeBasic - Features

General discussion for topics related to the FreeBASIC project or its community.
angros47
Posts: 2016
Joined: Jun 21, 2005 19:04

Re: Audio library for FreeBasic - Features

Post by angros47 »

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: 3942
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Audio library for FreeBasic - Features

Post by coderJeff »

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
Moderator
Posts: 1648
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Audio library for FreeBasic - Features

Post by paul doe »

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 ;)
hhr
Posts: 46
Joined: Nov 29, 2019 10:41

Re: Audio library for FreeBasic - Features

Post by hhr »

@angros47
I am testing the sound synthesizer in sfx and found that I can make my own functions without recompiling with buildwindows.bat.
That is good.

In some functions I had to change single to double, so in HarmonicWave. With single it produces harmonic artefacts, with double it works good.

I used Cool Edit with Frequency Analysis to test the wav-files.

GoldWave cannot open the wav-files:
GoldWave 4.21: Wave file RIFF chunk size is incorrect.
GoldWave 5.70: Internal data size is incorrect. The file may be corrupt. Continue anyway?

The function CreateWave produces disturb signals at the beginning and the end of the wav-file.

In the following program you can comment out line 58 to get the disturb signals only.

If you see an error in my program, please correct it.

Code: Select all

#cmdline "-exx"
#include "sfx.bi"
#inclib "fbsfx"

common shared __Samplerate as integer
''===================================================
'' SineWave function with AmplitudeModulate
type SineWaveFunctionA extends SoundFunction
   Freq as single
   Amplitude as single
   declare function GetNext() as single
end type

function SineWaveFunctionA.GetNext() as single
   t+=1
   if child=0 then
      return sin(6.28/__Samplerate*Freq*t+fm)*Amplitude
   else
      Amplitude=child->GetNext()
      return sin(6.28/__Samplerate*Freq*t+fm)*(Amplitude+1)/2
   end if
   
end function

function SineWaveA overload(Freq as single, Amplitude as single=1) as SineWaveFunctionA ptr
   dim w as SineWaveFunctionA ptr=new SineWaveFunctionA
   w->Freq=Freq
   w->Amplitude=Amplitude
   
   return w
end function

function SineWaveA overload(Freq as single, func as any ptr) as SineWaveFunctionA ptr
   dim w as SineWaveFunctionA ptr=new SineWaveFunctionA
   w->Freq=Freq
   w->child=func
   
   return w
end function
''===================================================
dim as long buffersize
dim as single d,start
dim as integer samplerate,channels,bits

samplerate=44100
channels=2
bits=16

SoundSet(samplerate,channels,bits)
''===================================================
d=1.1
start=0.1
dim shared as WaveHeaderType ptr pWave2
buffersize=samplerate*(start+d)
pWave2=CreateWave(buffersize,samplerate,channels,bits)

Sound pWave2,start,SineWaveA(440,ADSREnvelope(0.1,0.4,0.4,0.4,1)),d
''===================================================
dim as string path="test.wav"
SaveWave(path,pWave2)

PlayWave(pWave2)

''===================================================
sleep 2000

dim as WaveHeaderType ptr pWave
pWave = LoadWave(path)
if pWave = 0 then
   print "pWave = 0" : end
end if
PlayWave(pWave)

sleep

Last edited by hhr on Aug 05, 2022 12:11, edited 1 time in total.
angros47
Posts: 2016
Joined: Jun 21, 2005 19:04

Re: Audio library for FreeBasic - Features

Post by angros47 »

I cannot reproduce your issue: I tested your code, I hear no artifact.

The issue you describe sounds like the header of the WAV file is created incorrectly. Are you sure you haven't accidentally modified the function CreateWave?

It should be:

Code: Select all

#define FCC(c) *(cptr(Ulong Ptr,@##c))

function CreateWave(buffersize as long, frequency as long, channels as long, bits as long) as WaveHeaderType ptr

	if frequency=0 then frequency=__Samplerate
	if channels=0 then channels=__channels
	if bits=0 then bits=__bits_used


	dim as ulong  SampleSize=(bits\8)*channels
	dim as ulong     DataSize=SampleSize*buffersize

	'dim soundbuffer as ubyte ptr=new ubyte[DataSize+44]
	dim as ulong ptr s=allocate(DataSize+44)		'cast(ulong ptr,soundbuffer)
	s[ 0]=FCC("RIFF")
	s[ 1]=36 + DataSize
	s[ 2]=FCC("WAVE") 
	s[ 3]=FCC("fmt ")
	s[ 4]=16 
	s[ 5]=(channels shl 16) or 1
	s[ 6]=frequency
	s[ 7]=SampleSize*frequency
	s[ 8]=(Bits shl 16) or SampleSize
	s[ 9]=FCC("data")               
	s[10]=DataSize                  

	return cast(WaveHeaderType ptr,s)

end function
Also, why did you create a custom function SineWaveA instead of using AmplitudeModulate?
hhr
Posts: 46
Joined: Nov 29, 2019 10:41

Re: Audio library for FreeBasic - Features

Post by hhr »

I compiled your library without any changes and I was pleasantly surprised when I noticed that I can make my own modules in my own program.

You are right, I can use SineWave and AmplitudeModulate. With SineWaveA I simply wanted to practice.
It is difficult to become acquainted with the Sound modules.

I tested HarmonicWave with Cool Edit. The harmonic artefacts are under -70dB. I cannot hear them too.

I like sfx, but I think it is helpful to have some knowledge about hardware plug-in synthesizers.

Remains the problem with the disturbing signals. I hear them as clicking noise. And the problem with GoldWave.

Code: Select all

#cmdline "-exx"
#include "sfx.bi"
#inclib "fbsfx"

dim as single duration=1
dim as long buffersize
dim as integer samplerate,channels,bits
samplerate=44100
bits=16

channels=1
SoundSet(samplerate,channels,bits)
dim shared as WaveHeaderType ptr pWave1
buffersize=samplerate*duration
pWave1=CreateWave(buffersize,samplerate,channels,bits)

channels=2
SoundSet(samplerate,channels,bits)
dim shared as WaveHeaderType ptr pWave2
buffersize=samplerate*duration
pWave2=CreateWave(buffersize,samplerate,channels,bits)

SaveWave("silence1.wav",pWave1)
SaveWave("silence2.wav",pWave2)

PlayWave(pWave1)
PlayWave(pWave2)

sleep

Code: Select all

'' https://www.freebasic.net/wiki/ExtLibsfx
'' https://sourceforge.net/projects/freebasic-sfx-library/files/
'' https://en.wikipedia.org/wiki/Piano_key_frequencies

#cmdline "-exx"
#include "sfx.bi"
#inclib "fbsfx"

common shared __Samplerate as integer
''===================================================

type SustainEnvelopeFunction extends SoundFunction
   dc as single
   declare function GetNext() as single
end type

function SustainEnvelopeFunction.GetNext() as single
   return 2*dc-1
end function

function SustainEnvelope (dc as single) as SustainEnvelopeFunction ptr
   dim w as SustainEnvelopeFunction ptr=new SustainEnvelopeFunction
   w->dc=dc
   
   return w
end function

''===================================================

type ExponentialDecayEnvelopeFunction extends SoundFunction
   Dur as single
   declare function GetNext() as single
end type

function ExponentialDecayEnvelopeFunction.GetNext() as single
   t+=1
   return 2*exp(-t/(Dur*__Samplerate))-1
end function

function ExponentialDecayEnvelope (Dur as single) as ExponentialDecayEnvelopeFunction ptr
   dim w as ExponentialDecayEnvelopeFunction ptr=new ExponentialDecayEnvelopeFunction
   w->Dur=Dur
   
   return w
end function

''===================================================

dim as long numberoftones=200 ' number is chosen freely
dim as long buffersize,i,n(1 to numberoftones) ' n: MIDI note number
dim as single start,f(1 to numberoftones)      ' f: frequency
dim as single duration,d(1 to numberoftones),v(1 to numberoftones) ' d: duration, v: volume

n(1)=81      : d(1)=0.5   : v(1)=0.3   ' 81: A5, a", 880Hz
n(2)=n(1)+7  : d(2)=0.5   : v(2)=0.7  '5 instead of 7
n(3)=n(1)+5  : d(3)=0.25  : v(3)=0.2  '3 instead of 5
n(4)=n(1)+7  : d(4)=0.25  : v(4)=0.2  '5 instead of 7
n(5)=n(1)+0  : d(5)=0.5   : v(5)=0.5
n(6)=n(1)+0  : d(6)=0.25  : v(6)=0.4
n(7)=n(1)+0  : d(7)=0.25  : v(7)=0.4
n(8)=n(1)+3  : d(8)=0.25  : v(8)=0.9
n(9)=n(1)+3  : d(9)=0.25  : v(9)=0.5
n(10)=n(1)+7 : d(10)=0.5  : v(10)=0.2
n(11)=n(1)+0 : d(11)=0.5  : v(11)=0    ' v=0: Pause
n(12)=n(1)+0 : d(12)=0.5  : v(12)=0.7
n(13)=n(1)+7 : d(13)=0.5  : v(13)=0.7
n(14)=n(1)+5 : d(14)=0.25 : v(14)=0.7
n(15)=n(1)+7 : d(15)=0.25 : v(15)=0.7
n(16)=n(1)+0 : d(16)=0.5  : v(16)=0.7
n(17)=n(1)+3 : d(17)=0.25 : v(17)=0.9
n(18)=n(1)+3 : d(18)=0.25 : v(18)=0.9
n(19)=n(1)+2 : d(19)=0.25 : v(19)=0.9
n(20)=n(1)+2 : d(20)=0.25 : v(20)=0.9
n(21)=n(1)+0 : d(21)=0.5  : v(21)=1

function MidiNoteToFrequency(n as long) as single
   return (2^((n-69)/12))*440
end function

'fill frequency array, change duration of tones
for i=lbound(n) to ubound(n)
   f(i)=MidiNoteToFrequency(n(i))
   'd(i)/=1.5
next i

dim as integer samplerate,channels,bits
samplerate=44100
channels=1
bits=16

SoundSet(samplerate,channels,bits)

''===================================================

'calculate duration of pWave1
duration=0
for i=lbound(d) to ubound(d)
   duration+=d(i)
next i

dim shared as WaveHeaderType ptr pWave1
buffersize=samplerate*duration
pWave1=CreateWave(buffersize,samplerate,channels,bits)

sub tone1(start as single,f as single,d as single,v as single)
   if f<10 then exit sub
   Sound pWave1,start,AmplitudeModulate(ADSREnvelope(MixWaves(SineWave(f),MixWaves(SineWave(f/8),SineWave(f*2))),0.005,0,1,0.99,d),SustainEnvelope(v),0,0),d
end sub

'fill pWave1 buffer
start=0
for i=lbound(n) to ubound(n)
   tone1(start,f(i),d(i),v(i))
   start+=d(i)
next i

''===================================================

'changes for pWave2
n(10)=n(1)+7 : d(10)=1  : v(10)=0.3
n(11)=0      : d(11)=0  : v(11)=0
n(21)=n(1)+0 : d(21)=2  : v(21)=1

'calculate duration of pWave2
duration=0
for i=lbound(d) to ubound(d)
   duration+=d(i)
next i

dim shared as WaveHeaderType ptr pWave2
buffersize=samplerate*duration
pWave2=CreateWave(buffersize,samplerate,channels,bits)

sub tone2(start as single,f as single,d as single,v as single)
   if f<10 then exit sub
   Sound pWave2,start,AmplitudeModulate(AmplitudeModulate(HarmonicWave(f,1,0.15,0.07,0.06,0,0.03,0.01,0.01,0.01,0.01),ExponentialDecayEnvelope(0.2)),SustainEnvelope(v)),d
end sub

'fill pWave2 buffer
start=0
for i=lbound(n) to ubound(n)
   tone2(start,f(i),d(i),v(i))
   start+=d(i)
next i

''===================================================

SaveWave("test1.wav",pWave1)
SaveWave("test2.wav",pWave2)

PlayWave(pWave1)
Sound SustainEnvelope(0.5),0.5 ' 0.5 sec pause
PlayWave(pWave2)

sleep
angros47
Posts: 2016
Joined: Jun 21, 2005 19:04

Re: Audio library for FreeBasic - Features

Post by angros47 »

Ok, found the cause of that clicking sound:

Basically, since the memory is allocated, inside of the function CreateWave, with the command "allocate", it is not guaranteed to be all zero (that would mean silence), it contains random data. If you overwrite these data with the "sound" command, you won't hear the artifacts, otherwise you might. Even in your first example, you placed the first sound not at the beginning of the buffer, but at 0.1 seconds after it, and that caused the clicking sound. And since the previous content of the buffer is not defined (on one computer it can be a sequence of zero, on another a sequence of random numbers) not every computer would behave in the same way (and that was why I couldn't replicate your issue).

To fix it, in the file "wave.bas", locate the line:

Code: Select all

	dim as ulong ptr s=allocate(DataSize+44)
and replace the command "allocate" with "callocate". It should fix your issue.
hhr
Posts: 46
Joined: Nov 29, 2019 10:41

Re: Audio library for FreeBasic - Features

Post by hhr »

Thank you. Changing "wave.bas" and recompiling with "buildwindows.bat" fixed the clicking noise at the beginning of the file.
The problem at the end of the file and the problem with GoldWave remains.
Now I try to understand the wave header.
hhr
Posts: 46
Joined: Nov 29, 2019 10:41

Re: Audio library for FreeBasic - Features

Post by hhr »

Compiling with "buildwindows.bat" gives the message
.\windows\dsp.bas(12) warning 47(1): Use of reserved global or backend symbol, cs

I made wave files with sfx, Cool Edit and GoldWave. The file headers are equal.
The files made with Cool Edit and GoldWave have the same filelength.
The files made with sfx are 8 bytes shorter than the files made with Cool Edit and GoldWave.
fxm
Moderator
Posts: 11208
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Audio library for FreeBasic - Features

Post by fxm »

See Identifier Rules

.....
- For 64-bit compiler only and regarding the choice of user module level procedure identifier names, an additional restriction to the above normal 'Identifier Rules' should also exclude in the global namespace all register names or other symbols issued from the only 'intel' format assembler when it is used (the one by default, no problem with 'att' format), because they causes assembler errors or runtime bugs (due to the 64-bit compiler not decorating module level procedure names).
- Since fbc version 1.09.0, for x86 and x86_64 only (with any assembler format), the use of an inline asm symbol or a global/external/backend symbol induces a warning if such a symbol is used for a module level procedure or a shared variable in the global namespace.
.....
grindstone
Posts: 840
Joined: May 05, 2015 5:35
Location: Germany

Re: Audio library for FreeBasic - Features

Post by grindstone »

hhr wrote: Aug 04, 2022 19:35 Now I try to understand the wave header.
As I assume you are the same "hhr" as in the german forum, I recommend the RIFF WAVE description at Wikipedia. There is also a corresponding article in the english Wikipedia, but IMO it's much less informative.

For handling RIFF headers, I use this little snippet:

Code: Select all

Type RIFF_header
	filesize As ULong
	fmtlen As ULong
	fmttag As UShort
	channels As UShort
	samplerate As ULong
	bytespersecond As ULong
	frame As UShort
	bitspersample As UShort
	datalen As ULong
	headerlen As UShort
End Type

Declare Function GetRIFFval(header As String) As RIFF_header
Declare Function SetRIFFheader(header As String, parameter As RIFF_header) As String
Declare Sub showRIFFval(parameter As RIFF_header)

Function GetRIFFval(header As String) As RIFF_header
	Dim As Integer fmtpointer, datapointer
	Dim As RIFF_header rh
	Dim As Any Ptr hp
	
	fmtpointer = InStr(header,"fmt ")
	datapointer = InStr(header,"data")
	hp = StrPtr(header)
		
	With rh
		.filesize = *Cast(ULong Ptr,hp + 4)
		.fmtlen = *Cast(ULong Ptr,hp + fmtpointer + 3)
		.fmttag = *Cast(UShort Ptr,hp + fmtpointer + 7)
		.channels = *Cast(UShort Ptr,hp + fmtpointer + 9)
		.samplerate = *Cast(ULong Ptr,hp + fmtpointer + 11)
		.bytespersecond = *Cast(ULong Ptr,hp + fmtpointer + 15)
		.frame = *Cast(UShort Ptr,hp + fmtpointer + 19)
		.bitspersample = *Cast(UShort Ptr,hp + fmtpointer + 21)
		.datalen = *Cast(ULong Ptr,hp + datapointer + 3)
		.headerlen = datapointer + 7
	End With
		
	Return rh
	
End Function

Function SetRIFFheader(header As String = "", parameter As RIFF_header) As String
	Dim As Integer fmtpointer, datapointer
	Dim As String hd
	Dim As Any Ptr hp = StrPtr(header)
	
	If header = "" Then 'create header
		hd = "RIFF" + Mki(0) + "WAVEfmt " + Mki(16) + String(16,Chr(0)) + "data" + Mki(0) 'dummyheader
		parameter.fmtlen = 16
	Else
		hd = header
	EndIf
	
	fmtpointer = InStr(hd,"fmt ")
	datapointer = InStr(hd,"data")
	hd = Left(hd, datapointer + 7)
	
	With parameter
	  If .filesize Then
	  	*Cast(ULong Ptr,hp + 4) = .filesize
	  EndIf
		If .fmtlen Then
			*Cast(ULong Ptr,hp + fmtpointer + 3) = .fmtlen
		EndIf
		If .fmttag Then
			*Cast(UShort Ptr,hp + fmtpointer + 7) = .fmttag
		EndIf
		If .channels Then
			*Cast(UShort Ptr,hp + fmtpointer + 9) = .channels
		EndIf
		If .samplerate Then
			*Cast(ULong Ptr,hp + fmtpointer + 11) = .samplerate
		EndIf
		If .bytespersecond Then
			*Cast(ULong Ptr,hp + fmtpointer + 15) = .bytespersecond
		EndIf
		If .frame Then 'frame size = <number of channels> · ((<Bits/Sample (of one channel)> + 7) / 8)   (division without rest)
			*Cast(UShort Ptr,hp + fmtpointer + 19) = .frame
		EndIf
		If .bitspersample Then
			*Cast(UShort Ptr,hp + fmtpointer + 21) = .bitspersample
		EndIf
		If .datalen Then
			*Cast(ULong Ptr,hp + datapointer + 3) = .datalen
		EndIf
	End With
	
	Return hd
End Function

Sub showRIFFval(parameter As RIFF_header)
	
	With parameter
		Print "      .filesize ";.filesize;" (";.filesize + 8;")"
		Print "        .fmtlen ";.fmtlen 
		Print "        .fmttag ";.fmttag 
		Print "      .channels ";.channels
		Print "    .samplerate ";.samplerate
		Print ".bytespersecond ";.bytespersecond
		Print "         .frame ";.frame
		Print " .bitspersample ";.bitspersample 
		Print "       .datalen ";.datalen
		Print "     .headerlen ";.headerlen
	End With
	
End Sub
hhr
Posts: 46
Joined: Nov 29, 2019 10:41

Re: Audio library for FreeBasic - Features

Post by hhr »

In "writewav.bas" I changed "RiffLength" to "RiffLength+8" and recompiled with "buildwindows.bat".
Now the files have the right filelength and GoldWave can open them.
Beginning and end of the files seems to be OK.
Can this be the solution?
angros47
Posts: 2016
Joined: Jun 21, 2005 19:04

Re: Audio library for FreeBasic - Features

Post by angros47 »

Yes, it actually is the solution.
It was a mistake of mine: RiffLength, in fact, contains the size of the WAV file minus 8, so it needs to be fixed, and I forgot to add that when I wrote the function.

In fact, the header is usually 44 bytes, and in the CreateWave function only 36 is added (because the RIFF and WAVE strings are not taken in account)
Post Reply