PortAudio module for godot
module for godot
*1) only applies when utilizing c++ audio callback, not GDScript bindings
cd modules
git submodule add https://github.com/sebastian-heinz/portaudio.git
git submodule update --init --recursive
Edit the SCsub file and comment / uncomment the desired host apis in use_host_api = [
.
When building godot it will check if the host api is supported for the platform (windows, linux or osx) and available inside the use_host_api
-array. Only then the host api will be available.
Driver | windows | x11 | osx |
---|---|---|---|
ASIO | Working (*1) | Not Tested | Not Available |
DSOUND | Working | Not Available | Not Available |
WASAPI | Working (*2) | Not Available | Not Available |
WDM/KS | Working | Not Available | Not Available |
WMME | Error | Not Available | Not Available |
JACK | Not Available | Not Tested | Not Available |
ALSA | Not Available | Not Tested | Not Available |
ASIHPI | Not Available | Not Tested | Not Available |
COREAUDIO | Not Available | Not Available | Working |
OSS | Not Available | Not Tested | Not Available |
1) requires that the enduser has asio drivers installed (ex. ASIO4ALL) 2) if used godots wasapi driver will be disabled
PortAudio
is a singleton class, its purpose is to provide wrapper methods for all PortAudio calls (PA_*) with a godot type friendly interface. For direct access from anywhereA PortAudioTestNode
exists, simply add it to a scene via the editor and it will enumerate your host apis and devices and print them out:
extends Node
class_name ToneGenerator
# Member variables
var sample_rate = 8000
var frequenzy_hz = 40
var duration = 1
# Constants
const SAMPLE_SIZE = 4 # float
func _ready():
start_port_audio()
return
func start_port_audio():
var samples = duration * sample_rate
var device_index = PortAudio.get_default_output_device()
var out_p = PortAudioStreamParameter.new()
out_p.set_device_index(device_index)
out_p.set_channel_count(2)
var stream = PortAudioStream.new()
stream.set_output_stream_parameter(out_p)
stream.set_sample_format(PortAudioStreamParameter.FLOAT_32)
stream.set_frames_per_buffer(samples * SAMPLE_SIZE)
var audio_callback = funcref(self, "audio_callback")
var user_data = samples
var err = PortAudio.open_stream(stream, audio_callback, user_data)
if(err != PortAudio.NO_ERROR):
push_error("open_stream: %s" % err)
err = PortAudio.start_stream(stream)
if(err != PortAudio.NO_ERROR):
push_error("start_stream: %s" % err)
return
# Audio Callback
func audio_callback(data : PortAudioCallbackData):
var buff = data.get_output_buffer()
var samples = data.get_user_data()
for i in range (samples):
var sample : float = sin(2 * PI * float(i) / ( sample_rate / frequenzy_hz))
buff.put_float(sample)
return PortAudio.CONTINUE
This module will add PortAudio to the include path. It allows to work with PortAudio s library directly:
#include <portaudio.h>
For tutorials on how to use PortAudio have a look at the official documentation: http://portaudio.com/docs/v19-doxydocs/initializing_portaudio.html
Please refer to https://app.assembla.com/wiki/show/portaudio/Tips_Callbacks for a better understand about the delicate callback function.
Especially:
Regarding crossing language boundaries such as calling Java or Lua:
In general it should be avoided. But, in fact, Lua has a bounded time GC so,
like the Supercollider language, it could be used in a PortAudio callback
so long as you make sure it doesn’t violate the points I made above:
i.e. avoid calling the OS level memory allocator, file I/O, or doing other things
which violate the above in your Lua user functions. .
That said, running Lua in a PortAudio callback is definitely at the experimental end of the spectrum.
Exposing PortAudio to GDScript will have some performance overhead and introduces additional audio latency. If you are looking for low latency it would be best to utilzie the CallbackFunction in C++. To get a better understanding of how long the callback took, the duration in μs (Microsecond) can be obtained from the callback data PortAudioCallbackData::get_last_call_duration()
.
The callback provides a frames_per_buffer
-variable. This does not represent bytes. Depending on the format (FLOAT_32 = 4bytes, INT_16 = 2bytes) and channels the buffer size can be calculated.
bytes_per_channel = (FLOAT_32 = 4 bytes) (INT_16 = 2byte) (INT_8 = 1byte)
buffer_size = frames_per_buffer * channels * bytes_per_channel
Ensure that each callback the buffer is filled up correctly or it could result in slow and crackling audio. The same also applies when utilizing blocking mode via write()
.
If the execution time of the callback function is longer than the playback data provided to the buffer the audio might also become slow and crackling.
To calculate the playback duration of the buffer the requested frames can be divided by the sample rate.
time_request_seconds = frames_per_buffer / sample_rate
The execution time of the audio loop has to be faster than this time.
The return value of the callback indicates if it should continue to be called or it can be signaled to stop.
C++:
enum PortAudioCallbackResult {
CONTINUE = 0,
COMPLETE = 1,
ABORT = 2,
};
GDScript:
PortAudio.CONTINUE
PortAudio.COMPLETE
PortAudio.ABORT
4.x
2.0+
(optional)Godot:
https://github.com/godotengine/godot
Portaudio:
https://app.assembla.com/spaces/portaudio/git/source
http://www.portaudio.com/
https://github.com/PortAudio/portaudio
ASIO SDK 2.0:
https://www.steinberg.net/en/company/developers.html
Asio4All Driver:
http://www.asio4all.org/