Easy Blitzmax Streaming realtime audio with Raylib

Started by GW, June 20, 2019, 07:14:10

Previous topic - Next topic

GW

When it comes to Blitzmax and audio, There are lot of audio drivers/and libraries such as Directsound, FreeAudio, OpenAL, SoLoud, SDL, MaxMod etc.

But of those, only OpenAL has a method of streaming raw audio in realtime without the programmer  pulling thier hair out.  One downside of OpenAL is that it requires shipping a 32bit or 64bit dll (both named OpenAL32.dll   :( )  Raylib has no external dependencies and is backed by the WASAPI api for very low latency.

Enter Raylib.  It's a games library with simple C interface and blitz-like command set.  That simple api carries forward into the audio portion as well and the author has done a great job putting a simple api on top of MiniAudio, a cross-platform low-level audio library.

How to do it:
Download the Raylib source at www.raylib.com
Open the file 'raudio.c' and add the line #define RAUDIO_STANDALONE This sets the audio library to standalone and not have any dependencies with the rest of raylib.
Optional: Change 'MAX_STREAM_BUFFERS' to 3 and 'AUDIO_BUFFER_SIZE' to 2048.  If you have a slow computer, you may want to leave the defaults.

Next add the library and function definitions to blitzmax.
Import "Raylib\src\raudio.c"

Extern
    Function IsAudioDeviceReady%()
    Function InitAudioDevice()
    Function CloseAudioDevice()
    Function IsAudioBufferProcessed:Int(stream:AudioStream)                '// Check If any audio stream buffers requires refill
    Function PlayAudioStream(stream:AudioStream)                        '// Play audio stream
    Function InitAudioStream:AudioStream(sampleRate:UInt, sampleSize:UInt, channels:UInt)            '// Init audio stream (To stream raw audio pcm data)
    Function UpdateAudioStream(stream:AudioStream, Data:Byte Ptr, samplesCount:Int) '// Update audio stream buffers with data
    Function SetMasterVolume(volume:Float)
EndExtern



Steps: 
Start the audio device.
Create the audio stream that will be played.
Create a buffer that you will write your data too.
Start playing the stream.
During the program loop check if the sound card needs more data. When it does, write your audio data to the buffer you created and tell the library to integrate the new data.
That's it!


Example:
Rem
    Audio streaming with 'Raylib Audio' Example
    2019 A.Woodard "GW"
Endrem

SuperStrict
Framework brl.basic
Import brl.glmax2d

Import "C:\Dev\temp\Raylib\src\raudio.c"

Extern
    Function IsAudioDeviceReady%()
    Function InitAudioDevice()
    Function CloseAudioDevice()
    Function IsAudioBufferProcessed:Int(stream:AudioStream)                '// Check If any audio stream buffers requires refill
    Function PlayAudioStream(stream:AudioStream)                        '// Play audio stream
    Function InitAudioStream:AudioStream(sampleRate:UInt, sampleSize:UInt, channels:UInt)            '// Init audio stream (To stream raw audio pcm data)
    Function UpdateAudioStream(stream:AudioStream, Data:Byte Ptr, samplesCount:Int) '// Update audio stream buffers with data
    Function SetMasterVolume(volume:Float)
EndExtern

Struct AudioStream
    Field sampleRate:UInt      '// Frequency (samples per second)
    Field sampleSize:UInt      '// Bit depth (bits per sample): 8, 16, 32 (24 Not supported)
    Field channels:UInt        '// Number of channels (1-mono, 2-stereo)
    Field audioBuffer:Byte Ptr '// Pointer To internal data used by the audio system.
    Field format%              '// Audio format specifier
    Field source%         '// Audio source id
    Field buffers%[2]     '// Audio buffers (Double buffering)
End Struct

'--------------------------------START-----------------------------------------------
AppTitle = "RayLib realtime audio streaming"
Graphics 1024, 500
SetLineWidth(1.5)

InitAudioDevice()

If Not IsAudioDeviceReady()
    RuntimeError("No Audio")
EndIf

OnEnd(CloseAudioDevice)

Const BUFFERSIZE:Int = 2048
Global mystream:AudioStream = InitAudioStream(44100, 32, 1) ;    '// Create the audio stream
Global a_writebuff:Float[BUFFERSIZE]                        '// create a buffer and a pointer to the buffer
Global writeBuf:Float Ptr = VarPtr a_writebuff[0]
Global Time:Float = 0

Const TWOPI:Double = Pi * 2


PlayAudioStream(mystream)    '// Start playing

SetMasterVolume(0.25)    '// easy on the ears

Print "Starting!"

Const FRQ:Float = 107.66
Global phase:Float

While True
    If KeyHit(KEY_ESCAPE) Then End
    Local buffproc:Int = IsAudioBufferProcessed(mystream)    '// if the sound card wants more data this is true
    If buffproc Then
        For Local i:Int = 0 Until BUFFERSIZE
            '// Get waveform
            phase:+FRQ * (TWOPI / mystream.samplerate)
            If phase > Pi Then phase:-TWOPI
            Local x:Float = sw(FRQ, phase)    'calc audio data
            '//Mod w/mouse
            x:*Float(MouseY()) / GraphicsHeight()    '// change volume by MouseY
            Local yy:Float = Float(MouseX()) / GraphicsWidth()    '// change gain by MouseX
            If Abs(x * (yy)) > 0.5 Then x = Sine(-x)    '//fold distortion
           
            '//write to buffer
            a_writebuff[i] = x * 0.25
        Next
        UpdateAudioStream(mystream, writeBuf, BUFFERSIZE)    '// tell raylib to use the new buffer
        Draw
    EndIf
Wend

'---------------------------------------------------------------------------------------------------------------------------     
Function Draw()
    Local y:Float
    Local y2:Float
    Local t:Int = 0
    For Local i:Int = 0 Until BUFFERSIZE Step 2
        y = y2
        y2 = 250 + (a_writebuff[i] * 500)
        DrawLine(i / 2, y, i / 2, y2)
    Next
    Flip
    Cls
    Time:+1
End Function
'---------------------------------------------------------------------------------------------------------------------------
Function Sine:Double(x:Double)
    Return Sin(x / (Pi / 180.0))
End Function
'---------------------------------------------------------------------------------------------------------------------------
Function sw:Float(f:Float, ph:Float)
    Local i:Float = 1, x:Float
    While (f * i) < (4000 * (1.1 + Sin(Time * 4)))
        x:+(Sine(ph * i) * (1.0 / i))
        i:+1 + Abs(Sine(Time / 10))
    Wend
    Return x
End Function
'---------------------------------------------------------------------------------------------------------------------------



Download Sourcecode with compiled windows example


MikeHart


GW

Quote from: MikeHart on June 20, 2019, 08:46:23
Is that vanilla BMAX or NG?

It's Bmax NG.
It should work on vanilla after changing all references of 'AudioStream' to 'byte ptr',  but I tried it and received an exception on the call to InitAudioStream. So it might need little more work under the hood.  Maybe it's related to needing an unsigned int and bmax only passing a signed int.

Henri

Hi GW,

nice example and presentation. Good job 8)

Upon further examination, Raylib seems like a minimalist all-in-one solution for simple game development, where the audio portion is a wrapper for underline miniaudio library.
Miniaudio itself seems like a useful library with support for playback, decode, streaming and capture. I think Bmax is missing a comprehensive audio package at the moment, that is up-to-date, fully supported with the ability to playback and record.

Also, I like to standalone approach, with no dependencies, and a supportive license. Very Blitzlike :-)

-Henri
- Got 01100011 problems, but the bit ain't 00000001

GW

Quote from: Henri on June 20, 2019, 09:24:36
Hi GW,

nice example and presentation. Good job 8)

Upon further examination, Raylib seems like a minimalist all-in-one solution for simple game development, where the audio portion is a wrapper for underline miniaudio library.
Miniaudio itself seems like a useful library with support for playback, decode, streaming and capture. I think Bmax is missing a comprehensive audio package at the moment, that is up-to-date, fully supported with the ability to playback and record.

Also, I like to standalone approach, with no dependencies, and a supportive license. Very Blitzlike :-)

-Henri
Yeah, I tried to get streaming to work with all of the other bmax libraries, but either they don't support it. no documentation or my c++ skills are too weak.  Raylib was perfect fit.

It took a very short time to refactor my chiptune tracker away from openAL to raylib and it even feels like latency reduced too.
It raylib ever implemented shadowmapping I would seriously consider doing a BMax port of the whole thing.

Derron

https://github.com/bmx-ng/maxmod2.mod
-> audio streaming (if you talk about ogg/mp3 ... not "input") depending on the rtAudio library


https://github.com/GWRon/Dig
-> "SoundManager" with Audiostreams either using "FreeAudio" (shipped with BlitzMax) or above's maxmod2.mod


I am currently overhauling the "SoundManager" and already cut away a lot of stuff but still having issues - eg "legacy blitzmax" + "debug build" leads to crashes. Somehow the buffer I need to pass to "FreeAudio" (done by skidracer at that time) is not sized properly.

Benefit is: the original BlitzMax-Audio-commands would still work ("PlaySound()", "CueChannel()"). So best would be to have "TAudio" take care of updating its content on its own. So if you created an audiostream then the audio file should register itself to some generic stream manager. The stream manager I created uses:
- BlitzMax threading if built "threaded" (default on NG, legacy needs "-h" param)
- C threading for non-threaded builds (so legacy with out threading enabled). The C-thread just calls a function of the stream manager so that it can get regularily updated.

This threading is required as you else will have distorted audio if you eg load a big file in one "chunk" or if you process a big amount of data in one loop (buffers will run empty - or buffers will not get fresh data and play the old audio again and again).



I am with all of you: the current audio situation in BlitzMax is ... "outdated" (compared to other programming-toolkits).
Somewhere I wrote about "miniaudio" and blitzmax already but I just cannot find it.

Using a 3rd party library means to be able to rely on their bugfixes - once the audio wrapper was done.


bye
Ron

Brucey

I've been looking at a direct miniAudio implementation, but haven't had the time recently to work on it.

GW

Quote from: Brucey on June 20, 2019, 17:04:05
I've been looking at a direct miniAudio implementation, but haven't had the time recently to work on it.

I'd be willing to help, up to the limit of my lame c skills.  If you could take the time to write a tutorial about how you go about making modules, that would very helpful to other who would help pick up the slack. It's often hard from the source to see the logic behind how things are done.

Derron

I thought of having found first clues on how Brucey tackles modules creation but then - some weeks ago, he started to refine the way of doing (file structure of the steam.mod thing!).

Once the module is "there" I sometimes am able to extend it a tiny bit here and there but thats it... reached GetSkillLevelMax(2019) :-)


bye
Ron

Baggey

Just Found this thread.

Are you or would you be interested in having a crack at creating a C64 SID player in BlitzmaxNG! could be FUN!  8)

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on Acer 24" . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, Enterprise 128K, The SID chip. Im Misunderstood!

mainsworthy

#10
GW , I have been following this tutorial, and adapted the code, I have it playing RAW data and can speed it up and slow it down. But I cant stop the channel or sound , How do I do I?

here is a pic of what Im doing and I have put you in all the credits and help button





https://agamecreator.itch.io/the-search-for-extra-terrestrials-program

this is the version i was following. both really but went for pure blitz, Im new to audio so I really am trying very hard, stopchannel and pausechannel dont work, I dont think they will because there not part of the type structure.

what im trying to do, is close it on a button, then start it again on a button, i want to switch it on off , I can turn the display off but the sound continues.

https://www.syntaxbomb.com/audio/frequency-modulation-sound/msg347054650/#msg347054650



GW

The simplest way to stop the sound output is to just write zeros into the audio buffer when you want silence. 

mainsworthy

Quote from: GW on May 07, 2022, 11:21:20
The simplest way to stop the sound output is to just write zeros into the audio buffer when you want silence.

Thankyou! I will do it now.

mainsworthy

#13
Quote from: GW on May 07, 2022, 11:21:20
The simplest way to stop the sound output is to just write zeros into the audio buffer when you want silence.

I am really not good at this sound stuff, can you answer just a couple of things GW?   first I am taking the2 channel  16bit sample 4 bytes

ay2 = 0 + (modstore[44+aut]  +  modstore[44+aut+1] * 256)
ay = 0 + (modstore[44+aut+2]  +  modstore[44+aut+3] * 256)    ' the 44 is the header for audio files and aut is the counter that gets steped by 4, im not using ay at the moment i am asuming ay2 is mono

and I am also taking a 1 channel 16 bit sample 2 bytes and feeding them to your sliders

CFslider.val = tw
MAslider.val = ay2
MAslider.Name$ = ""

it shows the wav and seems to work but it does not sound like music

but It does not sound like audio, is this right, I am a total noob to audio, but its very interesting, but I need to have a startpoint where I can hear a Wav file?

any help would be great?

mainsworthy

#14
OK I can now here is Wav, but its quite distorted, I have set the 3 sliders with values, and I got some output, Im learning :)

No its not my wav, it seems to be software relating to the driver, but it is a tune of sorts, how do i use the buffer? I am putting data shorts direct into buffer prior to


buffer[Pos+f-] = data
buffer[Pos+f] = getaudio()

i just cant get a byte to sound

ok I filled the whole buffer, but I just cant tell whats playing