DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer

Started by Midimaster, March 22, 2021, 15:16:23

Previous topic - Next topic

Midimaster

The way you descripe is exactly what i'm already doing in the 20Tracks. But this is Java and there the  use of audio is already as we need it.

Now I'm searching for a approach to do it on BlitzMax. I don not want that somebody writes the code for me. I only need a minmal code to understand how to access the FreeAudio directlty. Derron wrote me, that it is possible but without a working example.

By the way: reducing the volume of the channels to the half, will only work upto 10 instruments. A half level is -10dB and 10 instruments with level of -10dB will reach 0dB again.
So my 20Tracks uses a "forecast-Normalize-Algo" to reduce the single tracks to the best value.


Back to PortAudio


The versio 19 has this feature:
Quote....In addition to this "Callback" architecture, V19 also supports a "Blocking I/O" model which uses read and write calls which may be more familiar to non-audio programmers. Note that at this time, not all APIs support this functionality....


see the double NULL in the OpenStream-Call:
Pa_OpenStream(
           &stream,
           NULL,
           &outputParameters,
           SAMPLE_RATE,
           FRAMES_PER_BUFFER,
           paClipOff,
           NULL,      /* no callback, use blocking API */
           NULL ;    /* no callback, so no callback userData */
....
Pa_WriteStream( stream, buffer, FRAMES_PER_BUFFER ));


And I also found soething similar in the old PortAudio 18.1, where we have a wrapper. There is a folder PABLIO and inside is this pablio.h:
Quote#ifndef _PABLIO_H
#define _PABLIO_H

#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

/*
* $Id: pablio.h,v 1.1.1.1 2002/01/22 00:52:53 phil Exp $
* PABLIO.h
* Portable Audio Blocking read/write utility.
*
* Author: Phil Burk, http://www.softsynth.com/portaudio/
*
* Include file for PABLIO, the Portable Audio Blocking I/O Library.
* PABLIO is built on top of PortAudio, the Portable Audio Library.
* For more information see: http://www.audiomulch.com/portaudio/
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "portaudio.h"
#include "ringbuffer.h"
#include <string.h>

typedef struct
{
    RingBuffer   inFIFO;
    RingBuffer   outFIFO;
    PortAudioStream *stream;
    int          bytesPerFrame;
    int          samplesPerFrame;
}
PABLIO_Stream;

/* Values for flags for OpenAudioStream(). */
#define PABLIO_READ     (1<<0)
#define PABLIO_WRITE    (1<<1)
#define PABLIO_READ_WRITE    (PABLIO_READ|PABLIO_WRITE)
#define PABLIO_MONO     (1<<2)
#define PABLIO_STEREO   (1<<3)

/************************************************************
* Write data to ring buffer.
* Will not return until all the data has been written.
*/
long WriteAudioStream( PABLIO_Stream *aStream, void *data, long numFrames );

/************************************************************
* Read data from ring buffer.
* Will not return until all the data has been read.
*/
long ReadAudioStream( PABLIO_Stream *aStream, void *data, long numFrames );

/************************************************************
* Return the number of frames that could be written to the stream without
* having to wait.
*/
long GetAudioStreamWriteable( PABLIO_Stream *aStream );

/************************************************************
* Return the number of frames that are available to be read from the
* stream without having to wait.
*/
long GetAudioStreamReadable( PABLIO_Stream *aStream );

/************************************************************
* Opens a PortAudio stream with default characteristics.
* Allocates PABLIO_Stream structure.
*
* flags parameter can be an ORed combination of:
*    PABLIO_READ, PABLIO_WRITE, or PABLIO_READ_WRITE,
*    and either PABLIO_MONO or PABLIO_STEREO
*/
PaError OpenAudioStream( PABLIO_Stream **aStreamPtr, double sampleRate,
                         PaSampleFormat format, long flags );

PaError CloseAudioStream( PABLIO_Stream *aStream );

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* _PABLIO_H */





...on the way to Egypt

Derron

The TBank is just a convenient helper to have "managed memory blocks". So an easy way to copy stuff, to write and read - and if no longer used, freeing it.


Quote from: Midimaster on March 23, 2021, 12:49:59
Now to your code:
Code (BlitzMax) Select
Graphics 800,600
Local audioSample:TAudioSample = CreateAudioSample(44100, 441000, SF_MONO16LE)
For Local i%=0 To 44100-1
AudioSample.samples[i]=Sin(i)*1000.0
Next

...does not play anything. What did I forget?


This is not my code. And I even added explanatory words -- for why I need to pass certain parameters ...
Instead of CreateAudioSample() I use CreateStaticAudioSample() .


I took your code and added some stuff here and there:
Code (BlitzMax) Select

SuperStrict
Framework Brl.StandardIO
Import Brl.Audio
Import Brl.FreeAudioAudio

Import Brl.GLMax2D
Import Brl.Bank
Import random.xoshiro

'open window
Graphics 800, 600
SetAudioDriver("FreeAudio")


'mono sound
rem
Local AudioSample:TAudioSample = CreateAudioSample(44100, 44100, SF_MONO16LE)
For Local i%=0 To 44100-1
AudioSample.samples[i] = Sin(i)*1000.0
Next
Local sound:TSound = LoadSound(AudioSample)
PlaySound Sound
endrem


Local format:Int = SF_MONO16LE 'SF_STEREO16LE
Local bits:Int = 16
Local freq:Int = 44100
Local channels:Int = 2
'Local bufferLength:Int = 1024
Local bufferLength:Int = 44100
Local audioBufferBank:TBank = New TBank
audioBufferBank.Resize( bufferLength )

Local audioSample:TAudioSample = CreateStaticAudioSample(audioBufferBank.Lock(), bufferLength, freq, format)
Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length, bits, channels, freq, audioSample.samples, $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)

'change sound after creation
'For Local i:int = 0 Until audioBufferBank.capacity()
' AudioSample.samples[i] = Sin(i)*1000.0
'Next

'or do it by manipulating the memory, not the "samples"
For Local i:int = 0 Until audioBufferBank.capacity()
audioBufferBank.PokeByte(size_t(i), int(Sin(i)*1000.0))
Next



PlaySound sound

Repeat
Cls

Flip
Until KeyHit(KEY_ESCAPE) Or AppTerminate()



Derron

This code should play your whatever-it-is (am no sound expert - not even an beginner). Press "space" and it should play a different tone.
All of them "repeated".

Use a channel (returned by PlaySound) to control volume etc - or to stop / pause playing.

Code (Blitzmax) Select

SuperStrict
Framework Brl.StandardIO
Import Brl.Audio
Import Brl.FreeAudioAudio

Import Brl.GLMax2D
Import Brl.Bank
Import random.xoshiro 'I prefer that over brl.RandomDefault

'open window
Graphics 800, 600
SetAudioDriver("FreeAudio")



Local format:Int = SF_MONO16LE 'SF_STEREO16LE
Local bits:Int = 16
Local freq:Int = 44100
Local channels:Int = 2
'Local bufferLength:Int = 1024
Local bufferLength:Int = 44100
Local audioBufferBank:TBank = New TBank
audioBufferBank.Resize( bufferLength )

Local audioSample:TAudioSample = CreateStaticAudioSample(audioBufferBank.Lock(), bufferLength, freq, format)
Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length, bits, channels, freq, audioSample.samples, $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)

FillSoundBuffer(audioBufferBank)


PlaySound sound

Repeat
Cls
DrawText("SPACE for random sound data", 0,0)
If KeyHit(KEY_SPACE) Then FillSoundBuffer(audioBufferBank)
Flip
Until KeyHit(KEY_ESCAPE) Or AppTerminate()



Function FillSoundBuffer(bank:TBank)
Local tone:int = Rand(0,1000)
'or do it by manipulating the memory, not the "samples"
For Local i:int = 0 Until bank.capacity()
bank.PokeByte(size_t(i), int(Sin(i)* tone))
Next
End Function

Derron

#18
As said I have no clue what samples etc really mean ... so I do not know what exactly which parameter changes

with
Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length, bits, channels, freq, audioSample.samples, $80000000 )
I get "gaps" in the playback - so sound .. silence ... sound ... silence.
I assume "audioSample.length" is incorrect here.

All this stuff requires a calculation based on bits, freq etc - so how many "samples" (??) fit into it.

For me this worked gapless (I divided the "audio sample length" by a number found by "testing")
Code (Blitzmax) Select

Local format:Int = SF_STEREO16LE
Local bits:Int = 16
Local freq:Int = 44100
Local channels:Int = 2
Local bufferLength:Int = 1024 * 4 '4kb buffer
Local audioBufferBank:TBank = New TBank
audioBufferBank.Resize( bufferLength )

Local audioSample:TAudioSample = CreateStaticAudioSample(audioBufferBank.Lock(), bufferLength, freq, format)
Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length / 32, bits, channels, freq, audioSample.samples, $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)

But ... it also altered the sound (pitched higher) ... so this is not the solution. Yet you now have a memory block which you can alter - and is used for playback, it just needs the right "parameters/sizes".


I am sure Midimaster and IWasAdam are now secretly laughing at me (come on be honest! this feeling of "I know it... young boy... I know it!"). So excuse me :)


bye
Ron

Derron

#19
You seem to be able to skip "TAudioSample" ... yet I do not know how to get rid of the "gap".

Code (Blitzmax) Select

Local format:Int = SF_STEREO16LE
Local bits:Int = 16
Local freq:Int = 44100
Local channels:Int = 2
Local sampleCount:Int = 1000
Local sampleSize:Int = 8
Local sampleData:TBank = CreateBank(sampleCount * sampleSize)

Local fa_sound:Byte Ptr = fa_CreateSound( int(sampleData.size()), bits, channels, freq, sampleData.Lock(), $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null )

FillSoundBuffer(sampleData)



And you might even be able to skip the "TSound" too
Code (BlitzMax) Select

Local format:Int = SF_STEREO16LE
Local bits:Int = 16
Local freq:Int = 44100
Local channels:Int = 2
Local sampleCount:Int = 1000
Local sampleSize:Int = 8     'think that equals  BytesPerSample[format] * channels
Local sampleData:TBank = CreateBank(sampleCount * sampleSize)

Local fa_sound:Byte Ptr = fa_CreateSound( int(sampleData.size()), bits, channels, freq, sampleData.Lock(), $80000000 )
'start playing
Local fa_channel:Int = fa_PlaySound( fa_sound,  0, 0)
'in case you need the TChannel (for volume adjustment etc)
'Local channel:TChannel = TFreeAudioChannel.CreateWithChannel( fa_channel )

FillSoundBuffer(sampleData)


Edit - maybe "length" is "size of block divided by bytersPerSample" ?
Code (BlitzMax) Select

Local fa_sound:Byte Ptr = fa_CreateSound( int(sampleData.size() / BytesPerSample[format]), bits, channels, freq, sampleData.Lock(), $80000000 )


Yet I still have a little "nothing" between the repetitions - at least it sounds so.
in my streamed audio thingy I did not have these hearable "error" - so maybe the way the buffer is filled is not how it is supposed to be done.


bye
Ron

Midimaster

#20
Quote from: Derron on March 23, 2021, 14:44:06
I took your  code and added some stuff here and there:.....
Code (BlitzMax) Select

SuperStrict
Framework Brl.StandardIO
Import Brl.Audio
Import Brl.FreeAudioAudio

Import Brl.GLMax2D
Import Brl.Bank


'open window
Graphics 800, 600
SetAudioDriver("FreeAudio")
.....


Ok I try to understand your code. It works.

There is a little bug in the code. You fill the whole buffer, but the Sound plays only half the time. This is because 2 channels with a 16bit-sound only needs 4Bytes.

Does this part only manipulating BYTEs instead of SHORTs?
    'or do it by manipulating the memory, not the "samples"
    For Local i:Int = 0 Until audioBufferBank.capacity()
            audioBufferBank.PokeByte(Size_T(i), Int(Sin(i)*1000.0))
    Next


Another question: What is this Size_T()-casting good for? Doesn't PokeByte expect an integer here? Or is it only because of preventing negativ values?


As a first result of the night I was able to code a 300msec Ringbuffer, which plays a song by copying 882 samples every 20msec. It is a executable example (...you need the "test.ogg" from attachment)
Code (BlitzMax) Select
SuperStrict
Graphics 800, 600
SetAudioDriver("FreeAudio")


Global Vorlage:TAudioSample=LoadAudioSample("test.ogg")

Local audioSample:TAudioSample = CreateAudioSample(8820, 22050, SF_MONO16LE)

Local fa_sound:Byte Ptr = fa_CreateSound( 8820, 16, 1, 22050, audioSample.samples, $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)


Global Zeit%=MilliSecs()
PlaySound sound
Zeit=MilliSecs()+50
Local Zeiger%=0
Global lfd%=0
Repeat
Cls
If Zeit<MilliSecs()
zeit=zeit+20
Print Zeiger
For Local i:Int = 0 Until 882
AudioSample.samples[zeiger+i] = Vorlage.Samples[lfd]
lfd=lfd+1
Next
Zeiger=(Zeiger +882) Mod 17640
EndIf
Flip 0
Until AppTerminate()


The only thing I do not understand is: Why do I need to raise the variable ZEIGER% to 17640, when the AudioSamples has only 8820 samples. Does this mean, that AudioSample.Samples[0] and AudioSample.Samples[1] show two Bytes of the same SHORT value? I would have expected that AudioSample.Samples[0] shows the first sample value and AudioSample.Samples[1] already the second, and so on.
...on the way to Egypt

Derron

How all this stereo, mono ... stuff works: I do not really know how this is interchained or not.


size_t ... can be removed, left over from some other tries (Streams in NG use size_t not integers).

Quote
Does this part only manipulating BYTEs instead of SHORTs?

there is also "PokeShort" and "PokeInt". for now this Code just places "the value" into each byte of the memory. At least I understood it that way.
You could even make "sampleData" an "sampleData:int[]" - should work too (then passing "sampleData" as param instead of "sampleData.lock()").



So I would interested in a code which works for SF_STEREO16LE and SF_MONO16LE (and other formats) ... without "gaps".


bye
Ron

Midimaster

#22
Here is a more abstract version of the SF_MONO16 Ringbuffer. It will be the base for testing also SF_STEREO16 next.

I could reduce the latency to 40msec and sharpen the code to a minimum:
It is a executable example (...you need the "test.ogg" from post#20 attachment)
Code (BlitzMax) Select
SuperStrict
Graphics 800, 600
SetAudioDriver("FreeAudio")
Global WritePointer:Int, ReadPointer:Int, WriteTime:Int
Global BITS:Int, CHANNELS:Int, BUFFER_SIZE:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int

Const HERTZ:Int=22050

Const CHUNK_TIME:Int = 20 'msec
Const FORMAT:Int     = SF_MONO16LE

Select FORMAT
Case SF_MONO16LE
BITS:Int=16
CHANNELS:Int=1
CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
BUFFER_SAMPLES = CHUNK_SIZE*2
BUFFER_SIZE    = BUFFER_SAMPLES * CHANNELS * BITS/8
Default
End
End Select

' derron's part:
Global Source:TAudioSample=LoadAudioSample("test.ogg")
Global Buffer:TAudioSample = CreateAudioSample(BUFFER_SIZE/2, HERTZ, FORMAT)
Local fa_sound:Byte Ptr = fa_CreateSound( BUFFER_SIZE/2, BITS, CHANNELS, HERTZ, Buffer.Samples, $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)

' now put 40msec Latency into the Buffer:
SendOneChunk()
SendOneChunk()

'already start playback with an empty buffer
PlaySound sound
WriteTime=MilliSecs()

Repeat
Cls
If WriteTime<MilliSecs()
' this cares about really 20msec timing:
WriteTime=WriteTime + CHUNK_TIME
Print "Write to " + WritePointer + " at time: " + WriteTime
SendOneChunk
EndIf
Flip 0
Until AppTerminate()
End

Function SendOneChunk()
' put a amount of sampls into the buffer:
For Local i:Int = 0 Until CHUNK_SIZE
Buffer.samples[WritePointer+i] = Source.Samples[ReadPointer]
ReadPointer=ReadPointer+1
Next
WritePointer=(WritePointer + CHUNK_SIZE) Mod BUFFER_SIZE
End Function
     

...on the way to Egypt

Derron

why are you only able to reduce latency to 40ms?

Can you explain that a bit ?

the whole "buffer refill loop":

        If WriteTime<MilliSecs()
                ' this cares about really 20msec timing:
                WriteTime=WriteTime + CHUNK_TIME
                Print "Write to " + WritePointer + " at time: " + WriteTime
                SendOneChunk
        EndIf

can be run in thread ...   which allows to do other stuff in the main one.

But we will tackle that once you got the stereo part working.


I do not how much this here will help:

local channel:TChannel = PlaySound(sound)

...
"playing channel position: " + TFreeAudioChannel(channel).Position()

Maybe this can be used to display what "x" is currently played.


bye
Ron

Midimaster

Why 40msec?
1.
40msec is for me a first step towards better values. I always develop apps in steps from "safe" to "fast". Each step is tested and when there are no problem the next step will be explored. So 40msec is a beginning.
2.
40msec is already nearly "real-time". As sound moves 343 meter/sec also in the real world you reach 40msec if somebody plays in a distance of 14meter. You are not able to feel a dis-synchronity if you see a singer performing 14 meters ahead of you.
3.
As closer you try to reach the 0-value you get more and more problems with dropouts etc.

At the moment I use a minimum of 3 Chunks:
- One (the first), where the SoundDevise is reading
- One (the second) as a buffer between the first and the third
- One (the third), where my App is writing


STEREO

Stereo is not that complicate. You only have to shovel the douple amount of bytes in the SendOneChunk()-function. Thats all.


At the moment the SendOneChunk()-function needs <1msec to complete. But the data are copied as single Bytes. This is not usefull in practice, because you only can manipulate the music when you have it as SHORTs. The Bytes in the RAM represent alternating the high and the low byte of these SHORTS. To combine them you have to care about LittleEndian or BigEndian.

In my next step I will try to find a fast SHORTs access to it. Maybe it's your BANK-approach, maybe I will try to do it with RAM-Streams. We will see.
...on the way to Egypt

Derron

Thanks for the elaborative explanation.


bye
Ron

Scaremonger

So will the ringbuffer continue to play silence when the buffer is empty or will it simply pause playback until the buffer is refilled?

Derron

This depends on you I think.
You could always use the channel (returned by PlaySound) to stop playback - and if you do you might better also stop refilling the buffers.

Yet I assume Midimaster just wants a way to "live output" something - so he won't stop (pun intended).


bye
Ron

Midimaster

#28
Silence at the end?
this is the dis-advantage of ring buffers. they will play the last datas of the buffer over and over again. So you have to fill the buffer in a last round with 0 to prevent this.

Now 16bit Stereo and Manipulation
Here is the next step. Now you can stream SF_STEREO16 and you can manipulate the samples by using a RAMStream. This is an executable example. use the TestC.ogg from attachment. (the poor quality you can hear is in the ogg file, not because of the algo). you can move the mouse to pan between LEFT and RIGHT.

What are the possible Chunk-times?

Any chunk time must be a possible divider of the 1000msec. f.e. 20 or 25 or 40, but not 30!
But the result must again be a possible divider of the samples frequency. 

In our example the chunk-time for refreshing must stay at 20msec, because only this value is without crackling. This is caused by the fact that the prime number in 22050Hz is 441 and this a 1/50 of 22050Hz. ---> 1/50 from 100msec= 20msec.
Also a possible chunk-times is 40msec = 882samples

This means with 44100Hz you could also use 10msec, 20msec and 40msec

This means with 48000Hz or 24000Hz you are more flexible: 1, 2, 4, 5, 8, 10, 20, 25 and 40msec

Here is the next version:
Code (BlitzMax) Select
SuperStrict

Graphics 800, 600
SetAudioDriver("FreeAudio")
Global WritePointer:Int, ReadPointer:Int, WriteTime:Int
Global BITS:Int, CHANNELS:Int, BUFFER_SIZE:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int, FORMAT:Int
Global Volume#
Const HERTZ:Int=22050

Const CHUNK_TIME:Int = 20 'msec  (=441 samples)

Global Source:TAudioSample=LoadAudioSample("testc.ogg")
FORMAT  = Source.Format
'FORMAT = SF_STEREO16LE

Select FORMAT
Case SF_MONO16LE
BITS:Int=16
CHANNELS:Int=1
CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
BUFFER_SAMPLES = HERTZ * CHUNK_TIME            * BITS/8/1000*8
BUFFER_SIZE    = BUFFER_SAMPLES * 2 * CHANNELS
Case SF_STEREO16LE
BITS:Int=16
CHANNELS:Int=2
CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
BUFFER_SAMPLES = HERTZ * CHUNK_TIME            * BITS/8/1000 *8
BUFFER_SIZE    = BUFFER_SAMPLES * 2 *CHANNELS
Default
Notify "Audio format not supported"
End
End Select

Global Lesen:TStream= CreateRamStream(Source.Samples,Source.length * CHANNELS * BITS/8,True,0)

Print "Chunksize=" + Chunk_size
Print "buffersize=" + BUFFER_SIZE
Print "source length=" + Source.length  + " format" + source.format + " in samples="  + Source.length * CHANNELS * BITS/8


' derron's part:
Global Buffer:TAudioSample = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
Local fa_sound:Byte Ptr = fa_CreateSound( BUFFER_SAMPLES, BITS, CHANNELS, HERTZ, Buffer.Samples, $80000000 )
Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)


Global Schreiben:TStream= CreateRamStream(Buffer.Samples,Buffer_size,True,True)


' now put 40msec Latency into the Buffer:
SendOneChunk()
SendOneChunk()

'already start playback with an empty buffer
PlaySound sound
WriteTime=MilliSecs()
Local StartTime%=MilliSecs()
Local z1$,z2$,z3$
Repeat
Cls
DrawText "Move the mouse to pan between left and right",100,100
DrawText "LEFT",10,300
DrawText "MIDDLE",300,300
DrawText "RIGHT",700,300
DrawTabText "Write to:", WritePointer  , 300,400
DrawTabText "at time:", (WriteTime-StartTime) , 300,430
DrawTabText "read from:", ReadPointer , 300,460

If WriteTime<MilliSecs()
' this cares about really 20msec timing:
WriteTime=WriteTime + CHUNK_TIME
'Print "Write to " + WritePointer + " at time: " + (WriteTime-StartTime) +" " + Buffer.Length + " " + buffer_size
SendOneChunk
EndIf
volume=1-MouseX()/800.0
Flip 0
Until AppTerminate()
End


Function DrawTabText(t1$,t2$,X%,y%)
DrawText t1, X-TextWidth(T1)-70,Y
DrawText t2, X-TextWidth(T2),Y
End Function

Function SendOneChunk()
' put a amount of samples into the buffer:
Local Zeit%=MilliSecs()
Lesen.Seek(ReadPointer)
Schreiben.Seek(WritePointer)
For Local i:Int = 0 Until CHUNK_SIZE Step 2
Local value:Int=Lesen.ReadShort()
If value>32768
value=value-65535
EndIf

' mouse volume:
If i Mod 4=0 Or FORMAT=SF_MONO16LE
value=value*volume
Else
value=value*(1-volume)
EndIf
If value<0
value=value+65535
EndIf

Schreiben.WriteShort(value)
ReadPointer=ReadPointer+2
Next
WritePointer=(WritePointer + CHUNK_SIZE) Mod (BUFFER_SIZE)
ReadPointer=ReadPointer Mod (Source.length * CHANNELS * BITS/8)
End Function
     


...on the way to Egypt

Midimaster

#29
It would be time to check, which driver causes which latency. Latency is the time the driver needs to really bring the sound to the speakers.

You think you hear the sound exactly in the moment where you start it playing? Wrong! Use this test app to find out how FreeAudio or DirectSound really perform on BlitzMax!

This is an executable BlitzMax-app. you need the Test_5_Clicks.ogg from the attachment. First select an avaiable driver in the code line #12. The start the app. Follow the steps in the app window. The 5 ticks will repeat every 6sec, so you can repeatly test the latency and get an avarage time.

Code (BlitzMax) Select
Graphics 800,600
Local Drivername$[9]
Local Nr%
For Local a:String = EachIn AudioDrivers()
Print "Use Nr=" + Nr + " to use  Driver: " + a
Drivername[Nr]=a
Nr=Nr+1
Next

'*** Select driver here:***********

Nr=1

'*********************************
SetAudioDriver DriverName[Nr]

Global Test:TSound=LoadSound("Test_5_Clicks.ogg",SOUND_LOOP)
PlaySound Test
Global StartTime%=MilliSecs()
Global LastLatency%, Avarage%, Rounds%
Repeat
Cls
SetColor 255,255,255
DrawText "A U D I O   D R I V E R   L A T E N C Y   C H E C K " ,100,40
DrawText "Checking audio driver: " + DriverName[Nr] ,200,70
DrawText "1. Listen to the ticks",200,90
DrawText "2. Feel the timing...",200,110
DrawText "3. Exactly at the 5th push LEFT MOUSE or the Key [X]",200,130
DrawText "RESULTS:",200,200
DrawText "   Last Lastency = " + LastLatency + " msec",200,230
If Rounds>0
DrawText "Avarage Lastency = " + Int(Avarage/rounds)+ " msec",200,250
EndIf
DrawText "click here:",200,370
DrawText "Press [R] to reset values",500,570
DrawRect 200,400,100,100
SetColor 1,1,1
DrawRect 201,401,98,98
If MouseDown(1)
Print latency(MilliSecs()-StartTime)
Repeat
Until MouseDown(1)=False
EndIf

If KeyDown(KEY_X)
Print latency(MilliSecs()-StartTime)
Repeat
Until KeyDown(KEY_X)=0
ElseIf KeyDown(KEY_R)
Rounds=0
Avarage=0
LastLatency=0
Repeat
Until KeyDown(KEY_X)=0
EndIf
Flip 0
Until AppTerminate()

Function Latency%(Value)
value=value-4000
value=value Mod 6000
If value>1000
value=value-6000
EndIf
LastLatency=value
Rounds=Rounds+1
Avarage=Avarage+value
Return value
End Function

...on the way to Egypt