September 15, 2019, 07:22:35 AM

Author Topic: Easy Blitzmax Streaming realtime audio with Raylib  (Read 548 times)

Offline GW

  • Full Member
  • ***
  • Posts: 169
Easy Blitzmax Streaming realtime audio with Raylib
« on: June 20, 2019, 07:14:10 AM »
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
Code: [Select]
#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.
Code: [Select]
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:
Code: [Select]
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


Offline MikeHart

  • Hero Member
  • *****
  • Posts: 598
  • Cerberus-X Dev Team
    • Cerberus X
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #1 on: June 20, 2019, 08:46:23 AM »
Is that vanilla BMAX or NG?

Offline GW

  • Full Member
  • ***
  • Posts: 169
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #2 on: June 20, 2019, 09:14:49 AM »
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.

Offline Henri

  • Full Member
  • ***
  • Posts: 208
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #3 on: June 20, 2019, 09:24:36 AM »
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

Offline GW

  • Full Member
  • ***
  • Posts: 169
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #4 on: June 20, 2019, 10:04:08 AM »
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.

Offline Derron

  • Hero Member
  • *****
  • Posts: 2399
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #5 on: June 20, 2019, 11:37:26 AM »
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

Offline Brucey

  • Jr. Member
  • **
  • Posts: 86
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #6 on: June 20, 2019, 05:04:05 PM »
I've been looking at a direct miniAudio implementation, but haven't had the time recently to work on it.

Offline GW

  • Full Member
  • ***
  • Posts: 169
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #7 on: June 20, 2019, 05:49:47 PM »
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.

Offline Derron

  • Hero Member
  • *****
  • Posts: 2399
Re: Easy Blitzmax Streaming realtime audio with Raylib
« Reply #8 on: June 20, 2019, 06:36:07 PM »
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