diff --git a/doc_classes/SiONDriver.xml b/doc_classes/SiONDriver.xml
index 8995942..88d5566 100644
--- a/doc_classes/SiONDriver.xml
+++ b/doc_classes/SiONDriver.xml
@@ -193,11 +193,28 @@
Executes a note on event using the given voice. 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.
+ 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]).
+
+
+
+
+
+
+
+
+
+
+
+
+ Executes a note on event with pitch bending applied using the given voice. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]).
+ The resulting pitch is bent from [param note] to [param note_to] over the course of [param bend_length], which must be in 1/16th of a beat. This length cannot be more than the length of the note and will be truncated to fit. This does not affect continuous notes that have the length of [code]0[/code].
+ See also [method note_on].
+
+
@@ -218,21 +235,6 @@
[/codeblocks]
-
-
-
-
-
-
-
-
-
- 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]).
-
-
@@ -274,6 +276,21 @@
Resumes streaming previously paused by [method pause].
+
+
+
+
+
+
+
+
+
+ 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]).
+
+
@@ -298,7 +315,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 track can be freed after being used. Making a track non-disposable must be treated with care as it can lead to performance penalties.
+ 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]).
diff --git a/example/gui/components/PianoRoll.gd b/example/gui/components/PianoRoll.gd
index 58ac664..62db386 100644
--- a/example/gui/components/PianoRoll.gd
+++ b/example/gui/components/PianoRoll.gd
@@ -33,7 +33,7 @@ func _init() -> void:
func _ready() -> void:
_update_instrument()
_update_positions()
-
+
if not Engine.is_editor_hint():
Controller.music_player.instrument_changed.connect(_update_instrument)
@@ -85,7 +85,7 @@ func _update_keys() -> void:
if is_drumkit:
var instrument := Controller.music_player.get_active_instrument()
var active_drumkit := Controller.voice_manager.get_drumkit(instrument.type)
-
+
_keys.resize(active_drumkit.size)
for i in active_drumkit.size:
_keys[i] = i
@@ -118,7 +118,7 @@ func _update_keys() -> void:
func _update_instrument() -> void:
if Engine.is_editor_hint():
return
-
+
var instrument := Controller.music_player.get_active_instrument()
is_drumkit = instrument.type != 0
@@ -135,7 +135,7 @@ func _update_positions() -> void:
var active_drumkit: Drumkit = null
var normal_key_count := 0
-
+
if is_drumkit:
var instrument := Controller.music_player.get_active_instrument()
active_drumkit = Controller.voice_manager.get_drumkit(instrument.type)
@@ -227,7 +227,7 @@ func _update_active_key(key: int) -> void:
var note_length := NOTE_LENGTH
if is_drumkit:
note_length = NOTE_LENGTH * 2
-
+
Controller.music_player.play_note(_active_key, note_length)
queue_redraw()
diff --git a/src/sequencer/base/mml_executor.cpp b/src/sequencer/base/mml_executor.cpp
index 015e072..259480d 100644
--- a/src/sequencer/base/mml_executor.cpp
+++ b/src/sequencer/base/mml_executor.cpp
@@ -63,32 +63,28 @@ void MMLExecutor::execute_single_note(int p_note, int p_tick_length) {
_current_tick_count = 0;
}
-bool MMLExecutor::pitch_bend_from(int p_note, int p_tick_length) {
+void MMLExecutor::bend_single_note(int p_to_note, int p_tick_length) {
if (_pointer != _note_event || p_tick_length == 0) {
- return false;
+ return;
}
int tick_length = p_tick_length;
- if (_note_event->get_length() != 0) {
- // SUS: This check and following adjustments seem inconsistent to me.
- // If the note event has length, but the bend event is shorter than that, we
- // make the bend event almost as long as the note event, and make the note
- // event one tick long.
- // However, if the bend event is longer than the note event, then we keep it
- // as is, and make the note event shorter by the bend event's length, meaning
- // that the note event now has negative length.
- if (tick_length < _note_event->get_length()) {
+ if (_note_event->get_length() > 0) {
+ // Bending cannot last longer than the note. But the trailing note
+ // must play for at least 1 tick.
+ if (tick_length > _note_event->get_length()) {
tick_length = _note_event->get_length() - 1;
}
_note_event->set_length(_note_event->get_length() - tick_length);
}
_bend_from_event->set_length(0);
- _bend_from_event->set_data(p_note);
+ _bend_from_event->set_data(_note_event->get_data());
_bend_event->set_length(tick_length);
+ _note_event->set_data(p_to_note);
_pointer = _bend_from_event;
- return true;
+ return;
}
MMLEvent *MMLExecutor::publish_processing_event(MMLEvent *p_event) {
diff --git a/src/sequencer/base/mml_executor.h b/src/sequencer/base/mml_executor.h
index bc89523..5e8206f 100644
--- a/src/sequencer/base/mml_executor.h
+++ b/src/sequencer/base/mml_executor.h
@@ -58,7 +58,7 @@ class MMLExecutor {
void reset_pointer();
void stop();
void execute_single_note(int p_note, int p_tick_length);
- bool pitch_bend_from(int p_note, int p_tick_length);
+ void bend_single_note(int p_to_note, int p_tick_length);
MMLEvent *publish_processing_event(MMLEvent *p_event);
diff --git a/src/sequencer/simml_track.cpp b/src/sequencer/simml_track.cpp
index a223aef..0dde453 100644
--- a/src/sequencer/simml_track.cpp
+++ b/src/sequencer/simml_track.cpp
@@ -123,10 +123,6 @@ void SiMMLTrack::set_pitch_bend(int p_value) {
_channel->set_pitch(_pitch_index + _pitch_bend);
}
-void SiMMLTrack::start_pitch_bending(int p_note_from, int p_tick_length) {
- _executor->pitch_bend_from(p_note_from, p_tick_length);
-}
-
void SiMMLTrack::set_note_immediately(int p_note, int p_sample_length, bool p_slur) {
// Play with key off when quantize_ratio == 0 or p_sample_length != 0.
if (!p_slur && (_quantize_ratio == 0 || p_sample_length > 0)) {
@@ -852,6 +848,10 @@ void SiMMLTrack::key_off(int p_sample_delay, bool p_with_reset) {
}
}
+void SiMMLTrack::bend_note(int p_to_note, int p_tick_length) {
+ _executor->bend_single_note(p_to_note, p_tick_length);
+}
+
void SiMMLTrack::sequence_on(const Ref &p_data, MMLSequence *p_sequence, int p_sample_length, int p_sample_delay) {
ERR_FAIL_NULL(p_sequence);
diff --git a/src/sequencer/simml_track.h b/src/sequencer/simml_track.h
index 19a7a7a..5fa5b45 100644
--- a/src/sequencer/simml_track.h
+++ b/src/sequencer/simml_track.h
@@ -277,7 +277,6 @@ class SiMMLTrack : public Object {
int get_pitch_bend() const { return _pitch_bend; }
void set_pitch_bend(int p_value);
- void start_pitch_bending(int p_note_from, int p_tick_length);
int get_pitch_shift() const { return _pitch_shift; }
void set_pitch_shift(int p_value) { _pitch_shift = p_value; }
@@ -374,6 +373,7 @@ class SiMMLTrack : public Object {
void key_on(int p_note, int p_tick_length = 0, int p_sample_delay = 0);
void key_off(int p_sample_delay = 0, bool p_with_reset = false);
+ void bend_note(int p_to_note, int p_tick_length);
void sequence_on(const Ref &p_data, MMLSequence *p_sequence, int p_sample_length = 0, int p_sample_delay = 0);
void sequence_off(int p_sample_delay = 0, bool p_with_reset = false);
diff --git a/src/sion_driver.cpp b/src/sion_driver.cpp
index 218f89c..9153608 100644
--- a/src/sion_driver.cpp
+++ b/src/sion_driver.cpp
@@ -664,10 +664,8 @@ void SiONDriver::resume() {
_is_paused = false;
}
-SiMMLTrack *SiONDriver::play_sound(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(p_length < 0, nullptr, "SiONDriver: Sound length cannot be less than zero.");
- ERR_FAIL_COND_V_MSG(p_delay < 0, nullptr, "SiONDriver: Sound delay cannot be less than zero.");
+SiMMLTrack *SiONDriver::_find_or_create_track(int p_track_id, double p_delay, double p_quant, bool p_disposable, int *r_delay_samples) {
+ ERR_FAIL_COND_V_MSG(p_delay < 0, nullptr, "SiONDriver: Playback delay cannot be less than zero.");
int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE;
double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
@@ -691,55 +689,67 @@ SiMMLTrack *SiONDriver::play_sound(int p_sample_number, double p_length, double
}
}
- if (!track) {
- track = sequencer->create_controllable_track(internal_track_id, p_disposable);
- }
+ *r_delay_samples = delay_samples;
if (track) {
- track->set_channel_module_type(SiONModuleType::MODULE_SAMPLE, 0);
- track->key_on(p_sample_number, _convert_event_length(p_length), delay_samples);
+ return track;
+ }
+
+ track = sequencer->create_controllable_track(internal_track_id, p_disposable);
+ ERR_FAIL_NULL_V_MSG(track, nullptr, "SiONDriver: Failed to allocate a track for playback. Pushing the limits?");
+ return track;
+}
+
+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(p_length < 0, nullptr, "SiONDriver: Sample length cannot be less than zero.");
+
+ int delay_samples = 0;
+ SiMMLTrack *track = _find_or_create_track(p_delay, p_quant, p_track_id, p_disposable, &delay_samples);
+ if (!track) {
+ return nullptr;
}
+ track->set_channel_module_type(SiONModuleType::MODULE_SAMPLE, 0);
+ track->key_on(p_sample_number, _convert_event_length(p_length), delay_samples);
+
return track;
}
SiMMLTrack *SiONDriver::note_on(int p_note, const Ref &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(p_length < 0, nullptr, "SiONDriver: Note length cannot be less than zero.");
- ERR_FAIL_COND_V_MSG(p_delay < 0, nullptr, "SiONDriver: Note delay cannot be less than zero.");
- int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE;
- double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
+ int delay_samples = 0;
+ SiMMLTrack *track = _find_or_create_track(p_delay, p_quant, p_track_id, p_disposable, &delay_samples);
+ if (!track) {
+ return nullptr;
+ }
- SiMMLTrack *track = nullptr;
+ if (p_voice.is_valid()) {
+ p_voice->update_track_voice(track);
+ }
+ track->key_on(p_note, _convert_event_length(p_length), delay_samples);
- // Check track ID conflicts.
- if (_note_on_exception_mode != NEM_IGNORE) {
- // Find a track with the same sound timings.
- track = sequencer->find_active_track(internal_track_id, delay_samples);
+ return track;
+}
- if (track && _note_on_exception_mode == NEM_REJECT) {
- return nullptr;
- }
- if (track && _note_on_exception_mode == NEM_SHIFT) {
- int step = sequencer->calculate_sample_length(p_quant);
- while (track) {
- delay_samples += step;
- track = sequencer->find_active_track(internal_track_id, delay_samples);
- }
- }
- }
+SiMMLTrack *SiONDriver::note_on_with_bend(int p_note, int p_note_to, double p_bend_length, const Ref &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(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.");
+ int delay_samples = 0;
+ SiMMLTrack *track = _find_or_create_track(p_delay, p_quant, p_track_id, p_disposable, &delay_samples);
if (!track) {
- track = sequencer->create_controllable_track(internal_track_id, p_disposable);
+ return nullptr;
}
- if (track) {
- if (p_voice.is_valid()) {
- p_voice->update_track_voice(track);
- }
- track->key_on(p_note, _convert_event_length(p_length), delay_samples);
+ if (p_voice.is_valid()) {
+ p_voice->update_track_voice(track);
}
+ track->key_on(p_note, _convert_event_length(p_length), delay_samples);
+ track->bend_note(p_note_to, _convert_event_length(p_bend_length));
return track;
}
@@ -749,7 +759,7 @@ TypedArray SiONDriver::note_off(int p_note, int p_track_id, double p
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray(), "SiONDriver: Note off delay cannot be less than zero.");
int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE;
- double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
+ int delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
TypedArray tracks;
for (SiMMLTrack *track : sequencer->get_tracks()) {
@@ -776,7 +786,7 @@ TypedArray SiONDriver::sequence_on(const Ref &p_data, cons
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray(), "SiONDriver: Sequence delay cannot be less than zero.");
int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_SEQUENCE;
- double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
+ int delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
int length_samples = sequencer->calculate_sample_length(p_length);
TypedArray tracks;
@@ -785,10 +795,13 @@ TypedArray SiONDriver::sequence_on(const Ref &p_data, cons
while (sequence) {
if (sequence->is_active()) {
SiMMLTrack *track = sequencer->create_controllable_track(internal_track_id, p_disposable);
+ ERR_FAIL_NULL_V_MSG(track, tracks, "SiONDriver: Failed to allocate a track for playback. Pushing the limits?");
+
track->sequence_on(p_data, sequence, length_samples, delay_samples);
if (p_voice.is_valid()) {
p_voice->update_track_voice(track);
}
+
tracks.push_back(track);
}
@@ -802,7 +815,7 @@ TypedArray SiONDriver::sequence_off(int p_track_id, double p_delay,
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray(), "SiONDriver: Sequence off delay cannot be less than zero.");
int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_SEQUENCE;
- double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
+ int delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant);
TypedArray tracks;
for (SiMMLTrack *track : sequencer->get_tracks()) {
@@ -1320,9 +1333,11 @@ void SiONDriver::_bind_methods() {
ClassDB::bind_method(D_METHOD("pause"), &SiONDriver::pause);
ClassDB::bind_method(D_METHOD("resume"), &SiONDriver::resume);
- ClassDB::bind_method(D_METHOD("play_sound", "sample_number", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::play_sound, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true));
+ 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));
ClassDB::bind_method(D_METHOD("note_off", "note", "track_id", "delay", "quantize", "stop_immediately"), &SiONDriver::note_off, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(false));
+
ClassDB::bind_method(D_METHOD("sequence_on", "data", "voice", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::sequence_on, DEFVAL((Object *)nullptr), DEFVAL(0), DEFVAL(0), DEFVAL(1), DEFVAL(0), DEFVAL(true));
ClassDB::bind_method(D_METHOD("sequence_off", "track_id", "delay", "quantize", "stop_with_reset"), &SiONDriver::sequence_off, DEFVAL(0), DEFVAL(1), DEFVAL(false));
diff --git a/src/sion_driver.h b/src/sion_driver.h
index d8ff8af..dc26b5d 100644
--- a/src/sion_driver.h
+++ b/src/sion_driver.h
@@ -163,6 +163,8 @@ class SiONDriver : public Node {
// Send the CHANGE_BPM event when position changes.
bool _notify_change_bpm_on_position_changed = true;
+ SiMMLTrack *_find_or_create_track(int p_track_id, double p_delay, double p_quant, bool p_disposable, int *r_delay_samples);
+
void _update_volume();
void _fade_callback(double p_value);
@@ -384,9 +386,11 @@ class SiONDriver : public Node {
void pause();
void resume();
- SiMMLTrack *play_sound(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 *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 &p_voice = Ref(), 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 &p_voice = Ref(), double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true);
TypedArray note_off(int p_note, int p_track_id = 0, double p_delay = 0, double p_quant = 0, bool p_stop_immediately = false);
+
TypedArray sequence_on(const Ref &p_data, const Ref &p_voice = Ref(), double p_length = 0, double p_delay = 0, double p_quant = 1, int p_track_id = 0, bool p_disposable = true);
TypedArray sequence_off(int p_track_id, double p_delay = 0, double p_quant = 1, bool p_stop_with_reset = false);