Ooops
October 21, 2021, 16:42:10

Author Topic: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer  (Read 3841 times)

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #15 on: March 23, 2021, 13:23:48 »
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:
Code: [Select]
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 */





See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #16 on: March 23, 2021, 14:44:06 »
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.


Now to your code:
Code: BlitzMax
  1. Graphics 800,600
  2. Local audioSample:TAudioSample = CreateAudioSample(44100, 441000, SF_MONO16LE)
  3. For Local i%=0 To 44100-1
  4.         AudioSample.samples[i]=Sin(i)*1000.0
  5. Next
  6.  
...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
  1. SuperStrict
  2. Framework Brl.StandardIO
  3. Import Brl.Audio
  4. Import Brl.FreeAudioAudio
  5.  
  6. Import Brl.GLMax2D
  7. Import Brl.Bank
  8. Import random.xoshiro
  9.  
  10. 'open window
  11. Graphics 800, 600
  12. SetAudioDriver("FreeAudio")
  13.  
  14.  
  15. 'mono sound
  16. rem
  17. Local AudioSample:TAudioSample = CreateAudioSample(44100, 44100, SF_MONO16LE)
  18. For Local i%=0 To 44100-1
  19.         AudioSample.samples[i] = Sin(i)*1000.0
  20. Next
  21. Local sound:TSound = LoadSound(AudioSample)
  22. PlaySound Sound
  23. endrem
  24.  
  25.  
  26. Local format:Int = SF_MONO16LE 'SF_STEREO16LE
  27. Local bits:Int = 16
  28. Local freq:Int = 44100
  29. Local channels:Int = 2
  30. 'Local bufferLength:Int = 1024
  31. Local bufferLength:Int = 44100
  32. Local audioBufferBank:TBank = New TBank
  33. audioBufferBank.Resize( bufferLength )
  34.  
  35. Local audioSample:TAudioSample = CreateStaticAudioSample(audioBufferBank.Lock(), bufferLength, freq, format)
  36. Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length, bits, channels, freq, audioSample.samples, $80000000 )
  37. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  38.  
  39. 'change sound after creation
  40. 'For Local i:int = 0 Until audioBufferBank.capacity()
  41. '       AudioSample.samples[i] = Sin(i)*1000.0
  42. 'Next
  43.  
  44. 'or do it by manipulating the memory, not the "samples"
  45. For Local i:int = 0 Until audioBufferBank.capacity()
  46.         audioBufferBank.PokeByte(size_t(i), int(Sin(i)*1000.0))
  47. Next
  48.  
  49.  
  50.  
  51. PlaySound sound
  52.  
  53. Repeat
  54.         Cls
  55.  
  56.         Flip
  57. Until KeyHit(KEY_ESCAPE) Or AppTerminate()
  58.  


Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #17 on: March 23, 2021, 14:54:43 »
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
  1. SuperStrict
  2. Framework Brl.StandardIO
  3. Import Brl.Audio
  4. Import Brl.FreeAudioAudio
  5.  
  6. Import Brl.GLMax2D
  7. Import Brl.Bank
  8. Import random.xoshiro 'I prefer that over brl.RandomDefault
  9.  
  10. 'open window
  11. Graphics 800, 600
  12. SetAudioDriver("FreeAudio")
  13.  
  14.  
  15.  
  16. Local format:Int = SF_MONO16LE 'SF_STEREO16LE
  17. Local bits:Int = 16
  18. Local freq:Int = 44100
  19. Local channels:Int = 2
  20. 'Local bufferLength:Int = 1024
  21. Local bufferLength:Int = 44100
  22. Local audioBufferBank:TBank = New TBank
  23. audioBufferBank.Resize( bufferLength )
  24.  
  25. Local audioSample:TAudioSample = CreateStaticAudioSample(audioBufferBank.Lock(), bufferLength, freq, format)
  26. Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length, bits, channels, freq, audioSample.samples, $80000000 )
  27. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  28.  
  29. FillSoundBuffer(audioBufferBank)
  30.  
  31.  
  32. PlaySound sound
  33.  
  34. Repeat
  35.         Cls
  36.         DrawText("SPACE for random sound data", 0,0)
  37.         If KeyHit(KEY_SPACE) Then FillSoundBuffer(audioBufferBank)
  38.         Flip
  39. Until KeyHit(KEY_ESCAPE) Or AppTerminate()
  40.  
  41.  
  42.  
  43. Function FillSoundBuffer(bank:TBank)
  44.         Local tone:int = Rand(0,1000)
  45.         'or do it by manipulating the memory, not the "samples"
  46.         For Local i:int = 0 Until bank.capacity()
  47.                 bank.PokeByte(size_t(i), int(Sin(i)* tone))
  48.         Next
  49. End Function
  50.  

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #18 on: March 23, 2021, 15:30:06 »
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
  1. Local format:Int = SF_STEREO16LE
  2. Local bits:Int = 16
  3. Local freq:Int = 44100
  4. Local channels:Int = 2
  5. Local bufferLength:Int = 1024 * 4 '4kb buffer
  6. Local audioBufferBank:TBank = New TBank
  7. audioBufferBank.Resize( bufferLength )
  8.  
  9. Local audioSample:TAudioSample = CreateStaticAudioSample(audioBufferBank.Lock(), bufferLength, freq, format)
  10. Local fa_sound:Byte Ptr = fa_CreateSound( audioSample.length / 32, bits, channels, freq, audioSample.samples, $80000000 )
  11. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  12.  
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
« Last Edit: March 23, 2021, 15:39:16 by Derron »

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #19 on: March 23, 2021, 16:10:19 »
You seem to be able to skip "TAudioSample" ... yet I do not know how to get rid of the "gap".

Code: BlitzMax
  1. Local format:Int = SF_STEREO16LE
  2. Local bits:Int = 16
  3. Local freq:Int = 44100
  4. Local channels:Int = 2
  5. Local sampleCount:Int = 1000
  6. Local sampleSize:Int = 8
  7. Local sampleData:TBank = CreateBank(sampleCount * sampleSize)
  8.  
  9. Local fa_sound:Byte Ptr = fa_CreateSound( int(sampleData.size()), bits, channels, freq, sampleData.Lock(), $80000000 )
  10. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null )
  11.  
  12. FillSoundBuffer(sampleData)
  13.  


And you might even be able to skip the "TSound" too
Code: BlitzMax
  1. Local format:Int = SF_STEREO16LE
  2. Local bits:Int = 16
  3. Local freq:Int = 44100
  4. Local channels:Int = 2
  5. Local sampleCount:Int = 1000
  6. Local sampleSize:Int = 8     'think that equals  BytesPerSample[format] * channels
  7. Local sampleData:TBank = CreateBank(sampleCount * sampleSize)
  8.  
  9. Local fa_sound:Byte Ptr = fa_CreateSound( int(sampleData.size()), bits, channels, freq, sampleData.Lock(), $80000000 )
  10. 'start playing
  11. Local fa_channel:Int = fa_PlaySound( fa_sound,  0, 0)
  12. 'in case you need the TChannel (for volume adjustment etc)
  13. 'Local channel:TChannel = TFreeAudioChannel.CreateWithChannel( fa_channel )
  14.  
  15. FillSoundBuffer(sampleData)
  16.  

Edit - maybe "length" is "size of block divided by bytersPerSample" ?
Code: BlitzMax
  1. Local fa_sound:Byte Ptr = fa_CreateSound( int(sampleData.size() / BytesPerSample[format]), bits, channels, freq, sampleData.Lock(), $80000000 )
  2.  

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
« Last Edit: March 23, 2021, 16:27:17 by Derron »

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Re:doing it with FreeAudio
« Reply #20 on: March 24, 2021, 02:08:13 »
I took your  code and added some stuff here and there:.....
Code: BlitzMax
  1. SuperStrict
  2. Framework Brl.StandardIO
  3. Import Brl.Audio
  4. Import Brl.FreeAudioAudio
  5.  
  6. Import Brl.GLMax2D
  7. Import Brl.Bank
  8.  
  9.  
  10. 'open window
  11. Graphics 800, 600
  12. SetAudioDriver("FreeAudio")
  13. .....
  14.  

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?
Code: [Select]
    '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
  1. SuperStrict
  2. Graphics 800, 600
  3. SetAudioDriver("FreeAudio")
  4.  
  5.  
  6. Global Vorlage:TAudioSample=LoadAudioSample("test.ogg")
  7.  
  8. Local audioSample:TAudioSample = CreateAudioSample(8820, 22050, SF_MONO16LE)
  9.  
  10. Local fa_sound:Byte Ptr = fa_CreateSound( 8820, 16, 1, 22050, audioSample.samples, $80000000 )
  11. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  12.  
  13.  
  14. Global Zeit%=MilliSecs()
  15. PlaySound sound
  16. Zeit=MilliSecs()+50
  17. Local Zeiger%=0
  18. Global lfd%=0
  19. Repeat
  20.         Cls
  21.         If Zeit<MilliSecs()
  22.                 zeit=zeit+20
  23.                 Print Zeiger
  24.                 For Local i:Int = 0 Until 882
  25.                         AudioSample.samples[zeiger+i] = Vorlage.Samples[lfd]
  26.                         lfd=lfd+1
  27.                 Next
  28.                 Zeiger=(Zeiger +882) Mod 17640
  29.         EndIf
  30.         Flip 0
  31. 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.
« Last Edit: March 24, 2021, 02:54:00 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #21 on: March 24, 2021, 07:13:04 »
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

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Reducing latency to 40msec
« Reply #22 on: March 24, 2021, 12:12:56 »
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
  1. SuperStrict
  2. Graphics 800, 600
  3. SetAudioDriver("FreeAudio")
  4. Global WritePointer:Int, ReadPointer:Int, WriteTime:Int
  5. Global BITS:Int, CHANNELS:Int, BUFFER_SIZE:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int
  6.  
  7. Const HERTZ:Int=22050
  8.  
  9. Const CHUNK_TIME:Int = 20 'msec
  10. Const FORMAT:Int     = SF_MONO16LE
  11.  
  12. Select FORMAT
  13.         Case SF_MONO16LE
  14.                 BITS:Int=16
  15.                 CHANNELS:Int=1
  16.                 CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
  17.                 BUFFER_SAMPLES = CHUNK_SIZE*2
  18.                 BUFFER_SIZE    = BUFFER_SAMPLES * CHANNELS * BITS/8
  19.         Default
  20.                 End
  21. End Select
  22.  
  23. ' derron's part:
  24. Global Source:TAudioSample=LoadAudioSample("test.ogg")
  25. Global Buffer:TAudioSample = CreateAudioSample(BUFFER_SIZE/2, HERTZ, FORMAT)
  26. Local fa_sound:Byte Ptr = fa_CreateSound( BUFFER_SIZE/2, BITS, CHANNELS, HERTZ, Buffer.Samples, $80000000 )
  27. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  28.  
  29. ' now put 40msec Latency into the Buffer:
  30. SendOneChunk()
  31. SendOneChunk()
  32.  
  33. 'already start playback with an empty buffer
  34. PlaySound sound
  35. WriteTime=MilliSecs()
  36.  
  37. Repeat
  38.         Cls
  39.         If WriteTime<MilliSecs()
  40.                 ' this cares about really 20msec timing:
  41.                 WriteTime=WriteTime + CHUNK_TIME
  42.                 Print "Write to " + WritePointer + " at time: " + WriteTime
  43.                 SendOneChunk
  44.         EndIf
  45.         Flip 0
  46. Until AppTerminate()
  47. End
  48.  
  49. Function SendOneChunk()
  50.                 ' put a amount of sampls into the buffer:
  51.                 For Local i:Int = 0 Until CHUNK_SIZE
  52.                                 Buffer.samples[WritePointer+i] = Source.Samples[ReadPointer]
  53.                                 ReadPointer=ReadPointer+1
  54.                 Next
  55.                 WritePointer=(WritePointer + CHUNK_SIZE) Mod BUFFER_SIZE
  56. End Function
  57.      
« Last Edit: March 25, 2021, 13:24:44 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #23 on: March 24, 2021, 15:30:05 »
why are you only able to reduce latency to 40ms?

Can you explain that a bit ?

the whole "buffer refill loop":
Code: [Select]
        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:
Code: [Select]
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

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Latency below 40msec
« Reply #24 on: March 24, 2021, 16:03:02 »
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.
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #25 on: March 24, 2021, 16:20:37 »
Thanks for the elaborative explanation.


bye
Ron

Offline Scaremonger

  • Full Member
  • ***
  • Posts: 233
    • ITSpeedway - Ramblings of a geek!
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #26 on: March 24, 2021, 19:45:37 »
So will the ringbuffer continue to play silence when the buffer is empty or will it simply pause playback until the buffer is refilled?

Offline Derron

  • Hero Member
  • *****
  • Posts: 3667
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #27 on: March 24, 2021, 21:27:03 »
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

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #28 on: March 25, 2021, 01:13:45 »
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
  1. SuperStrict
  2.  
  3. Graphics 800, 600
  4. SetAudioDriver("FreeAudio")
  5. Global WritePointer:Int, ReadPointer:Int, WriteTime:Int
  6. Global BITS:Int, CHANNELS:Int, BUFFER_SIZE:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int, FORMAT:Int
  7. Global Volume#
  8. Const HERTZ:Int=22050
  9.  
  10. Const CHUNK_TIME:Int = 20 'msec  (=441 samples)
  11.  
  12. Global Source:TAudioSample=LoadAudioSample("testc.ogg")
  13. FORMAT  = Source.Format
  14. 'FORMAT = SF_STEREO16LE
  15.  
  16. Select FORMAT
  17.         Case SF_MONO16LE
  18.                 BITS:Int=16
  19.                 CHANNELS:Int=1
  20.                 CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
  21.                 BUFFER_SAMPLES = HERTZ * CHUNK_TIME            * BITS/8/1000*8
  22.                 BUFFER_SIZE    = BUFFER_SAMPLES * 2 * CHANNELS
  23.         Case SF_STEREO16LE
  24.                 BITS:Int=16
  25.                 CHANNELS:Int=2
  26.                 CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
  27.                 BUFFER_SAMPLES = HERTZ * CHUNK_TIME            * BITS/8/1000 *8
  28.                 BUFFER_SIZE    = BUFFER_SAMPLES * 2 *CHANNELS
  29.         Default
  30.                 Notify "Audio format not supported"
  31.                 End
  32. End Select
  33.  
  34. Global Lesen:TStream= CreateRamStream(Source.Samples,Source.length * CHANNELS * BITS/8,True,0)
  35.  
  36. Print "Chunksize=" + Chunk_size
  37. Print "buffersize=" + BUFFER_SIZE
  38. Print "source length=" + Source.length  + " format" + source.format + " in samples="  + Source.length * CHANNELS * BITS/8
  39.  
  40.  
  41. ' derron's part:
  42. Global Buffer:TAudioSample = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  43. Local fa_sound:Byte Ptr = fa_CreateSound( BUFFER_SAMPLES, BITS, CHANNELS, HERTZ, Buffer.Samples, $80000000 )
  44. Local sound:TSound = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  45.  
  46.  
  47. Global Schreiben:TStream= CreateRamStream(Buffer.Samples,Buffer_size,True,True)
  48.  
  49.  
  50. ' now put 40msec Latency into the Buffer:
  51. SendOneChunk()
  52. SendOneChunk()
  53.  
  54. 'already start playback with an empty buffer
  55. PlaySound sound
  56. WriteTime=MilliSecs()
  57. Local StartTime%=MilliSecs()
  58. Local z1$,z2$,z3$
  59. Repeat
  60.         Cls
  61.         DrawText "Move the mouse to pan between left and right",100,100
  62.         DrawText "LEFT",10,300
  63.         DrawText "MIDDLE",300,300
  64.         DrawText "RIGHT",700,300
  65.         DrawTabText "Write to:", WritePointer  , 300,400
  66.         DrawTabText "at time:", (WriteTime-StartTime) , 300,430
  67.         DrawTabText "read from:", ReadPointer , 300,460
  68.        
  69.         If WriteTime<MilliSecs()
  70.                 ' this cares about really 20msec timing:
  71.                 WriteTime=WriteTime + CHUNK_TIME
  72.                 'Print "Write to " + WritePointer + " at time: " + (WriteTime-StartTime) +" " + Buffer.Length + " " + buffer_size
  73.                 SendOneChunk
  74.         EndIf
  75.         volume=1-MouseX()/800.0
  76.         Flip 0
  77. Until AppTerminate()
  78. End
  79.  
  80.  
  81. Function DrawTabText(t1$,t2$,X%,y%)
  82.         DrawText t1, X-TextWidth(T1)-70,Y
  83.         DrawText t2, X-TextWidth(T2),Y
  84. End Function
  85.  
  86. Function SendOneChunk()
  87.                 ' put a amount of samples into the buffer:
  88.                 Local Zeit%=MilliSecs()
  89.                                 Lesen.Seek(ReadPointer)
  90.                                 Schreiben.Seek(WritePointer)
  91.                 For Local i:Int = 0 Until CHUNK_SIZE Step 2
  92.                                 Local value:Int=Lesen.ReadShort()
  93.                                 If value>32768
  94.                                         value=value-65535
  95.                                 EndIf
  96.                                
  97.                                 ' mouse volume:
  98.                                 If i Mod 4=0 Or FORMAT=SF_MONO16LE
  99.                                         value=value*volume
  100.                                 Else
  101.                                         value=value*(1-volume)                         
  102.                                 EndIf
  103.                                 If value<0
  104.                                         value=value+65535
  105.                                 EndIf
  106.                                
  107.                                 Schreiben.WriteShort(value)
  108.                                 ReadPointer=ReadPointer+2
  109.                 Next
  110.                 WritePointer=(WritePointer + CHUNK_SIZE) Mod (BUFFER_SIZE)
  111.                 ReadPointer=ReadPointer Mod (Source.length * CHANNELS * BITS/8)
  112. End Function
  113.      

« Last Edit: March 25, 2021, 03:40:18 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Testing latency of different Audio Drivers
« Reply #29 on: March 25, 2021, 09:55:07 »
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
  1. Graphics 800,600
  2. Local Drivername$[9]
  3. Local Nr%
  4. For Local a:String = EachIn AudioDrivers()
  5.         Print "Use Nr=" + Nr + " to use  Driver: " + a
  6.         Drivername[Nr]=a
  7.         Nr=Nr+1
  8. Next
  9.  
  10. '*** Select driver here:***********
  11.  
  12. Nr=1
  13.  
  14. '*********************************
  15. SetAudioDriver DriverName[Nr]
  16.  
  17. Global Test:TSound=LoadSound("Test_5_Clicks.ogg",SOUND_LOOP)
  18. PlaySound Test
  19. Global StartTime%=MilliSecs()
  20. Global LastLatency%, Avarage%, Rounds%
  21. Repeat
  22.         Cls
  23.         SetColor 255,255,255
  24.         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
  25.         DrawText "Checking audio driver: " + DriverName[Nr] ,200,70
  26.         DrawText "1. Listen to the ticks",200,90
  27.         DrawText "2. Feel the timing...",200,110
  28.         DrawText "3. Exactly at the 5th push LEFT MOUSE or the Key [X]",200,130
  29.         DrawText "RESULTS:",200,200
  30.         DrawText "   Last Lastency = " + LastLatency + " msec",200,230
  31.         If Rounds>0
  32.                 DrawText "Avarage Lastency = " + Int(Avarage/rounds)+ " msec",200,250
  33.         EndIf
  34.         DrawText "click here:",200,370
  35.         DrawText "Press [R] to reset values",500,570
  36.         DrawRect 200,400,100,100
  37.         SetColor 1,1,1
  38.         DrawRect 201,401,98,98
  39.         If MouseDown(1)
  40.                 Print latency(MilliSecs()-StartTime)
  41.                 Repeat         
  42.                 Until MouseDown(1)=False
  43.         EndIf
  44.        
  45.         If KeyDown(KEY_X)
  46.                 Print latency(MilliSecs()-StartTime)
  47.                 Repeat         
  48.                 Until KeyDown(KEY_X)=0
  49.         ElseIf KeyDown(KEY_R)
  50.                 Rounds=0
  51.                 Avarage=0
  52.                 LastLatency=0
  53.                 Repeat         
  54.                 Until KeyDown(KEY_X)=0
  55.         EndIf
  56.         Flip 0
  57. Until AppTerminate()
  58.  
  59. Function Latency%(Value)
  60.         value=value-4000
  61.         value=value Mod 6000
  62.         If value>1000
  63.                 value=value-6000
  64.         EndIf
  65.         LastLatency=value
  66.         Rounds=Rounds+1
  67.         Avarage=Avarage+value
  68.         Return value
  69. End Function
  70.  
  71.  
« Last Edit: March 25, 2021, 10:00:02 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal