1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-01 02:58:11 +00:00

Piano: Add release

Notice that we are calculating release time according to the level when
the note is turned off rather than the sustain level. Naively using the
sustain level gives very long release times if you turn the note off
during attack, whereas this deterministically gives the same release
time.
This commit is contained in:
William McPherson 2020-02-05 18:00:16 +11:00 committed by Andreas Kling
parent ab9475a3f3
commit 59bde64ba6
5 changed files with 47 additions and 14 deletions

View file

@ -34,6 +34,7 @@ AudioEngine::AudioEngine()
set_sustain_impl(0);
set_attack(0);
set_decay(0);
set_release(0);
}
AudioEngine::~AudioEngine()
@ -62,6 +63,12 @@ void AudioEngine::fill_buffer(FixedArray<Sample>& buffer)
m_power[note] = m_sustain_level;
break;
case Release:
m_power[note] -= m_release_step[note];
if (m_power[note] <= 0) {
m_power[note] = 0;
m_envelope[note] = Done;
continue;
}
break;
default:
ASSERT_NOT_REACHED();
@ -158,6 +165,17 @@ double AudioEngine::noise() const
return w;
}
static inline double calculate_step(double distance, int milliseconds)
{
if (milliseconds == 0)
return distance;
constexpr double samples_per_millisecond = sample_rate / 1000.0;
double samples = milliseconds * samples_per_millisecond;
double step = distance / samples;
return step;
}
void AudioEngine::set_note(int note, Switch switch_note)
{
ASSERT(note >= 0 && note < note_count);
@ -171,8 +189,8 @@ void AudioEngine::set_note(int note, Switch switch_note)
} else {
if (m_note_on[note] >= 1) {
if (m_note_on[note] == 1) {
m_power[note] = 0;
m_envelope[note] = Done;
m_release_step[note] = calculate_step(m_power[note], m_release);
m_envelope[note] = Release;
}
--m_note_on[note];
}
@ -215,17 +233,6 @@ void AudioEngine::set_wave(Direction direction)
}
}
static inline double calculate_step(double distance, int milliseconds)
{
if (milliseconds == 0)
return distance;
constexpr double samples_per_millisecond = sample_rate / 1000.0;
double samples = milliseconds * samples_per_millisecond;
double step = distance / samples;
return step;
}
void AudioEngine::set_attack(int attack)
{
ASSERT(attack >= 0);
@ -253,6 +260,12 @@ void AudioEngine::set_sustain(int sustain)
set_decay(m_decay);
}
void AudioEngine::set_release(int release)
{
ASSERT(release >= 0);
m_release = release;
}
void AudioEngine::set_delay(int delay)
{
ASSERT(delay >= 0);

View file

@ -46,6 +46,7 @@ public:
int attack() const { return m_attack; }
int decay() const { return m_decay; }
int sustain() const { return m_sustain; }
int release() const { return m_release; }
int delay() const { return m_delay; }
int time() const { return m_time; }
int tick() const { return m_tick; }
@ -59,6 +60,7 @@ public:
void set_attack(int attack);
void set_decay(int decay);
void set_sustain(int sustain);
void set_release(int release);
void set_delay(int delay);
private:
@ -90,6 +92,8 @@ private:
double m_decay_step;
int m_sustain;
double m_sustain_level;
int m_release;
double m_release_step[note_count];
int m_delay { 0 };
int m_time { 0 };

View file

@ -53,6 +53,7 @@ KnobsWidget::KnobsWidget(GUI::Widget* parent, AudioEngine& audio_engine, MainWid
m_attack_label = GUI::Label::construct("Attack", m_labels_container);
m_decay_label = GUI::Label::construct("Decay", m_labels_container);
m_sustain_label = GUI::Label::construct("Sustain", m_labels_container);
m_release_label = GUI::Label::construct("Release", m_labels_container);
m_delay_label = GUI::Label::construct("Delay", m_labels_container);
m_values_container = GUI::Widget::construct(this);
@ -65,6 +66,7 @@ KnobsWidget::KnobsWidget(GUI::Widget* parent, AudioEngine& audio_engine, MainWid
m_attack_value = GUI::Label::construct(String::number(m_audio_engine.attack()), m_values_container);
m_decay_value = GUI::Label::construct(String::number(m_audio_engine.decay()), m_values_container);
m_sustain_value = GUI::Label::construct(String::number(m_audio_engine.sustain()), m_values_container);
m_release_value = GUI::Label::construct(String::number(m_audio_engine.release()), m_values_container);
m_delay_value = GUI::Label::construct(String::number(m_audio_engine.delay() / m_audio_engine.tick()), m_values_container);
m_knobs_container = GUI::Widget::construct(this);
@ -128,6 +130,17 @@ KnobsWidget::KnobsWidget(GUI::Widget* parent, AudioEngine& audio_engine, MainWid
m_sustain_value->set_text(String::number(new_sustain));
};
constexpr int max_release = 1000;
m_release_knob = GUI::Slider::construct(Orientation::Vertical, m_knobs_container);
m_release_knob->set_range(0, max_release);
m_release_knob->set_value(max_release - m_audio_engine.release());
m_release_knob->on_value_changed = [this](int value) {
int new_release = max_release - value;
m_audio_engine.set_release(new_release);
ASSERT(new_release == m_audio_engine.release());
m_release_value->set_text(String::number(new_release));
};
constexpr int max_delay = 8;
m_delay_knob = GUI::Slider::construct(Orientation::Vertical, m_knobs_container);
m_delay_knob->set_range(0, max_delay);

View file

@ -56,6 +56,7 @@ private:
RefPtr<GUI::Label> m_attack_label;
RefPtr<GUI::Label> m_decay_label;
RefPtr<GUI::Label> m_sustain_label;
RefPtr<GUI::Label> m_release_label;
RefPtr<GUI::Label> m_delay_label;
RefPtr<GUI::Widget> m_values_container;
@ -64,6 +65,7 @@ private:
RefPtr<GUI::Label> m_attack_value;
RefPtr<GUI::Label> m_decay_value;
RefPtr<GUI::Label> m_sustain_value;
RefPtr<GUI::Label> m_release_value;
RefPtr<GUI::Label> m_delay_value;
RefPtr<GUI::Widget> m_knobs_container;
@ -72,6 +74,7 @@ private:
RefPtr<GUI::Slider> m_attack_knob;
RefPtr<GUI::Slider> m_decay_knob;
RefPtr<GUI::Slider> m_sustain_knob;
RefPtr<GUI::Slider> m_release_knob;
RefPtr<GUI::Slider> m_delay_knob;
bool m_change_octave { true };

View file

@ -60,7 +60,7 @@ MainWidget::MainWidget(AudioEngine& audio_engine)
m_knobs_widget = KnobsWidget::construct(m_keys_and_knobs_container, audio_engine, *this);
m_knobs_widget->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
m_knobs_widget->set_preferred_size(300, 0);
m_knobs_widget->set_preferred_size(350, 0);
}
MainWidget::~MainWidget()