Skip to content

Commit

Permalink
Refactor SiONDriver.play into two separate method
Browse files Browse the repository at this point in the history
This helps clean up the semantics of how
the driver works. We now have a dedicated
stream method, which can be called to just
start streaming without any data. At the
same time the old play method now expects
data for processing.

This is accompanied by two new methods,
is_streaming and is_paused, which report on
the corresponding statuses.

PS. This has a minor compatibility
implication. It is impossible now to call
SiONDriver.play without any arguments. But
the data argument itself is not enforced, and
can be null. Meaning that play(null) can still
be used to achieve the same effect as
stream().
  • Loading branch information
YuriSizov committed Dec 2, 2024
1 parent 6272492 commit cd16bb4
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 37 deletions.
61 changes: 44 additions & 17 deletions doc_classes/SiONDriver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<method name="get_audio_playback" qualifiers="const">
<return type="AudioStreamGeneratorPlayback" />
<description>
Returns the internal [AudioStreamGeneratorPlayback] instance. Only available during streaming (after calling [method play]).
Returns the internal [AudioStreamGeneratorPlayback] instance. Only available during streaming (see [method stream]).
</description>
</method>
<method name="get_audio_player" qualifiers="const">
Expand Down Expand Up @@ -164,9 +164,22 @@
Returns the flavor of the current version, e.g. "stable" or "beta1".
</description>
</method>
<method name="is_paused" qualifiers="const">
<return type="bool" />
<description>
Returns whether the streaming process is paused. See [method pause] and [method resume].
</description>
</method>
<method name="is_queue_executing" qualifiers="const">
<return type="bool" />
<description>
Returns whether the job queue is currently executing jobs.
</description>
</method>
<method name="is_streaming" qualifiers="const">
<return type="bool" />
<description>
Returns whether the driver is currently streaming (regardless of the paused flag). See [method stream] and [method play].
</description>
</method>
<method name="note_off">
Expand All @@ -179,7 +192,7 @@
<description>
Executes a note off event for all matching tracks (see also [method SiMMLTrack.get_track_id]). Returns affected tracks.
[param delay] and [param quantize] must be in 1/16th of a beat.
[b]Note:[/b] Streaming must be initialized before this method is called (see [method play]).
[b]Note:[/b] Streaming must be initialized before this method is called (see [method stream]).
</description>
</method>
<method name="note_on">
Expand All @@ -195,7 +208,7 @@
Executes a note on event using the given voice. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]).
If [param length] is set to [code]0[/code], the note will play continuously, according to the voice configuration. It can die off naturally, but it can also continue playing until the [method note_off] method is called explicitly. [param length], [param delay], and [param quantize] must be in 1/16th of a beat.
Use [param disposable] to control whether the created track can be freed after being used. Non-disposable tracks must be treated with care as it can lead to performance penalties.
[b]Note:[/b] Streaming must be initialized before this method is called (see [method play]).
[b]Note:[/b] Streaming must be initialized before this method is called (see [method stream]).
</description>
</method>
<method name="note_on_with_bend">
Expand All @@ -218,16 +231,16 @@
<method name="pause">
<return type="void" />
<description>
Pauses streaming initialized by [method play].
Pauses streaming initialized by [method stream] or [method play].
</description>
</method>
<method name="play">
<return type="void" />
<param index="0" name="data" type="Variant" default="null" />
<param index="0" name="data" type="Variant" />
<param index="1" name="reset_effector" type="bool" default="true" />
<description>
Starts streaming using the data provided, which is played immediately. Stops existing streaming, if it exists (see [method stop]). Does nothing when streaming is paused (see [method pause]).
Empty data can be provided to this method to simply initialize streaming without playing anything immediately. See also [method note_on], [method note_off], [method sequence_on], and [method sequence_off].
Starts streaming using the data provided, which is played immediately. If the driver is already streaming, it is gracefully stopped first (see [method stop]). Paused streams are terminated as well.
To start streaming without any data, call [method stream] instead.
[codeblocks]
[gdscript]
driver.play("t100 l8 [ ccggaag4 ffeeddc4 | [ggffeed4]2 ]2")
Expand Down Expand Up @@ -288,7 +301,7 @@
Plays the sound for the given sample number. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]).
[param length], [param delay], and [param quantize] must be in 1/16th of a beat.
Use [param disposable] to control whether the track can be freed after being used. Making a track non-disposable must be treated with care as it can lead to performance penalties.
[b]Note:[/b] Streaming must be initialized before this method is called (see [method play]).
[b]Note:[/b] Streaming must be initialized before this method is called (see [method stream]).
</description>
</method>
<method name="sequence_off">
Expand All @@ -300,7 +313,7 @@
<description>
Stops execution of sequences for all matching tracks (see also [method SiMMLTrack.get_track_id]). Returns affected tracks.
[param delay] and [param quantize] must be in 1/16th of a beat.
[b]Note:[/b] Streaming must be initialized before this method is called (see [method play]).
[b]Note:[/b] Streaming must be initialized before this method is called (see [method stream]).
</description>
</method>
<method name="sequence_on">
Expand All @@ -316,7 +329,7 @@
Executes a sequence, or sequences, provided by the given data and using the given voice. Returns affected, or newly created, tracks (see also [method SiMMLTrack.get_track_id]).
[param length], [param delay], and [param quantize] must be in 1/16th of a beat.
Use [param disposable] to control whether the created track can be freed after being used. Non-disposable tracks must be treated with care as it can lead to performance penalties.
[b]Note:[/b] Streaming must be initialized before this method is called (see [method play]).
[b]Note:[/b] Streaming must be initialized before this method is called (see [method stream]).
</description>
</method>
<method name="set_beat_callback_interval">
Expand Down Expand Up @@ -364,7 +377,21 @@
<method name="stop">
<return type="void" />
<description>
Stops streaming initialized by [method play]. Attempts to stop gracefully if triggered while processing.
Stops streaming initialized by [method stream] or [method play]. Attempts to stop gracefully if triggered while processing.
</description>
</method>
<method name="stream">
<return type="void" />
<param index="0" name="reset_effector" type="bool" default="true" />
<description>
Starts driver streaming, allowing you to use it in the interactive mode. See [method note_on], [method note_off], [method sequence_on], and [method sequence_off].
To immediately play some data, use [method play] instead.
[codeblocks]
[gdscript]
driver.stream()
driver.note_on(42, SiONVoice.new(), 4)
[/gdscript]
[/codeblocks]
</description>
</method>
</methods>
Expand Down Expand Up @@ -395,19 +422,19 @@
<signal name="fade_in_completed">
<param index="0" name="event" type="SiONEvent" />
<description>
Emitted if the fader is used and after the fading in has finished. This signal is emitted after the [signal streaming] signal. Contains a [SiONDriver] instance, a [SiONData] instance (unless [method play] was called without data), and the streaming buffer as a byte array.
Emitted if the fader is used and after the fading in has finished. This signal is emitted after the [signal streaming] signal. Contains a [SiONDriver] instance, a [SiONData] instance (if [method play] was called instead of [method stream]), and the streaming buffer as a byte array.
</description>
</signal>
<signal name="fade_out_completed">
<param index="0" name="event" type="SiONEvent" />
<description>
Emitted if the fader is used and after the fading out has finished. This signal is emitted after the [signal streaming] signal. Contains a [SiONDriver] instance, a [SiONData] instance (unless [method play] was called without data), and the streaming buffer as a byte array.
Emitted if the fader is used and after the fading out has finished. This signal is emitted after the [signal streaming] signal. Contains a [SiONDriver] instance, a [SiONData] instance (if [method play] was called instead of [method stream]), and the streaming buffer as a byte array.
</description>
</signal>
<signal name="fading">
<param index="0" name="event" type="SiONEvent" />
<description>
Emitted if the fader is used and while the fading is happening. This signal is emitted after the [signal streaming] signal. Contains a [SiONDriver] instance and a [SiONData] instance (unless [method play] was called without data).
Emitted if the fader is used and while the fading is happening. This signal is emitted after the [signal streaming] signal. Contains a [SiONDriver] instance and a [SiONData] instance (if [method play] was called instead of [method stream]).
</description>
</signal>
<signal name="note_off_frame">
Expand Down Expand Up @@ -467,19 +494,19 @@
<signal name="stream_started">
<param index="0" name="event" type="SiONEvent" />
<description>
Emitted after streaming has started. This signal is emitted before the first [signal streaming] signal. Contains a [SiONDriver] instance and a [SiONData] instance (unless [method play] was called without data).
Emitted after streaming has started. This signal is emitted before the first [signal streaming] signal. Contains a [SiONDriver] instance and a [SiONData] instance (if [method play] was called instead of [method stream]).
</description>
</signal>
<signal name="stream_stopped">
<param index="0" name="event" type="SiONEvent" />
<description>
Emitted after streaming has stopped. Contains a [SiONDriver] instance and a [SiONData] instance (unless [method play] was called without data).
Emitted after streaming has stopped. Contains a [SiONDriver] instance and a [SiONData] instance (if [method play] was called instead of [method stream]).
</description>
</signal>
<signal name="streaming">
<param index="0" name="event" type="SiONEvent" />
<description>
Emitted during streaming when a portion of the buffer is ready. Contains a [SiONDriver] instance, a [SiONData] instance (unless [method play] was called without data), and the streaming buffer as a byte array.
Emitted during streaming when a portion of the buffer is ready. Contains a [SiONDriver] instance, a [SiONData] instance (if [method play] was called instead of [method stream]), and the streaming buffer as a byte array.
See also [method set_stream_event_enabled].
</description>
</signal>
Expand Down
2 changes: 1 addition & 1 deletion example/globals/MusicPlayer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func play_tune(mml_string: String) -> void:


func start_streaming() -> void:
_driver.play(null, false)
_driver.stream(false)
print("Driver is streaming.")


Expand Down
36 changes: 23 additions & 13 deletions src/sion_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,13 +599,7 @@ int SiONDriver::queue_render(const Variant &p_data, int p_buffer_size, int p_buf

// Playback.

void SiONDriver::play(const Variant &p_data, bool p_reset_effector) {
if (_is_paused) { // Just unpause and continue.
_is_paused = false;
return;
}

stop(); // Stop any existing sound.
void SiONDriver::_prepare_stream(const Variant &p_data, bool p_reset_effector) {
_prepare_process(p_data, p_reset_effector);

_performance_stats.total_processing_time = 0;
Expand All @@ -623,6 +617,16 @@ void SiONDriver::play(const Variant &p_data, bool p_reset_effector) {
_set_processing_immediate();
}

void SiONDriver::stream(bool p_reset_effector) {
stop();
_prepare_stream(nullptr, p_reset_effector);
}

void SiONDriver::play(const Variant &p_data, bool p_reset_effector) {
stop();
_prepare_stream(p_data, p_reset_effector);
}

void SiONDriver::stop() {
if (!_is_streaming) {
return;
Expand Down Expand Up @@ -657,7 +661,9 @@ void SiONDriver::reset() {
}

void SiONDriver::pause() {
_is_paused = true;
if (_is_streaming) {
_is_paused = true;
}
}

void SiONDriver::resume() {
Expand Down Expand Up @@ -701,7 +707,7 @@ SiMMLTrack *SiONDriver::_find_or_create_track(int p_track_id, double p_delay, do
}

SiMMLTrack *SiONDriver::sample_on(int p_sample_number, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) {
ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first.");
ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.stream() first.");
ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Sample length cannot be less than zero.");

int delay_samples = 0;
Expand All @@ -717,7 +723,7 @@ SiMMLTrack *SiONDriver::sample_on(int p_sample_number, double p_length, double p
}

SiMMLTrack *SiONDriver::note_on(int p_note, const Ref<SiONVoice> &p_voice, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) {
ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first.");
ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.stream() first.");
ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Note length cannot be less than zero.");

int delay_samples = 0;
Expand All @@ -735,7 +741,7 @@ SiMMLTrack *SiONDriver::note_on(int p_note, const Ref<SiONVoice> &p_voice, doubl
}

SiMMLTrack *SiONDriver::note_on_with_bend(int p_note, int p_note_to, double p_bend_length, const Ref<SiONVoice> &p_voice, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) {
ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first.");
ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.stream() first.");
ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Note length cannot be less than zero.");
ERR_FAIL_COND_V_MSG(p_bend_length < 0, nullptr, "SiONDriver: Pitch bending length cannot be less than zero.");

Expand All @@ -755,7 +761,7 @@ SiMMLTrack *SiONDriver::note_on_with_bend(int p_note, int p_note_to, double p_be
}

TypedArray<SiMMLTrack> SiONDriver::note_off(int p_note, int p_track_id, double p_delay, double p_quant, bool p_stop_immediately) {
ERR_FAIL_COND_V_MSG(!_is_streaming, TypedArray<SiMMLTrack>(), "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first.");
ERR_FAIL_COND_V_MSG(!_is_streaming, TypedArray<SiMMLTrack>(), "SiONDriver: Driver is not streaming, you must call SiONDriver.stream() first.");
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray<SiMMLTrack>(), "SiONDriver: Note off delay cannot be less than zero.");

int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE;
Expand Down Expand Up @@ -1327,12 +1333,16 @@ void SiONDriver::_bind_methods() {
ClassDB::bind_method(D_METHOD("render", "data", "buffer_size", "buffer_channel_num", "reset_effector"), &SiONDriver::render, DEFVAL(2), DEFVAL(true));
ClassDB::bind_method(D_METHOD("queue_render", "data", "buffer_size", "buffer_channel_num", "reset_effector"), &SiONDriver::queue_render, DEFVAL(2), DEFVAL(false));

ClassDB::bind_method(D_METHOD("play", "data", "reset_effector"), &SiONDriver::play, DEFVAL(Variant()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("stream", "reset_effector"), &SiONDriver::stream, DEFVAL(true));
ClassDB::bind_method(D_METHOD("play", "data", "reset_effector"), &SiONDriver::play, DEFVAL(true));
ClassDB::bind_method(D_METHOD("stop"), &SiONDriver::stop);
ClassDB::bind_method(D_METHOD("reset"), &SiONDriver::reset);
ClassDB::bind_method(D_METHOD("pause"), &SiONDriver::pause);
ClassDB::bind_method(D_METHOD("resume"), &SiONDriver::resume);

ClassDB::bind_method(D_METHOD("is_streaming"), &SiONDriver::is_streaming);
ClassDB::bind_method(D_METHOD("is_paused"), &SiONDriver::is_paused);

ClassDB::bind_method(D_METHOD("sample_on", "sample_number", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::sample_on, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true));
ClassDB::bind_method(D_METHOD("note_on", "note", "voice", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::note_on, DEFVAL((Object *)nullptr), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true));
ClassDB::bind_method(D_METHOD("note_on_with_bend", "note", "note_to", "bend_length", "voice", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::note_on_with_bend, DEFVAL((Object *)nullptr), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true));
Expand Down
10 changes: 6 additions & 4 deletions src/sion_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class SiONDriver : public Node {

void _prepare_compile(String p_mml, const Ref<SiONData> &p_data);
void _prepare_render(const Variant &p_data, int p_buffer_size, int p_buffer_channel_num, bool p_reset_effector);
void _prepare_stream(const Variant &p_data, bool p_reset_effector);
bool _rendering();
void _streaming();

Expand Down Expand Up @@ -359,9 +360,6 @@ class SiONDriver : public Node {
bool is_notify_change_bpm_on_position_changed() const { return _notify_change_bpm_on_position_changed; }
void set_notify_change_bpm_on_position_changed(bool p_enabled) { _notify_change_bpm_on_position_changed = p_enabled; }

bool is_streaming() const { return _is_streaming; }
bool is_paused() const { return _is_paused; }

double get_streaming_position() const;
void set_start_position(double p_value);

Expand All @@ -380,12 +378,16 @@ class SiONDriver : public Node {

// Playback.

void play(const Variant &p_data = Variant(), bool p_reset_effector = true);
void stream(bool p_reset_effector = true);
void play(const Variant &p_data, bool p_reset_effector = true);
void stop();
void reset();
void pause();
void resume();

bool is_streaming() const { return _is_streaming; }
bool is_paused() const { return _is_paused; }

SiMMLTrack *sample_on(int p_sample_number, double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true);
SiMMLTrack *note_on(int p_note, const Ref<SiONVoice> &p_voice = Ref<SiONVoice>(), double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true);
SiMMLTrack *note_on_with_bend(int p_note, int p_note_to, double p_bend_length, const Ref<SiONVoice> &p_voice = Ref<SiONVoice>(), double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true);
Expand Down
2 changes: 1 addition & 1 deletion tests/run/driver-lifecycle.gd
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func run(scene_tree: SceneTree) -> void:
_assert_not_null("driver parent", driver.get_parent())
_assert_not_null("audio player", driver.get_audio_player())

driver.play()
driver.stream()
await scene_tree.process_frame

_assert_equal("on play: audio playing", driver.get_audio_player().is_playing(), true)
Expand Down
2 changes: 1 addition & 1 deletion tests/run/voices-sound-consistency.gd
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func _sample_voice(driver: SiONDriver, voice: SiONVoice) -> void:
var note_value := 60
var note_length := 1 * SAMPLE_LENGTH

driver.play(null, false)
driver.stream(false)
driver.streaming.connect(_collect_streamed_data)
driver.set_stream_event_enabled(true)

Expand Down

0 comments on commit cd16bb4

Please sign in to comment.