Skip to content

Commit

Permalink
Improve available API of SiONDriver
Browse files Browse the repository at this point in the history
Adds some validity checks for input arguments,
also fixes the volume setter which is actually
expects values in the range between 0 and 1.

Exposes additional methods which can be used
to control audio bus and panning, by providing
access to the underlying Godot types.

Closes #18.
  • Loading branch information
YuriSizov committed Jun 28, 2024
1 parent 032694d commit c1e4141
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 65 deletions.
68 changes: 60 additions & 8 deletions doc_classes/SiONDriver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,58 @@
Creates a new instance of [SiONDriver]. Prefer using this method instead of the class constructor.
Use [param buffer_size] to control the size of the generated buffer. Bigger size is more demanding on your hardware. Supported values are [code]2048[/code], [code]4096[/code], [code]8192[/code].
Use [param channel_num] to configure the number of output channels. Can only be [code]1[/code] for mono and [code]2[/code] for stereo.
Use [param sample_rate] to control the sample rate. Only [code]44100[/code] is supported at this time.
Use [param sample_rate] to control the sampling rate. Only [code]44100[/code] is supported at this time.
Use [param bitrate] to enable quantization.
</description>
</method>
<method name="get_audio_playback" qualifiers="const">
<return type="AudioStreamGeneratorPlayback" />
<description>
Returns the internal [AudioStreamGeneratorPlayback] instance. Only available during streaming (after calling [method play]).
</description>
</method>
<method name="get_audio_player" qualifiers="const">
<return type="AudioStreamPlayer" />
<description>
Returns the internal [AudioStreamPlayer] instance. The player instance can be used to change the audio bus used by the synthesizer.
</description>
</method>
<method name="get_audio_stream" qualifiers="const">
<return type="AudioStreamGenerator" />
<description>
Returns the internal [AudioStreamGenerator] instance.
</description>
</method>
<method name="get_bitrate" qualifiers="const">
<return type="float" />
<description>
Returns the bitrate value used for quantization. See also [method create].
</description>
</method>
<method name="get_buffer_length" qualifiers="const">
<return type="int" />
<description>
Returns the target size of the generated buffer. See also [method create].
</description>
</method>
<method name="get_channel_num" qualifiers="const">
<return type="int" />
<description>
Returns the number of output channels. See also [method create].
</description>
</method>
<method name="get_effector" qualifiers="const">
<return type="SiEffector" />
<description>
Returns a reference to the [SiEffector] instance. You can use it to control global effects and filters.
</description>
</method>
<method name="get_sample_rate" qualifiers="const">
<return type="float" />
<description>
Returns the sampling rate. See also [method create].
</description>
</method>
<method name="get_sequencer" qualifiers="const">
<return type="SiMMLSequencer" />
<description>
Expand All @@ -49,6 +91,12 @@
Returns a reference to the [SiOPMSoundChip] instance.
</description>
</method>
<method name="get_track_count" qualifiers="const">
<return type="int" />
<description>
Returns the number of active tracks in the sequencer.
</description>
</method>
<method name="get_version" qualifiers="static">
<return type="String" />
<description>
Expand Down Expand Up @@ -174,13 +222,6 @@
Enables or disables reporting of the beat track event. Beat events are fired at a quick pace, so handling of them can become a performance bottleneck.
</description>
</method>
<method name="set_bpm">
<return type="void" />
<param index="0" name="bpm" type="float" />
<description>
Sets the beats per minute tempo.
</description>
</method>
<method name="set_fading_event_enabled">
<return type="void" />
<param index="0" name="enabled" type="bool" />
Expand Down Expand Up @@ -209,6 +250,17 @@
</description>
</method>
</methods>
<members>
<member name="bpm" type="float" setter="set_bpm" getter="get_bpm" default="120.0">
Beats per minute, or tempo, of the output. Values between [code]1[/code] and [code]4000[/code] are allowed.
</member>
<member name="max_track_count" type="int" setter="set_max_track_count" getter="get_max_track_count" default="128">
Maximum number of tracks that can exist at the same time.
</member>
<member name="volume" type="float" setter="set_volume" getter="get_volume" default="1.0">
Base volume of the output, before fading is applied. The volume is set as a linear value between [code]0.0[/code] and [code]1[/code].
</member>
</members>
<signals>
<signal name="bpm_changed">
<param index="0" name="event" type="SiONTrackEvent" />
Expand Down
104 changes: 64 additions & 40 deletions src/sion_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "sion_driver.h"

#include <godot_cpp/classes/time.hpp>
#include <godot_cpp/core/math.hpp>
#include <godot_cpp/variant/packed_vector2_array.hpp>

#include "sion_data.h"
Expand Down Expand Up @@ -46,7 +47,7 @@ bool SiONDriver::_allow_multiple_drivers = false;

//

SiONDriver::SiONDriverJob::SiONDriverJob(String p_mml, Vector<double> p_buffer, const Ref<SiONData> &p_data, int p_channel_count, bool p_reset_effector) {
SiONDriver::SiONDriverJob::SiONDriverJob(String p_mml, Vector<double> p_buffer, const Ref<SiONData> &p_data, int p_channel_num, bool p_reset_effector) {
mml = p_mml;
buffer = p_buffer;

Expand All @@ -58,7 +59,7 @@ SiONDriver::SiONDriverJob::SiONDriverJob(String p_mml, Vector<double> p_buffer,
data = new_data;
}

channel_count = p_channel_count;
channel_num = p_channel_num;
reset_effector = p_reset_effector;
}

Expand All @@ -82,17 +83,17 @@ SiOPMWaveTable *SiONDriver::set_wave_table(int p_index, Vector<double> p_table)
return wave_table;
}

SiOPMWavePCMData *SiONDriver::set_pcm_wave(int p_index, const Variant &p_data, double p_sampling_note, int p_key_range_from, int p_key_range_to, int p_src_channel_count, int p_channel_count) {
SiOPMWavePCMData *SiONDriver::set_pcm_wave(int p_index, const Variant &p_data, double p_sampling_note, int p_key_range_from, int p_key_range_to, int p_src_channel_num, int p_channel_num) {
Ref<SiMMLVoice> pcm_voice = SiOPMRefTable::get_instance()->get_global_pcm_voice(p_index & (SiOPMRefTable::PCM_DATA_MAX - 1));
SiOPMWavePCMTable *pcm_table = Object::cast_to<SiOPMWavePCMTable>(pcm_voice->get_wave_data());
SiOPMWavePCMData *pcm_data = memnew(SiOPMWavePCMData(p_data, (int)(p_sampling_note * 64), p_src_channel_count, p_channel_count));
SiOPMWavePCMData *pcm_data = memnew(SiOPMWavePCMData(p_data, (int)(p_sampling_note * 64), p_src_channel_num, p_channel_num));

pcm_table->set_sample(pcm_data, p_key_range_from, p_key_range_to);
return pcm_data;
}

SiOPMWaveSamplerData *SiONDriver::set_sampler_wave(int p_index, const Variant &p_data, bool p_ignore_note_off, int p_pan, int p_src_channel_count, int p_channel_count) {
return SiOPMRefTable::get_instance()->register_sampler_data(p_index, p_data, p_ignore_note_off, p_pan, p_src_channel_count, p_channel_count);
SiOPMWaveSamplerData *SiONDriver::set_sampler_wave(int p_index, const Variant &p_data, bool p_ignore_note_off, int p_pan, int p_src_channel_num, int p_channel_num) {
return SiOPMRefTable::get_instance()->register_sampler_data(p_index, p_data, p_ignore_note_off, p_pan, p_src_channel_num, p_channel_num);
}

void SiONDriver::set_pcm_voice(int p_index, const Ref<SiONVoice> &p_voice) {
Expand Down Expand Up @@ -279,34 +280,38 @@ int SiONDriver::get_max_track_count() const {
}

void SiONDriver::set_max_track_count(int p_value) {
ERR_FAIL_COND_MSG(p_value < 1, "SiONDriver: Max track limit cannot be lower than 1.");

sequencer->set_max_track_count(p_value);
}

void SiONDriver::_update_volume() {
double db_volume = Math::linear2db(_master_volume * _fader_volume);
_audio_player->set_volume_db(db_volume);
}

double SiONDriver::get_volume() const {
return _master_volume;
}

void SiONDriver::set_volume(double p_value) {
_master_volume = p_value;
_audio_player->set_volume_db(_master_volume * _fader_volume);
}

double SiONDriver::get_pan() const {
// TODO: Add panning support for Godot types.
//return _sound_transform->pan;
return 0;
}
ERR_FAIL_COND_MSG(p_value < 0 || p_value > 1, "SiONDriver: Volume must be between 0.0 and 1.0 (inclusive).");

void SiONDriver::set_pan(double p_value) {
// TODO: Add panning support for Godot types.
//_sound_transform->pan = p_value;
_master_volume = p_value;
_update_volume();
}

double SiONDriver::get_bpm() const {
return sequencer->get_effective_bpm();
}

void SiONDriver::set_bpm(double p_value) {
// Scholars debate whether BPM even has the upper limit. At some point it definitely turns into tone for most people,
// with no discernible beat in earshot. But having no limit at all for the API feels strange. Besides, we don't have
// infinitely scalable performance. So as a compromise you can set the BPM to up to 4000 beats per minute.
// You're welcome!
ERR_FAIL_COND_MSG(p_value < 1 || p_value > 4000, "SiONDriver: BPM must be between 1 and 4000 (inclusive).");

sequencer->set_effective_bpm(p_value);
}

Expand Down Expand Up @@ -364,11 +369,11 @@ void SiONDriver::_prepare_compile(String p_mml, const Ref<SiONData> &p_data) {
_current_job_type = JobType::COMPILE;
}

void SiONDriver::_prepare_render(const Variant &p_data, Vector<double> p_render_buffer, int p_render_buffer_channel_count, bool p_reset_effector) {
void SiONDriver::_prepare_render(const Variant &p_data, Vector<double> p_render_buffer, int p_render_buffer_channel_num, bool p_reset_effector) {
_prepare_process(p_data, p_reset_effector);

_render_buffer = p_render_buffer;
_render_buffer_channel_count = (p_render_buffer_channel_count == 2 ? 2 : 1);
_render_buffer_channel_num = (p_render_buffer_channel_num == 2 ? 2 : 1);
_render_buffer_size_max = _render_buffer.size();
_render_buffer_index = 0;

Expand All @@ -389,7 +394,7 @@ bool SiONDriver::_rendering() {

// Limit the rendering length.
int rendering_length = _buffer_length << 1;
int buffer_extension = _buffer_length << (_render_buffer_channel_count - 1);
int buffer_extension = _buffer_length << (_render_buffer_channel_num - 1);

if (_render_buffer_size_max != 9 && _render_buffer_size_max < (_render_buffer_index + buffer_extension)) {
buffer_extension = _render_buffer_size_max - _render_buffer_index;
Expand All @@ -404,7 +409,7 @@ bool SiONDriver::_rendering() {
// Read the output.
Vector<double> *output_buffer = sound_chip->get_output_buffer_ptr();

if (_render_buffer_channel_count == 2) {
if (_render_buffer_channel_num == 2) {
for (int i = 0, j = _render_buffer_index; i < rendering_length; i++, j++) {
_render_buffer.write[j] = (*output_buffer)[i];
}
Expand Down Expand Up @@ -520,11 +525,11 @@ int SiONDriver::queue_compile(String p_mml, const Ref<SiONData> &p_data) {
return _job_queue.size();
}

Vector<double> SiONDriver::render(const Variant &p_data, Vector<double> p_render_buffer, int p_render_buffer_channel_count, bool p_reset_effector) {
Vector<double> SiONDriver::render(const Variant &p_data, Vector<double> p_render_buffer, int p_render_buffer_channel_num, bool p_reset_effector) {
stop();

int start_time = Time::get_singleton()->get_ticks_msec();
_prepare_render(p_data, p_render_buffer, p_render_buffer_channel_count, p_reset_effector);
_prepare_render(p_data, p_render_buffer, p_render_buffer_channel_num, p_reset_effector);

while (true) { // Render everything.
if (_rendering()) {
Expand All @@ -536,7 +541,7 @@ Vector<double> SiONDriver::render(const Variant &p_data, Vector<double> p_render
return _render_buffer;
}

int SiONDriver::queue_render(const Variant &p_data, Vector<double> p_render_buffer, int p_render_buffer_channel_count, bool p_reset_effector) {
int SiONDriver::queue_render(const Variant &p_data, Vector<double> p_render_buffer, int p_render_buffer_channel_num, bool p_reset_effector) {
if (p_data.get_type() == Variant::NIL || p_render_buffer.is_empty()) {
return _job_queue.size();
}
Expand All @@ -550,15 +555,15 @@ int SiONDriver::queue_render(const Variant &p_data, Vector<double> p_render_buff
// Queue compilation first.
_job_queue.push_back(memnew(SiONDriverJob(mml_string, Vector<double>(), sion_data, 2, false)));
// Then queue the render.
_job_queue.push_back(memnew(SiONDriverJob(String(), p_render_buffer, sion_data, p_render_buffer_channel_count, p_reset_effector)));
_job_queue.push_back(memnew(SiONDriverJob(String(), p_render_buffer, sion_data, p_render_buffer_channel_num, p_reset_effector)));

return _job_queue.size();
} break;

case Variant::OBJECT: {
Ref<SiONData> sion_data = p_data;
if (sion_data.is_valid()) {
_job_queue.push_back(memnew(SiONDriverJob(String(), p_render_buffer, sion_data, p_render_buffer_channel_count, p_reset_effector)));
_job_queue.push_back(memnew(SiONDriverJob(String(), p_render_buffer, sion_data, p_render_buffer_channel_num, p_reset_effector)));

return _job_queue.size();
}
Expand All @@ -567,7 +572,7 @@ int SiONDriver::queue_render(const Variant &p_data, Vector<double> p_render_buff
default: break; // Silences enum warnings.
}

ERR_FAIL_V_MSG(_job_queue.size(), "SiONDriver: Unsupported data type.");
ERR_FAIL_V_MSG(_job_queue.size(), "SiONDriver: Data type is unsupported by the render.");
}

// Playback.
Expand Down Expand Up @@ -621,7 +626,7 @@ void SiONDriver::stop() {
_fader_volume = 1;
_audio_playback = Ref<AudioStreamGeneratorPlayback>();
_audio_player->stop();
_audio_player->set_volume_db(_master_volume);
_update_volume();
sequencer->stop_sequence();

_dispatch_event(memnew(SiONEvent(SiONEvent::STREAM_STOPPED, this)));
Expand Down Expand Up @@ -796,7 +801,7 @@ TypedArray<SiMMLTrack> SiONDriver::sequence_off(int p_track_id, double p_delay,

void SiONDriver::_fade_callback(double p_value) {
_fader_volume = p_value;
_audio_player->set_volume_db(_master_volume * _fader_volume);
_update_volume();

if (!_fading_event_enabled) {
return;
Expand Down Expand Up @@ -865,7 +870,7 @@ void SiONDriver::_prepare_process(const Variant &p_data, bool p_reset_effector)

// Order of operations below is critical.

sound_chip->initialize(_channel_count, _bitrate, _buffer_length); // Initialize DSP.
sound_chip->initialize(_channel_num, _bitrate, _buffer_length); // Initialize DSP.
sound_chip->reset(); // Reset all channels.

if (p_reset_effector) { // Initialize or reset effectors.
Expand Down Expand Up @@ -1002,7 +1007,7 @@ bool SiONDriver::_prepare_next_job() {
_job_queue.pop_front();

if (job->mml.is_empty()) {
_prepare_render(job->data, job->buffer, job->channel_count, job->reset_effector);
_prepare_render(job->data, job->buffer, job->channel_num, job->reset_effector);
} else {
_prepare_compile(job->mml, job->data);
}
Expand Down Expand Up @@ -1169,8 +1174,8 @@ void SiONDriver::_notification(int p_what) {

//

SiONDriver *SiONDriver::create(int p_buffer_length, int p_channel_count, int p_sample_rate, int p_bitrate) {
return memnew(SiONDriver(p_buffer_length, p_channel_count, p_sample_rate, p_bitrate));
SiONDriver *SiONDriver::create(int p_buffer_length, int p_channel_num, int p_sample_rate, int p_bitrate) {
return memnew(SiONDriver(p_buffer_length, p_channel_num, p_sample_rate, p_bitrate));
}

void SiONDriver::_bind_methods() {
Expand All @@ -1197,8 +1202,29 @@ void SiONDriver::_bind_methods() {

// Public API.

ClassDB::bind_method(D_METHOD("get_audio_player"), &SiONDriver::get_audio_player);
ClassDB::bind_method(D_METHOD("get_audio_stream"), &SiONDriver::get_audio_stream);
ClassDB::bind_method(D_METHOD("get_audio_playback"), &SiONDriver::get_audio_playback);

ClassDB::bind_method(D_METHOD("get_track_count"), &SiONDriver::get_track_count);
ClassDB::bind_method(D_METHOD("get_max_track_count"), &SiONDriver::get_max_track_count);
ClassDB::bind_method(D_METHOD("set_max_track_count", "value"), &SiONDriver::set_max_track_count);

ClassDB::add_property("SiONDriver", PropertyInfo(Variant::INT, "max_track_count"), "set_max_track_count", "get_max_track_count");

ClassDB::bind_method(D_METHOD("get_buffer_length"), &SiONDriver::get_buffer_length);
ClassDB::bind_method(D_METHOD("get_channel_num"), &SiONDriver::get_channel_num);
ClassDB::bind_method(D_METHOD("get_sample_rate"), &SiONDriver::get_sample_rate);
ClassDB::bind_method(D_METHOD("get_bitrate"), &SiONDriver::get_bitrate);

ClassDB::bind_method(D_METHOD("get_volume"), &SiONDriver::get_volume);
ClassDB::bind_method(D_METHOD("set_volume", "value"), &SiONDriver::set_volume);
ClassDB::bind_method(D_METHOD("get_bpm"), &SiONDriver::get_bpm);
ClassDB::bind_method(D_METHOD("set_bpm", "bpm"), &SiONDriver::set_bpm);

ClassDB::add_property("SiONDriver", PropertyInfo(Variant::FLOAT, "volume"), "set_volume", "get_volume");
ClassDB::add_property("SiONDriver", PropertyInfo(Variant::FLOAT, "bpm"), "set_bpm", "get_bpm");

ClassDB::bind_method(D_METHOD("set_beat_callback_interval", "length_16th"), &SiONDriver::set_beat_callback_interval);
ClassDB::bind_method(D_METHOD("set_timer_interval", "length_16th"), &SiONDriver::set_timer_interval);

Expand Down Expand Up @@ -1321,12 +1347,12 @@ void SiONDriver::_bind_methods() {
BIND_ENUM_CONSTANT(PULSE_USER_PCM);
}

SiONDriver::SiONDriver(int p_buffer_length, int p_channel_count, int p_sample_rate, int p_bitrate) {
SiONDriver::SiONDriver(int p_buffer_length, int p_channel_num, int p_sample_rate, int p_bitrate) {
ERR_FAIL_COND_MSG(!_allow_multiple_drivers && _mutex, "SiONDriver: Only one driver instance is allowed.");
_mutex = this;

ERR_FAIL_COND_MSG((p_buffer_length != 2048 && p_buffer_length != 4096 && p_buffer_length != 8192), "SiONDriver: Buffer length can only be 2048, 4096, or 8192.");
ERR_FAIL_COND_MSG((p_channel_count != 1 && p_channel_count != 2), "SiONDriver: Channel count can only be 1 (mono) or 2 (stereo).");
ERR_FAIL_COND_MSG((p_channel_num != 1 && p_channel_num != 2), "SiONDriver: Channel number can only be 1 (mono) or 2 (stereo).");
ERR_FAIL_COND_MSG((p_sample_rate != 44100), "SiONDriver: Sampling rate can only be 44100.");

sound_chip = memnew(SiOPMSoundChip);
Expand All @@ -1339,11 +1365,9 @@ SiONDriver::SiONDriver(int p_buffer_length, int p_channel_count, int p_sample_ra

// Main sound.
{
// TODO: Add panning support for Godot types.

_audio_player = memnew(AudioStreamPlayer);
add_child(_audio_player);
_audio_player->set_volume_db(_master_volume * _fader_volume);
_update_volume();

_audio_stream.instantiate();
_audio_stream->set_mix_rate(p_sample_rate);
Expand All @@ -1369,7 +1393,7 @@ SiONDriver::SiONDriver(int p_buffer_length, int p_channel_count, int p_sample_ra

{
_buffer_length = p_buffer_length;
_channel_count = p_channel_count;
_channel_num = p_channel_num;
_sample_rate = p_sample_rate;
_bitrate = p_bitrate;
}
Expand Down
Loading

0 comments on commit c1e4141

Please sign in to comment.