summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Applications/Piano/AudioEngine.cpp216
-rw-r--r--Applications/Piano/AudioEngine.h85
-rw-r--r--Applications/Piano/KeysWidget.cpp320
-rw-r--r--Applications/Piano/KeysWidget.h61
-rw-r--r--Applications/Piano/KnobsWidget.cpp131
-rw-r--r--Applications/Piano/KnobsWidget.h69
-rw-r--r--Applications/Piano/MainWidget.cpp142
-rw-r--r--Applications/Piano/MainWidget.h65
-rw-r--r--Applications/Piano/Makefile7
-rw-r--r--Applications/Piano/Music.h206
-rw-r--r--Applications/Piano/PianoWidget.cpp585
-rw-r--r--Applications/Piano/PianoWidget.h121
-rw-r--r--Applications/Piano/RollWidget.cpp154
-rw-r--r--Applications/Piano/RollWidget.h54
-rw-r--r--Applications/Piano/WaveWidget.cpp114
-rw-r--r--Applications/Piano/WaveWidget.h48
-rw-r--r--Applications/Piano/main.cpp29
17 files changed, 1646 insertions, 761 deletions
diff --git a/Applications/Piano/AudioEngine.cpp b/Applications/Piano/AudioEngine.cpp
new file mode 100644
index 0000000000..e32028d8ac
--- /dev/null
+++ b/Applications/Piano/AudioEngine.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AudioEngine.h"
+#include <limits>
+#include <math.h>
+
+AudioEngine::AudioEngine()
+{
+}
+
+AudioEngine::~AudioEngine()
+{
+}
+
+void AudioEngine::fill_buffer(FixedArray<Sample>& buffer)
+{
+ memset(buffer.data(), 0, buffer_size);
+
+ for (size_t i = 0; i < buffer.size(); ++i) {
+ for (size_t note = 0; note < note_count; ++note) {
+ if (!m_note_on[note])
+ continue;
+ double val = 0;
+ switch (m_wave) {
+ case Wave::Sine:
+ val = (volume * m_power[note]) * sine(note);
+ break;
+ case Wave::Saw:
+ val = (volume * m_power[note]) * saw(note);
+ break;
+ case Wave::Square:
+ val = (volume * m_power[note]) * square(note);
+ break;
+ case Wave::Triangle:
+ val = (volume * m_power[note]) * triangle(note);
+ break;
+ case Wave::Noise:
+ val = (volume * m_power[note]) * noise();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ buffer[i].left += val;
+ }
+ buffer[i].right = buffer[i].left;
+ }
+
+ if (m_decay) {
+ for (size_t note = 0; note < note_count; ++note) {
+ if (m_note_on[note]) {
+ m_power[note] -= m_decay / 100.0;
+ if (m_power[note] < 0)
+ m_power[note] = 0;
+ }
+ }
+ }
+
+ if (m_delay) {
+ if (m_delay_buffers.size() >= m_delay) {
+ auto to_blend = m_delay_buffers.dequeue();
+ for (size_t i = 0; i < to_blend->size(); ++i) {
+ buffer[i].left += (*to_blend)[i].left * 0.333333;
+ buffer[i].right += (*to_blend)[i].right * 0.333333;
+ }
+ }
+
+ auto delay_buffer = make<FixedArray<Sample>>(buffer.size());
+ memcpy(delay_buffer->data(), buffer.data(), buffer_size);
+ m_delay_buffers.enqueue(move(delay_buffer));
+ }
+
+ if (++m_time == m_tick)
+ m_time = 0;
+
+ memcpy(m_back_buffer_ptr->data(), buffer.data(), buffer_size);
+ swap(m_front_buffer_ptr, m_back_buffer_ptr);
+}
+
+// All of the information for these waves is on Wikipedia.
+
+double AudioEngine::sine(size_t note)
+{
+ double pos = note_frequencies[note] / sample_rate;
+ double sin_step = pos * 2 * M_PI;
+ double w = sin(m_pos[note]);
+ m_pos[note] += sin_step;
+ return w;
+}
+
+double AudioEngine::saw(size_t note)
+{
+ double saw_step = note_frequencies[note] / sample_rate;
+ double t = m_pos[note];
+ double w = (0.5 - (t - floor(t))) * 2;
+ m_pos[note] += saw_step;
+ return w;
+}
+
+double AudioEngine::square(size_t note)
+{
+ double pos = note_frequencies[note] / sample_rate;
+ double square_step = pos * 2 * M_PI;
+ double w = sin(m_pos[note]) >= 0 ? 1 : -1;
+ m_pos[note] += square_step;
+ return w;
+}
+
+double AudioEngine::triangle(size_t note)
+{
+ double triangle_step = note_frequencies[note] / sample_rate;
+ double t = m_pos[note];
+ double w = fabs(fmod((4 * t) + 1, 4) - 2) - 1;
+ m_pos[note] += triangle_step;
+ return w;
+}
+
+double AudioEngine::noise() const
+{
+ double random_percentage = static_cast<double>(rand()) / RAND_MAX;
+ double w = (random_percentage * 2) - 1;
+ return w;
+}
+
+void AudioEngine::set_note(int note, Switch switch_note)
+{
+ ASSERT(note >= 0 && note < note_count);
+
+ if (switch_note == On) {
+ if (m_note_on[note] == 0) {
+ m_pos[note] = 0;
+ m_power[note] = 0;
+ }
+ ++m_power[note];
+ ++m_note_on[note];
+ } else {
+ if (m_note_on[note] >= 1) {
+ if (--m_power[note] < 0)
+ m_power[note] = 0;
+ --m_note_on[note];
+ }
+ }
+
+ ASSERT(m_note_on[note] != std::numeric_limits<u8>::max());
+ ASSERT(m_power[note] >= 0);
+}
+
+void AudioEngine::set_note_current_octave(int note, Switch switch_note)
+{
+ set_note(note + octave_base(), switch_note);
+}
+
+void AudioEngine::set_octave(Direction direction)
+{
+ if (direction == Up) {
+ if (m_octave < octave_max)
+ ++m_octave;
+ } else {
+ if (m_octave > octave_min)
+ --m_octave;
+ }
+}
+
+void AudioEngine::set_wave(int wave)
+{
+ ASSERT(wave >= first_wave && wave <= last_wave);
+ m_wave = wave;
+}
+
+void AudioEngine::set_wave(Direction direction)
+{
+ if (direction == Up) {
+ if (++m_wave > last_wave)
+ m_wave = first_wave;
+ } else {
+ if (--m_wave < first_wave)
+ m_wave = last_wave;
+ }
+}
+
+void AudioEngine::set_decay(int decay)
+{
+ ASSERT(decay >= 0);
+ m_decay = decay;
+}
+
+void AudioEngine::set_delay(int delay)
+{
+ ASSERT(delay >= 0);
+ m_delay_buffers.clear();
+ m_delay = delay;
+}
diff --git a/Applications/Piano/AudioEngine.h b/Applications/Piano/AudioEngine.h
new file mode 100644
index 0000000000..14280a716f
--- /dev/null
+++ b/Applications/Piano/AudioEngine.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Music.h"
+#include <AK/FixedArray.h>
+#include <AK/Noncopyable.h>
+#include <AK/Queue.h>
+
+class AudioEngine {
+ AK_MAKE_NONCOPYABLE(AudioEngine)
+ AK_MAKE_NONMOVABLE(AudioEngine)
+public:
+ AudioEngine();
+ ~AudioEngine();
+
+ const FixedArray<Sample>& buffer() const { return *m_front_buffer_ptr; }
+ int octave() const { return m_octave; }
+ int octave_base() const { return (m_octave - octave_min) * 12; }
+ int wave() const { return m_wave; }
+ int decay() const { return m_decay; }
+ int delay() const { return m_delay; }
+ int time() const { return m_time; }
+ int tick() const { return m_tick; }
+
+ void fill_buffer(FixedArray<Sample>& buffer);
+ void set_note(int note, Switch);
+ void set_note_current_octave(int note, Switch);
+ void set_octave(Direction);
+ void set_wave(int wave);
+ void set_wave(Direction);
+ void set_decay(int decay);
+ void set_delay(int delay);
+
+private:
+ double sine(size_t note);
+ double saw(size_t note);
+ double square(size_t note);
+ double triangle(size_t note);
+ double noise() const;
+
+ FixedArray<Sample> m_front_buffer { sample_count };
+ FixedArray<Sample> m_back_buffer { sample_count };
+ FixedArray<Sample>* m_front_buffer_ptr { &m_front_buffer };
+ FixedArray<Sample>* m_back_buffer_ptr { &m_back_buffer };
+
+ Queue<NonnullOwnPtr<FixedArray<Sample>>> m_delay_buffers;
+
+ u8 m_note_on[note_count] { 0 };
+ double m_power[note_count]; // Initialized lazily.
+ double m_pos[note_count]; // Initialized lazily.
+
+ int m_octave { 4 };
+ int m_wave { first_wave };
+ int m_decay { 0 };
+ int m_delay { 0 };
+
+ int m_time { 0 };
+ int m_tick { 8 };
+};
diff --git a/Applications/Piano/KeysWidget.cpp b/Applications/Piano/KeysWidget.cpp
new file mode 100644
index 0000000000..36d4ce19e6
--- /dev/null
+++ b/Applications/Piano/KeysWidget.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "KeysWidget.h"
+#include "AudioEngine.h"
+#include <LibGUI/GPainter.h>
+
+KeysWidget::KeysWidget(GWidget* parent, AudioEngine& audio_engine)
+ : GFrame(parent)
+ , m_audio_engine(audio_engine)
+{
+ set_frame_thickness(2);
+ set_frame_shadow(FrameShadow::Sunken);
+ set_frame_shape(FrameShape::Container);
+ set_fill_with_background_color(true);
+}
+
+KeysWidget::~KeysWidget()
+{
+}
+
+int KeysWidget::mouse_note() const
+{
+ if (m_mouse_down && m_mouse_note + m_audio_engine.octave_base() < note_count)
+ return m_mouse_note; // Can be -1.
+ else
+ return -1;
+}
+
+void KeysWidget::set_key(int key, Switch switch_key)
+{
+ if (key == -1 || key + m_audio_engine.octave_base() >= note_count)
+ return;
+
+ if (switch_key == On) {
+ ++m_key_on[key];
+ } else {
+ if (m_key_on[key] >= 1)
+ --m_key_on[key];
+ }
+ ASSERT(m_key_on[key] <= 2);
+
+ m_audio_engine.set_note_current_octave(key, switch_key);
+}
+
+int KeysWidget::key_code_to_key(int key_code) const
+{
+ switch (key_code) {
+ case Key_A:
+ return 0;
+ case Key_W:
+ return 1;
+ case Key_S:
+ return 2;
+ case Key_E:
+ return 3;
+ case Key_D:
+ return 4;
+ case Key_F:
+ return 5;
+ case Key_T:
+ return 6;
+ case Key_G:
+ return 7;
+ case Key_Y:
+ return 8;
+ case Key_H:
+ return 9;
+ case Key_U:
+ return 10;
+ case Key_J:
+ return 11;
+ case Key_K:
+ return 12;
+ case Key_O:
+ return 13;
+ case Key_L:
+ return 14;
+ case Key_P:
+ return 15;
+ case Key_Semicolon:
+ return 16;
+ case Key_Apostrophe:
+ return 17;
+ case Key_RightBracket:
+ return 18;
+ case Key_Return:
+ return 19;
+ default:
+ return -1;
+ }
+}
+
+constexpr int white_key_width = 24;
+constexpr int black_key_width = 16;
+constexpr int black_key_x_offset = black_key_width / 2;
+constexpr int black_key_height = 60;
+
+constexpr char white_key_labels[] = {
+ 'A',
+ 'S',
+ 'D',
+ 'F',
+ 'G',
+ 'H',
+ 'J',
+ 'K',
+ 'L',
+ ';',
+ '\'',
+ 'r',
+};
+constexpr int white_key_labels_count = sizeof(white_key_labels) / sizeof(char);
+
+constexpr char black_key_labels[] = {
+ 'W',
+ 'E',
+ 'T',
+ 'Y',
+ 'U',
+ 'O',
+ 'P',
+ ']',
+};
+constexpr int black_key_labels_count = sizeof(black_key_labels) / sizeof(char);
+
+constexpr int black_key_offsets[] = {
+ white_key_width,
+ white_key_width * 2,
+ white_key_width,
+ white_key_width,
+ white_key_width * 2,
+};
+
+constexpr int white_key_note_accumulator[] = {
+ 2,
+ 2,
+ 1,
+ 2,
+ 2,
+ 2,
+ 1,
+};
+
+constexpr int black_key_note_accumulator[] = {
+ 2,
+ 3,
+ 2,
+ 2,
+ 3,
+};
+
+void KeysWidget::paint_event(GPaintEvent& event)
+{
+ GPainter painter(*this);
+ painter.translate(frame_thickness(), frame_thickness());
+
+ int note = 0;
+ int x = 0;
+ int i = 0;
+ for (;;) {
+ Rect rect(x, 0, white_key_width, frame_inner_rect().height());
+ painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::White);
+ painter.draw_rect(rect, Color::Black);
+ if (i < white_key_labels_count) {
+ rect.set_height(rect.height() * 1.5);
+ painter.draw_text(rect, StringView(&white_key_labels[i], 1), TextAlignment::Center, Color::Black);
+ }
+
+ note += white_key_note_accumulator[i % white_keys_per_octave];
+ x += white_key_width;
+ ++i;
+
+ if (note + m_audio_engine.octave_base() >= note_count)
+ break;
+ if (x >= frame_inner_rect().width())
+ break;
+ }
+
+ note = 1;
+ x = white_key_width - black_key_x_offset;
+ i = 0;
+ for (;;) {
+ Rect rect(x, 0, black_key_width, black_key_height);
+ painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::Black);
+ painter.draw_rect(rect, Color::Black);
+ if (i < black_key_labels_count) {
+ rect.set_height(rect.height() * 1.5);
+ painter.draw_text(rect, StringView(&black_key_labels[i], 1), TextAlignment::Center, Color::White);
+ }
+
+ note += black_key_note_accumulator[i % black_keys_per_octave];
+ x += black_key_offsets[i % black_keys_per_octave];
+ ++i;
+
+ if (note + m_audio_engine.octave_base() >= note_count)
+ break;
+ if (x >= frame_inner_rect().width())
+ break;
+ }
+
+ GFrame::paint_event(event);
+}
+
+constexpr int notes_per_white_key[] = {
+ 1,
+ 3,
+ 5,
+ 6,
+ 8,
+ 10,
+ 12,
+};
+
+// Keep in mind that in any of these functions a note value can be out of
+// bounds. Bounds checking is done in set_key().
+
+static inline int note_from_white_keys(int white_keys)
+{
+ int octaves = white_keys / white_keys_per_octave;
+ int remainder = white_keys % white_keys_per_octave;
+ int notes_from_octaves = octaves * notes_per_octave;
+ int notes_from_remainder = notes_per_white_key[remainder];
+ int note = (notes_from_octaves + notes_from_remainder) - 1;
+ return note;
+}
+
+int KeysWidget::note_for_event_position(Point point) const
+{
+ if (!frame_inner_rect().contains(point))
+ return -1;
+
+ point.move_by(-frame_thickness(), -frame_thickness());
+
+ int white_keys = point.x() / white_key_width;
+ int note = note_from_white_keys(white_keys);
+
+ bool black_key_on_left = note != 0 && key_pattern[(note - 1) % notes_per_octave] == Black;
+ if (black_key_on_left) {
+ int black_key_x = (white_keys * white_key_width) - black_key_x_offset;
+ Rect black_key(black_key_x, 0, black_key_width, black_key_height);
+ if (black_key.contains(point))
+ return note - 1;
+ }
+
+ bool black_key_on_right = key_pattern[(note + 1) % notes_per_octave] == Black;
+ if (black_key_on_right) {
+ int black_key_x = ((white_keys + 1) * white_key_width) - black_key_x_offset;
+ Rect black_key(black_key_x, 0, black_key_width, black_key_height);
+ if (black_key.contains(point))
+ return note + 1;
+ }
+
+ return note;
+}
+
+void KeysWidget::mousedown_event(GMouseEvent& event)
+{
+ if (event.button() != Left)
+ return;
+
+ m_mouse_down = true;
+
+ m_mouse_note = note_for_event_position(event.position());
+
+ set_key(m_mouse_note, On);
+ update();
+}
+
+void KeysWidget::mouseup_event(GMouseEvent& event)
+{
+ if (event.button() != Left)
+ return;
+
+ m_mouse_down = false;
+
+ set_key(m_mouse_note, Off);
+ update();
+}
+
+void KeysWidget::mousemove_event(GMouseEvent& event)
+{
+ if (!m_mouse_down)
+ return;
+
+ int new_mouse_note = note_for_event_position(event.position());
+
+ if (m_mouse_note == new_mouse_note)
+ return;
+
+ set_key(m_mouse_note, Off);
+ set_key(new_mouse_note, On);
+ update();
+
+ m_mouse_note = new_mouse_note;
+}
diff --git a/Applications/Piano/KeysWidget.h b/Applications/Piano/KeysWidget.h
new file mode 100644
index 0000000000..c8cdb315ae
--- /dev/null
+++ b/Applications/Piano/KeysWidget.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Music.h"
+#include <LibGUI/GFrame.h>
+
+class AudioEngine;
+
+class KeysWidget final : public GFrame {
+ C_OBJECT(KeysWidget)
+public:
+ virtual ~KeysWidget() override;
+
+ int key_code_to_key(int key_code) const;
+ int mouse_note() const;
+
+ void set_key(int key, Switch);
+
+private:
+ KeysWidget(GWidget* parent, AudioEngine&);
+
+ virtual void paint_event(GPaintEvent&) override;
+ virtual void mousedown_event(GMouseEvent&) override;
+ virtual void mouseup_event(GMouseEvent&) override;
+ virtual void mousemove_event(GMouseEvent&) override;
+
+ int note_for_event_position(Point) const;
+
+ AudioEngine& m_audio_engine;
+
+ u8 m_key_on[note_count] { 0 };
+
+ bool m_mouse_down { false };
+ int m_mouse_note { -1 };
+};
diff --git a/Applications/Piano/KnobsWidget.cpp b/Applications/Piano/KnobsWidget.cpp
new file mode 100644
index 0000000000..0a55467b5f
--- /dev/null
+++ b/Applications/Piano/KnobsWidget.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "KnobsWidget.h"
+#include "AudioEngine.h"
+#include "MainWidget.h"
+#include <LibGUI/GBoxLayout.h>
+#include <LibGUI/GLabel.h>
+#include <LibGUI/GSlider.h>
+
+KnobsWidget::KnobsWidget(GWidget* parent, AudioEngine& audio_engine, MainWidget& main_widget)
+ : GFrame(parent)
+ , m_audio_engine(audio_engine)
+ , m_main_widget(main_widget)
+{
+ set_frame_thickness(2);
+ set_frame_shadow(FrameShadow::Sunken);
+ set_frame_shape(FrameShape::Container);
+ set_layout(make<GBoxLayout>(Orientation::Vertical));
+ set_fill_with_background_color(true);
+
+ m_labels_container = GWidget::construct(this);
+ m_labels_container->set_layout(make<GBoxLayout>(Orientation::Horizontal));
+ m_labels_container->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
+ m_labels_container->set_preferred_size(0, 20);
+
+ m_octave_label = GLabel::construct("Octave", m_labels_container);
+ m_wave_label = GLabel::construct("Wave", m_labels_container);
+ m_decay_label = GLabel::construct("Decay", m_labels_container);
+ m_delay_label = GLabel::construct("Delay", m_labels_container);
+
+ m_values_container = GWidget::construct(this);
+ m_values_container->set_layout(make<GBoxLayout>(Orientation::Horizontal));
+ m_values_container->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
+ m_values_container->set_preferred_size(0, 10);
+
+ m_octave_value = GLabel::construct(String::number(m_audio_engine.octave()), m_values_container);
+ m_wave_value = GLabel::construct(wave_strings[m_audio_engine.wave()], m_values_container);
+ m_decay_value = GLabel::construct(String::number(m_audio_engine.decay()), m_values_container);
+ m_delay_value = GLabel::construct(String::number(m_audio_engine.delay() / m_audio_engine.tick()), m_values_container);
+
+ m_knobs_container = GWidget::construct(this);
+ m_knobs_container->set_layout(make<GBoxLayout>(Orientation::Horizontal));
+
+ // FIXME: Implement vertical flipping in GSlider, not here.
+
+ m_octave_knob = GSlider::construct(Orientation::Vertical, m_knobs_container);
+ m_octave_knob->set_tooltip("Z: octave down, X: octave up");
+ m_octave_knob->set_range(octave_min - 1, octave_max - 1);
+ m_octave_knob->set_value(m_audio_engine.octave() - 1);
+ m_octave_knob->on_value_changed = [this](int value) {
+ int new_octave = octave_max - value;
+ if (m_change_octave)
+ m_main_widget.set_octave_and_ensure_note_change(new_octave == m_audio_engine.octave() + 1 ? Up : Down);
+ ASSERT(new_octave == m_audio_engine.octave());
+ m_octave_value->set_text(String::number(new_octave));
+ };
+
+ m_wave_knob = GSlider::construct(Orientation::Vertical, m_knobs_container);
+ m_wave_knob->set_tooltip("C: cycle through waveforms");
+ m_wave_knob->set_range(0, last_wave);
+ m_wave_knob->set_value(last_wave - m_audio_engine.wave());
+ m_wave_knob->on_value_changed = [this](int value) {
+ int new_wave = last_wave - value;
+ m_audio_engine.set_wave(new_wave);
+ ASSERT(new_wave == m_audio_engine.wave());
+ m_wave_value->set_text(wave_strings[new_wave]);
+ };
+
+ constexpr int max_decay = 20;
+ m_decay_knob = GSlider::construct(Orientation::Vertical, m_knobs_container);
+ m_decay_knob->set_range(0, max_decay);
+ m_decay_knob->set_value(max_decay);
+ m_decay_knob->on_value_changed = [this](int value) {
+ int new_decay = max_decay - value;
+ m_audio_engine.set_decay(new_decay);
+ ASSERT(new_decay == m_audio_engine.decay());
+ m_decay_value->set_text(String::number(new_decay));
+ };
+
+ constexpr int max_delay = 8;
+ m_delay_knob = GSlider::construct(Orientation::Vertical, m_knobs_container);
+ m_delay_knob->set_range(0, max_delay);
+ m_delay_knob->set_value(max_delay);
+ m_delay_knob->on_value_changed = [this](int value) {
+ int new_delay = m_audio_engine.tick() * (max_delay - value);
+ m_audio_engine.set_delay(new_delay);
+ ASSERT(new_delay == m_audio_engine.delay());
+ m_delay_value->set_text(String::number(new_delay / m_audio_engine.tick()));
+ };
+}
+
+KnobsWidget::~KnobsWidget()
+{
+}
+
+void KnobsWidget::update_knobs()
+{
+ m_wave_knob->set_value(last_wave - m_audio_engine.wave());
+
+ // FIXME: This is needed because when the slider is changed directly, it
+ // needs to change the octave, but if the octave was changed elsewhere, we
+ // need to change the slider without changing the octave.
+ m_change_octave = false;
+ m_octave_knob->set_value(octave_max - m_audio_engine.octave());
+ m_change_octave = true;
+}
diff --git a/Applications/Piano/KnobsWidget.h b/Applications/Piano/KnobsWidget.h
new file mode 100644
index 0000000000..a2f6f2f0e6
--- /dev/null
+++ b/Applications/Piano/KnobsWidget.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/GFrame.h>
+
+class GSlider;
+class GLabel;
+class AudioEngine;
+class MainWidget;
+
+class KnobsWidget final : public GFrame {
+ C_OBJECT(KnobsWidget)
+public:
+ virtual ~KnobsWidget() override;
+
+ void update_knobs();
+
+private:
+ KnobsWidget(GWidget* parent, AudioEngine&, MainWidget&);
+
+ AudioEngine& m_audio_engine;
+ MainWidget& m_main_widget;
+
+ RefPtr<GWidget> m_labels_container;
+ RefPtr<GLabel> m_octave_label;
+ RefPtr<GLabel> m_wave_label;
+ RefPtr<GLabel> m_decay_label;
+ RefPtr<GLabel> m_delay_label;
+
+ RefPtr<GWidget> m_values_container;
+ RefPtr<GLabel> m_octave_value;
+ RefPtr<GLabel> m_wave_value;
+ RefPtr<GLabel> m_decay_value;
+ RefPtr<GLabel> m_delay_value;
+
+ RefPtr<GWidget> m_knobs_container;
+ RefPtr<GSlider> m_octave_knob;
+ RefPtr<GSlider> m_wave_knob;
+ RefPtr<GSlider> m_decay_knob;
+ RefPtr<GSlider> m_delay_knob;
+
+ bool m_change_octave { true };
+};
diff --git a/Applications/Piano/MainWidget.cpp b/Applications/Piano/MainWidget.cpp
new file mode 100644
index 0000000000..5a8c006ae7
--- /dev/null
+++ b/Applications/Piano/MainWidget.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "MainWidget.h"
+#include "AudioEngine.h"
+#include "KeysWidget.h"
+#include "KnobsWidget.h"
+#include "RollWidget.h"
+#include "WaveWidget.h"
+#include <LibGUI/GBoxLayout.h>
+
+MainWidget::MainWidget(AudioEngine& audio_engine)
+ : m_audio_engine(audio_engine)
+{
+ set_layout(make<GBoxLayout>(Orientation::Vertical));
+ layout()->set_spacing(2);
+ layout()->set_margins({ 2, 2, 2, 2 });
+ set_fill_with_background_color(true);
+
+ m_wave_widget = WaveWidget::construct(this, audio_engine);
+ m_wave_widget->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
+ m_wave_widget->set_preferred_size(0, 100);
+
+ m_roll_widget = RollWidget::construct(this, audio_engine);
+ m_roll_widget->set_size_policy(SizePolicy::Fill, SizePolicy::Fill);
+ m_roll_widget->set_preferred_size(0, 300);
+
+ m_keys_and_knobs_container = GWidget::construct(this);
+ m_keys_and_knobs_container->set_layout(make<GBoxLayout>(Orientation::Horizontal));
+ m_keys_and_knobs_container->layout()->set_spacing(2);
+ m_keys_and_knobs_container->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
+ m_keys_and_knobs_container->set_preferred_size(0, 100);
+ m_keys_and_knobs_container->set_fill_with_background_color(true);
+
+ m_keys_widget = KeysWidget::construct(m_keys_and_knobs_container, audio_engine);
+
+ m_knobs_widget = KnobsWidget::construct(m_keys_and_knobs_container, audio_engine, *this);
+ m_knobs_widget->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill);
+ m_knobs_widget->set_preferred_size(200, 0);
+}
+
+MainWidget::~MainWidget()
+{
+}
+
+// FIXME: There are some unnecessary calls to update() throughout this program,
+// which are an easy target for optimization.
+
+void MainWidget::custom_event(CCustomEvent&)
+{
+ m_wave_widget->update();
+
+ if (m_audio_engine.time() == 0)
+ m_roll_widget->update_roll();
+}
+
+void MainWidget::keydown_event(GKeyEvent& event)
+{
+ // This is to stop held-down keys from creating multiple events.
+ if (m_keys_pressed[event.key()])
+ return;
+ else
+ m_keys_pressed[event.key()] = true;
+
+ note_key_action(event.key(), On);
+ special_key_action(event.key());
+ m_keys_widget->update();
+}
+
+void MainWidget::keyup_event(GKeyEvent& event)
+{
+ m_keys_pressed[event.key()] = false;
+
+ note_key_action(event.key(), Off);
+ m_keys_widget->update();
+}
+
+void MainWidget::note_key_action(int key_code, Switch switch_note)
+{
+ int key = m_keys_widget->key_code_to_key(key_code);
+ m_keys_widget->set_key(key, switch_note);
+}
+
+void MainWidget::special_key_action(int key_code)
+{
+ switch (key_code) {
+ case Key_Z:
+ set_octave_and_ensure_note_change(Down);
+ break;
+ case Key_X:
+ set_octave_and_ensure_note_change(Up);
+ break;
+ case Key_C:
+ m_audio_engine.set_wave(Up);
+ m_knobs_widget->update_knobs();
+ break;
+ }
+}
+
+void MainWidget::set_octave_and_ensure_note_change(Direction direction)
+{
+ m_keys_widget->set_key(m_keys_widget->mouse_note(), Off);
+ for (int i = 0; i < key_code_count; ++i) {
+ if (m_keys_pressed[i])
+ note_key_action(i, Off);
+ }
+
+ m_audio_engine.set_octave(direction);
+
+ m_keys_widget->set_key(m_keys_widget->mouse_note(), On);
+ for (int i = 0; i < key_code_count; ++i) {
+ if (m_keys_pressed[i])
+ note_key_action(i, On);
+ }
+
+ m_knobs_widget->update_knobs();
+ m_keys_widget->update();
+}
diff --git a/Applications/Piano/MainWidget.h b/Applications/Piano/MainWidget.h
new file mode 100644
index 0000000000..5f1fce8afd
--- /dev/null
+++ b/Applications/Piano/MainWidget.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/GWidget.h>
+#include <Music.h>
+
+class AudioEngine;
+class WaveWidget;
+class RollWidget;
+class KeysWidget;
+class KnobsWidget;
+
+class MainWidget final : public GWidget {
+ C_OBJECT(MainWidget)
+public:
+ virtual ~MainWidget() override;
+
+ void set_octave_and_ensure_note_change(Direction);
+
+private:
+ explicit MainWidget(AudioEngine&);
+
+ virtual void keydown_event(GKeyEvent&) override;
+ virtual void keyup_event(GKeyEvent&) override;
+ virtual void custom_event(CCustomEvent&) override;
+
+ void note_key_action(int key_code, Switch);
+ void special_key_action(int key_code);
+
+ AudioEngine& m_audio_engine;
+
+ RefPtr<WaveWidget> m_wave_widget;
+ RefPtr<RollWidget> m_roll_widget;
+ RefPtr<GWidget> m_keys_and_knobs_container;
+ RefPtr<KeysWidget> m_keys_widget;
+ RefPtr<KnobsWidget> m_knobs_widget;
+
+ bool m_keys_pressed[key_code_count] { false };
+};
diff --git a/Applications/Piano/Makefile b/Applications/Piano/Makefile
index f39d2ba9f6..718820b211 100644
--- a/Applications/Piano/Makefile
+++ b/Applications/Piano/Makefile
@@ -1,5 +1,10 @@
OBJS = \
- PianoWidget.o \
+ AudioEngine.o \
+ MainWidget.o \
+ WaveWidget.o \
+ RollWidget.o \
+ KeysWidget.o \
+ KnobsWidget.o \
main.o
PROGRAM = Piano
diff --git a/Applications/Piano/Music.h b/Applications/Piano/Music.h
index 26910b456d..716a17ddfc 100644
--- a/Applications/Piano/Music.h
+++ b/Applications/Piano/Music.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -27,62 +28,185 @@
#pragma once
#include <AK/Types.h>
+#include <LibDraw/Color.h>
namespace Music {
+// CD quality
+// - Stereo
+// - 16 bit
+// - 44,100 samples/sec
+// - 1,411.2 kbps
+
struct Sample {
i16 left;
i16 right;
};
-enum WaveType { Sine, Saw, Square, Triangle, Noise, InvalidWave };
+constexpr int sample_count = 1024;
+
+constexpr int buffer_size = sample_count * sizeof(Sample);
+
+constexpr double sample_rate = 44100;
+
+constexpr double volume = 1800;
-enum PianoKey {
- K_None,
- K_C1, K_Db1, K_D1, K_Eb1, K_E1, K_F1, K_Gb1, K_G1, K_Ab1, K_A1, K_Bb1, K_B1,
- K_C2, K_Db2, K_D2, K_Eb2, K_E2, K_F2, K_Gb2, K_G2,
+enum Switch {
+ Off,
+ On,
};
-inline bool is_white(PianoKey n)
-{
- switch (n) {
- case K_C1:
- case K_D1:
- case K_E1:
- case K_F1:
- case K_G1:
- case K_A1:
- case K_B1:
- case K_C2:
- case K_D2:
- case K_E2:
- case K_F2:
- case K_G2:
- return true;
- default:
- return false;
- }
-}
+enum Direction {
+ Down,
+ Up,
+};
+
+enum Wave {
+ Sine,
+ Triangle,
+ Square,
+ Saw,
+ Noise,
+};
-enum Note {
- C1, Db1, D1, Eb1, E1, F1, Gb1, G1, Ab1, A1, Bb1, B1,
- C2, Db2, D2, Eb2, E2, F2, Gb2, G2, Ab2, A2, Bb2, B2,
- C3, Db3, D3, Eb3, E3, F3, Gb3, G3, Ab3, A3, Bb3, B3,
- C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4, A4, Bb4, B4,
- C5, Db5, D5, Eb5, E5, F5, Gb5, G5, Ab5, A5, Bb5, B5,
- C6, Db6, D6, Eb6, E6, F6, Gb6, G6, Ab6, A6, Bb6, B6,
- C7, Db7, D7, Eb7, E7, F7, Gb7, G7, Ab7, A7, Bb7, B7,
+constexpr const char* wave_strings[] = {
+ "Sine",
+ "Triangle",
+ "Square",
+ "Saw",
+ "Noise",
};
-const double note_frequency[] = {
- /* Octave 1 */ 32.70, 34.65, 36.71, 38.89, 41.20, 43.65, 46.25, 49.00, 51.91, 55.00, 58.27, 61.74,
- /* Octave 2 */ 65.41, 69.30, 73.42, 77.78, 82.41, 87.31, 92.50, 98.00, 103.83, 110.00, 116.54, 123.47,
- /* Octave 3 */ 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94,
- /* Octave 4 */ 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88,
- /* Octave 5 */ 523.25, 554.37, 587.33, 622.25, 659.25, 698.46, 739.99, 783.99, 830.61, 880.00, 932.33, 987.77,
- /* Octave 6 */ 1046.50, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760.00, 1864.66, 1975.53,
- /* Octave 7 */ 2093.00, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520.00, 3729.31, 3951.07,
+constexpr int first_wave = Sine;
+constexpr int last_wave = Noise;
+
+enum KeyColor {
+ White,
+ Black,
+};
+
+constexpr KeyColor key_pattern[] = {
+ White,
+ Black,
+ White,
+ Black,
+ White,
+ White,
+ Black,
+ White,
+ Black,
+ White,
+ Black,
+ White,
+};
+
+const Color note_pressed_color(64, 64, 255);
+const Color column_playing_color(128, 128, 255);
+
+constexpr int notes_per_octave = 12;
+constexpr int white_keys_per_octave = 7;
+constexpr int black_keys_per_octave = 5;
+constexpr int octave_min = 1;
+constexpr int octave_max = 7;
+
+// Equal temperament, A = 440Hz
+// We calculate note frequencies relative to A4:
+// 440.0 * pow(pow(2.0, 1.0 / 12.0), N)
+// Where N is the note distance from A.
+constexpr double note_frequencies[] = {
+ // Octave 1
+ 32.703195662574764,
+ 34.647828872108946,
+ 36.708095989675876,
+ 38.890872965260044,
+ 41.203444614108669,
+ 43.653528929125407,
+ 46.249302838954222,
+ 48.99942949771858,
+ 51.913087197493056,
+ 54.999999999999915,
+ 58.270470189761156,
+ 61.735412657015416,
+ // Octave 2
+ 65.406391325149571,
+ 69.295657744217934,
+ 73.416191979351794,
+ 77.781745930520117,
+ 82.406889228217381,
+ 87.307057858250872,
+ 92.4986056779085,
+ 97.998858995437217,
+ 103.82617439498618,
+ 109.99999999999989,
+ 116.54094037952237,
+ 123.4708253140309,
+ // Octave 3
+ 130.8127826502992,
+ 138.59131548843592,
+ 146.83238395870364,
+ 155.56349186104035,
+ 164.81377845643485,
+ 174.61411571650183,
+ 184.99721135581709,
+ 195.99771799087452,
+ 207.65234878997245,
+ 219.99999999999989,
+ 233.08188075904488,
+ 246.94165062806198,
+ // Octave 4
+ 261.62556530059851,
+ 277.18263097687202,
+ 293.66476791740746,
+ 311.12698372208081,
+ 329.62755691286986,
+ 349.22823143300383,
+ 369.99442271163434,
+ 391.99543598174927,
+ 415.30469757994513,
+ 440,
+ 466.16376151808993,
+ 493.88330125612413,
+ // Octave 5
+ 523.25113060119736,
+ 554.36526195374427,
+ 587.32953583481526,
+ 622.25396744416196,
+ 659.25511382574007,
+ 698.456462866008,
+ 739.98884542326903,
+ 783.99087196349899,
+ 830.60939515989071,
+ 880.00000000000034,
+ 932.32752303618031,
+ 987.76660251224882,
+ // Octave 6
+ 1046.5022612023952,
+ 1108.7305239074892,
+ 1174.659071669631,
+ 1244.5079348883246,
+ 1318.5102276514808,
+ 1396.9129257320169,
+ 1479.977690846539,
+ 1567.9817439269987,
+ 1661.2187903197821,
+ 1760.000000000002,
+ 1864.6550460723618,
+ 1975.5332050244986,
+ // Octave 7
+ 2093.0045224047913,
+ 2217.4610478149793,
+ 2349.3181433392633,
+ 2489.0158697766506,
+ 2637.020455302963,
+ 2793.8258514640347,
+ 2959.9553816930793,
+ 3135.9634878539991,
+ 3322.437580639566,
+ 3520.0000000000055,
+ 3729.3100921447249,
+ 3951.0664100489994,
};
+constexpr int note_count = sizeof(note_frequencies) / sizeof(double);
}
diff --git a/Applications/Piano/PianoWidget.cpp b/Applications/Piano/PianoWidget.cpp
deleted file mode 100644
index 0703c402e5..0000000000
--- a/Applications/Piano/PianoWidget.cpp
+++ /dev/null
@@ -1,585 +0,0 @@
-/*
- * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "PianoWidget.h"
-#include <AK/Queue.h>
-#include <LibDraw/GraphicsBitmap.h>
-#include <LibGUI/GPainter.h>
-#include <math.h>
-
-PianoWidget::PianoWidget()
-{
- set_font(Font::default_fixed_width_font());
-}
-
-PianoWidget::~PianoWidget()
-{
-}
-
-void PianoWidget::paint_event(GPaintEvent& event)
-{
- GPainter painter(*this);
- painter.add_clip_rect(event.rect());
-
- painter.fill_rect(event.rect(), Color::Black);
-
- render_wave(painter);
- render_piano(painter);
- render_knobs(painter);
- render_roll(painter);
-}
-
-void PianoWidget::fill_audio_buffer(uint8_t* stream, int len)
-{
- if (++m_time == m_tick) {
- m_time = 0;
- change_roll_column();
- }
-
- m_sample_count = len / sizeof(Sample);
- memset(stream, 0, len);
-
- auto* sst = (Sample*)stream;
- for (int i = 0; i < m_sample_count; ++i) {
- static const double volume = 1800;
- for (size_t n = 0; n < (sizeof(m_note_on) / sizeof(u8)); ++n) {
- if (!m_note_on[n])
- continue;
- double val = 0;
- switch (m_wave_type) {
- case WaveType::Sine:
- val = ((volume * m_power[n]) * w_sine(n));
- break;
- case WaveType::Saw:
- val = ((volume * m_power[n]) * w_saw(n));
- break;
- case WaveType::Square:
- val = ((volume * m_power[n]) * w_square(n));
- break;
- case WaveType::Triangle:
- val = ((volume * m_power[n]) * w_triangle(n));
- break;
- case WaveType::Noise:
- val = ((volume * m_power[n]) * w_noise());
- break;
- }
- sst[i].left += val;
- }
- sst[i].right = sst[i].left;
- }
-
- // Decay pressed notes.
- if (m_decay_enabled) {
- for (size_t n = 0; n < (sizeof(m_note_on) / sizeof(u8)); ++n) {
- if (m_note_on[n])
- m_power[n] *= 0.965;
- }
- }
-
- static Queue<Sample*> delay_frames;
- static const int delay_length_in_frames = m_tick * 4;
-
- if (m_delay_enabled) {
- if (delay_frames.size() >= delay_length_in_frames) {
- auto* to_blend = delay_frames.dequeue();
- for (int i = 0; i < m_sample_count; ++i) {
- sst[i].left += to_blend[i].left * 0.333333;
- sst[i].right += to_blend[i].right * 0.333333;
- }
- delete[] to_blend;
- }
- Sample* frame = new Sample[m_sample_count];
- memcpy(frame, sst, m_sample_count * sizeof(Sample));
-
- delay_frames.enqueue(frame);
- }
-
- ASSERT(len <= 2048 * (int)sizeof(Sample));
- memcpy(m_back_buffer, (Sample*)stream, len);
- swap(m_front_buffer, m_back_buffer);
-}
-
-double PianoWidget::w_sine(size_t n)
-{
- double pos = note_frequency[n] / 44100.0;
- double sin_step = pos * 2 * M_PI;
- double w = sin(m_sin_pos[n]);
- m_sin_pos[n] += sin_step;
- return w;
-}
-
-static inline double hax_floor(double t)
-{
- return (int)t;
-}
-
-double PianoWidget::w_saw(size_t n)
-{
- double saw_step = note_frequency[n] / 44100.0;
- double t = m_saw_pos[n];
- double w = (0.5 - (t - hax_floor(t))) * 2;
- //printf("w: %g, step: %g\n", w, saw_step);
- m_saw_pos[n] += saw_step;
- return w;
-}
-
-double PianoWidget::w_square(size_t n)
-{
- double pos = note_frequency[n] / 44100.0;
- double square_step = pos * 2 * M_PI;
- double w = sin(m_square_pos[n]);
- if (w > 0)
- w = 1;
- else
- w = -1;
- //printf("w: %g, step: %g\n", w, square_step);
- m_square_pos[n] += square_step;
- return w;
-}
-
-double PianoWidget::w_triangle(size_t n)
-{
- double triangle_step = note_frequency[n] / 44100.0;
- double t = m_triangle_pos[n];
- double w = fabs(fmod((4 * t) + 1, 4) - 2) - 1;
- m_triangle_pos[n] += triangle_step;
- return w;
-}
-
-double PianoWidget::w_noise()
-{
- return (((double)rand() / RAND_MAX) * 2.0) - 1.0;
-}
-
-int PianoWidget::octave_base() const
-{
- return (m_octave - m_octave_min) * 12;
-}
-
-struct KeyDefinition {
- int index;
- PianoKey piano_key;
- String label;
- KeyCode key_code;
-};
-
-const KeyDefinition key_definitions[] = {
- { 0, K_C1, "A", KeyCode::Key_A },
- { 1, K_D1, "S", KeyCode::Key_S },
- { 2, K_E1, "D", KeyCode::Key_D },
- { 3, K_F1, "F", KeyCode::Key_F },
- { 4, K_G1, "G", KeyCode::Key_G },
- { 5, K_A1, "H", KeyCode::Key_H },
- { 6, K_B1, "J", KeyCode::Key_J },
- { 7, K_C2, "K", KeyCode::Key_K },
- { 8, K_D2, "L", KeyCode::Key_L },
- { 9, K_E2, ";", KeyCode::Key_Semicolon },
- { 10, K_F2, "'", KeyCode::Key_Apostrophe },
- { 11, K_G2, "r", KeyCode::Key_Return },
- { 0, K_Db1, "W", KeyCode::Key_W },
- { 1, K_Eb1, "E", KeyCode::Key_E },
- { 3, K_Gb1, "T", KeyCode::Key_T },
- { 4, K_Ab1, "Y", KeyCode::Key_Y },
- { 5, K_Bb1, "U", KeyCode::Key_U },
- { 7, K_Db2, "O", KeyCode::Key_O },
- { 8, K_Eb2, "P", KeyCode::Key_P },
- { 10, K_Gb2, "]", KeyCode::Key_RightBracket },
-};
-
-void PianoWidget::note(KeyCode key_code, SwitchNote switch_note)
-{
- for (auto& kd : key_definitions) {
- if (kd.key_code == key_code) {
- note(kd.piano_key, switch_note);
- return;
- }
- }
-}
-
-void PianoWidget::note(PianoKey piano_key, SwitchNote switch_note)
-{
- int n = octave_base() + piano_key;
-
- if (switch_note == On) {
- if (m_note_on[n] == 0) {
- m_sin_pos[n] = 0;
- m_square_pos[n] = 0;
- m_saw_pos[n] = 0;
- m_triangle_pos[n] = 0;
- }
- ++m_note_on[n];
- m_power[n] = 1;
- } else {
- if (m_note_on[n] > 1) {
- --m_note_on[n];
- } else if (m_note_on[n] == 1) {
- --m_note_on[n];
- m_power[n] = 0;
- }
- }
-}
-
-void PianoWidget::keydown_event(GKeyEvent& event)
-{
- if (keys[event.key()])
- return;
- keys[event.key()] = true;
-
- switch (event.key()) {
- case KeyCode::Key_C:
-
- if (++m_wave_type == InvalidWave)
- m_wave_type = 0;
- break;
- case KeyCode::Key_V:
- m_delay_enabled = !m_delay_enabled;
- break;
- case KeyCode::Key_B:
- m_decay_enabled = !m_decay_enabled;
- break;
- case KeyCode::Key_Z:
- if (m_octave > m_octave_min)
- --m_octave;
- memset(m_note_on, 0, sizeof(m_note_on));
- break;
- case KeyCode::Key_X:
- if (m_octave < m_octave_max)
- ++m_octave;
- memset(m_note_on, 0, sizeof(m_note_on));
- break;
- default:
- note((KeyCode)event.key(), On);
- }
-
- update();
-}
-
-void PianoWidget::keyup_event(GKeyEvent& event)
-{
- keys[event.key()] = false;
- note((KeyCode)event.key(), Off);
- update();
-}
-
-void PianoWidget::mousedown_event(GMouseEvent& event)
-{
- m_mouse_pressed = true;
-
- m_piano_key_under_mouse = find_key_for_relative_position(event.x() - x(), event.y() - y());
- if (m_piano_key_under_mouse) {
- note(m_piano_key_under_mouse, On);
- update();
- return;
- }
-
- RollNote* roll_note_under_mouse = find_roll_note_for_relative_position(event.x() - x(), event.y() - y());
- if (roll_note_under_mouse)
- roll_note_under_mouse->pressed = !roll_note_under_mouse->pressed;
- update();
-}
-
-void PianoWidget::mouseup_event(GMouseEvent&)
-{
- m_mouse_pressed = false;
-
- note(m_piano_key_under_mouse, Off);
- update();
-}
-
-void PianoWidget::mousemove_event(GMouseEvent& event)
-{
- if (!m_mouse_pressed)
- return;
-
- PianoKey mouse_was_over = m_piano_key_under_mouse;
-
- m_piano_key_under_mouse = find_key_for_relative_position(event.x() - x(), event.y() - y());
-
- if (m_piano_key_under_mouse == mouse_was_over)
- return;
-
- if (mouse_was_over)
- note(mouse_was_over, Off);
- if (m_piano_key_under_mouse)
- note(m_piano_key_under_mouse, On);
- update();
-}
-
-void PianoWidget::render_wave(GPainter& painter)
-{
- Color wave_color;
- switch (m_wave_type) {
- case WaveType::Sine:
- wave_color = Color(255, 192, 0);
- break;
- case WaveType::Saw:
- wave_color = Color(240, 100, 128);
- break;
- case WaveType::Square:
- wave_color = Color(128, 160, 255);
- break;
- case WaveType::Triangle:
- wave_color = Color(35, 171, 35);
- break;
- case WaveType::Noise:
- wave_color = Color(197, 214, 225);
- break;
- }
-
- int prev_x = 0;
- int prev_y = m_height / 2;
- for (int x = 0; x < m_sample_count; ++x) {
- double val = m_front_buffer[x].left;
- val /= 32768;
- val *= m_height;
- int y = ((m_height / 8) - 8) + val;
- if (x == 0)
- painter.set_pixel({ x, y }, wave_color);
- else
- painter.draw_line({ prev_x, prev_y }, { x, y }, wave_color);
- prev_x = x;
- prev_y = y;
- }
-}
-
-static int white_key_width = 22;
-static int white_key_height = 60;
-static int black_key_width = 16;
-static int black_key_height = 35;
-static int black_key_stride = white_key_width - black_key_width;
-static int black_key_offset = white_key_width - black_key_width / 2;
-
-Rect PianoWidget::define_piano_key_rect(int index, PianoKey n) const
-{
- Rect rect;
- int stride = 0;
- int offset = 0;
- if (is_white(n)) {
- rect.set_width(white_key_width);
- rect.set_height(white_key_height);
- } else {
- rect.set_width(black_key_width);
- rect.set_height(black_key_height);
- stride = black_key_stride;
- offset = black_key_offset;
- }
- rect.set_x(offset + index * rect.width() + (index * stride));
- rect.set_y(m_height - white_key_height);
- return rect;
-}
-
-PianoKey PianoWidget::find_key_for_relative_position(int x, int y) const
-{
- // here we iterate backwards because we want to try to match the black
- // keys first, which are defined last
- for (int i = (sizeof(key_definitions) / sizeof(KeyDefinition)) - 1; i >= 0; i--) {
- auto& kd = key_definitions[i];
-
- auto rect = define_piano_key_rect(kd.index, kd.piano_key);
-
- if (rect.contains(x, y))
- return kd.piano_key;
- }
-
- return K_None;
-}
-
-void PianoWidget::render_piano_key(GPainter& painter, int index, PianoKey n, const StringView& text)
-{
- Color color;
- if (m_note_on[octave_base() + n]) {
- color = Color(64, 64, 255);
- } else {
- if (is_white(n))
- color = Color::White;
- else
- color = Color::Black;
- }
-
- auto rect = define_piano_key_rect(index, n);
-
- painter.fill_rect(rect, color);
- painter.draw_rect(rect, Color::Black);
-
- Color text_color;
- if (is_white(n)) {
- text_color = Color::Black;
- } else {
- text_color = Color::White;
- }
- Rect r(rect.x(), rect.y() + rect.height() / 2, rect.width(), rect.height() / 2);
- painter.draw_text(r, text, TextAlignment::Center, text_color);
-}
-
-void PianoWidget::render_piano(GPainter& painter)
-{
- for (auto& kd : key_definitions)
- render_piano_key(painter, kd.index, kd.piano_key, kd.label);
-}
-
-static int knob_width = 100;
-
-void PianoWidget::render_knob(GPainter& painter, const Rect& rect, bool state, const StringView& text)
-{
- Color text_color;
- if (state) {
- painter.fill_rect(rect, Color(0, 200, 0));
- text_color = Color::Black;
- } else {
- painter.draw_rect(rect, Color(180, 0, 0));
- text_color = Color(180, 0, 0);
- }
- painter.draw_text(rect, text, TextAlignment::Center, text_color);
-}
-
-void PianoWidget::render_knobs(GPainter& painter)
-{
- Rect delay_knob_rect(m_width - knob_width - 16, m_height - 50, knob_width, 16);
- render_knob(painter, delay_knob_rect, m_delay_enabled, "V: Delay ");
-
- Rect decay_knob_rect(m_width - knob_width - 16, m_height - 30, knob_width, 16);
- render_knob(painter, decay_knob_rect, m_decay_enabled, "B: Decay ");
-
- Rect octave_knob_rect(m_width - knob_width - 16 - knob_width - 16, m_height - 50, knob_width, 16);
- auto text = String::format("Z/X: Oct %d ", m_octave);
- int oct_rgb_step = 255 / (m_octave_max + 4);
- int oshade = (m_octave + 4) * oct_rgb_step;
- painter.draw_rect(octave_knob_rect, Color(oshade, oshade, oshade));
- painter.draw_text(octave_knob_rect, text, TextAlignment::Center, Color(oshade, oshade, oshade));
-
- Rect wave_knob_rect(m_width - knob_width - 16 - knob_width - 16, m_height - 30, knob_width, 16);
- switch (m_wave_type) {
- case WaveType::Sine:
- painter.draw_rect(wave_knob_rect, Color(255, 192, 0));
- painter.draw_text(wave_knob_rect, "C: Sine ", TextAlignment::Center, Color(255, 192, 0));
- break;
- case WaveType::Saw:
- painter.draw_rect(wave_knob_rect, Color(240, 100, 128));
- painter.draw_text(wave_knob_rect, "C: Sawtooth", TextAlignment::Center, Color(240, 100, 128));
- break;
- case WaveType::Square:
- painter.draw_rect(wave_knob_rect, Color(128, 160, 255));
- painter.draw_text(wave_knob_rect, "C: Square ", TextAlignment::Center, Color(128, 160, 255));
- break;
- case WaveType::Triangle:
- painter.draw_rect(wave_knob_rect, Color(35, 171, 35));
- painter.draw_text(wave_knob_rect, "C: Triangle", TextAlignment::Center, Color(35, 171, 35));
- break;
- case WaveType::Noise:
- painter.draw_rect(wave_knob_rect, Color(197, 214, 225));
- painter.draw_text(wave_knob_rect, "C: Noise ", TextAlignment::Center, Color(197, 214, 225));
- break;
- }
-}
-
-static int roll_columns = 32;
-static int roll_rows = 20;
-static int roll_note_size = 512 / roll_columns;
-static int roll_height = roll_note_size * roll_rows;
-static int roll_y = 512 - white_key_height - roll_height - 16;
-
-Rect PianoWidget::define_roll_note_rect(int column, int row) const
-{
- Rect rect;
- rect.set_width(roll_note_size);
- rect.set_height(roll_note_size);
- rect.set_x(column * roll_note_size);
- rect.set_y(roll_y + (row * roll_note_size));
-
- return rect;
-}
-
-PianoWidget::RollNote* PianoWidget::find_roll_note_for_relative_position(int x, int y)
-{
- for (int row = 0; row < roll_rows; ++row) {
- for (int column = 0; column < roll_columns; ++column) {
- auto rect = define_roll_note_rect(column, row);
- if (rect.contains(x, y))
- return &m_roll_notes[row][column];
- }
- }
-
- return nullptr;
-}
-
-void PianoWidget::render_roll_note(GPainter& painter, int column, int row, PianoKey key)
-{
- Color color;
- auto roll_note = m_roll_notes[row][column];
- if (roll_note.pressed) {
- if (roll_note.playing)
- color = Color(24, 24, 255);
- else
- color = Color(64, 64, 255);
- } else {
- if (roll_note.playing)
- color = Color(104, 104, 255);
- else
- color = is_white(key) ? Color::White : Color::MidGray;
- }
-
- auto rect = define_roll_note_rect(column, row);
-
- painter.fill_rect(rect, color);
- painter.draw_rect(rect, Color::Black);
-}
-
-void PianoWidget::render_roll(GPainter& painter)
-{
- for (int row = 0; row < roll_rows; ++row) {
- PianoKey key = (PianoKey)(roll_rows - row);
- for (int column = 0; column < roll_columns; ++column)
- render_roll_note(painter, column, row, key);
- }
-}
-
-void PianoWidget::change_roll_column()
-{
- static int current_column = 0;
- static int previous_column = roll_columns - 1;
-
- for (int row = 0; row < roll_rows; ++row) {
- m_roll_notes[row][previous_column].playing = false;
- if (m_roll_notes[row][previous_column].pressed)
- note((PianoKey)(roll_rows - row), Off);
-
- m_roll_notes[row][current_column].playing = true;
- if (m_roll_notes[row][current_column].pressed)
- note((PianoKey)(roll_rows - row), On);
- }
-
- if (++current_column == roll_columns)
- current_column = 0;
- if (++previous_column == roll_columns)
- previous_column = 0;
-
- update();
-}
-
-void PianoWidget::custom_event(CCustomEvent&)
-{
- update();
-}
diff --git a/Applications/Piano/PianoWidget.h b/Applications/Piano/PianoWidget.h
deleted file mode 100644
index bdeff89d9d..0000000000
--- a/Applications/Piano/PianoWidget.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#pragma once
-
-#include "Music.h"
-#include <LibGUI/GWidget.h>
-
-class GPainter;
-
-class PianoWidget final : public GWidget {
- C_OBJECT(PianoWidget)
-public:
- virtual ~PianoWidget() override;
-
- void fill_audio_buffer(uint8_t* stream, int len);
-
-private:
- PianoWidget();
- virtual void paint_event(GPaintEvent&) override;
- virtual void keydown_event(GKeyEvent&) override;
- virtual void keyup_event(GKeyEvent&) override;
- virtual void custom_event(CCustomEvent&) override;
- virtual void mousedown_event(GMouseEvent&) override;
- virtual void mouseup_event(GMouseEvent&) override;
- virtual void mousemove_event(GMouseEvent&) override;
-
- double w_sine(size_t);
- double w_saw(size_t);
- double w_square(size_t);
- double w_triangle(size_t);
- double w_noise();
-
- struct RollNote {
- bool pressed;
- bool playing;
- };
-
- Rect define_piano_key_rect(int index, PianoKey) const;
- PianoKey find_key_for_relative_position(int x, int y) const;
- Rect define_roll_note_rect(int column, int row) const;
- RollNote* find_roll_note_for_relative_position(int x, int y);
-
- void render_wave(GPainter&);
- void render_piano_key(GPainter&, int index, PianoKey, const StringView&);
- void render_piano(GPainter&);
- void render_knobs(GPainter&);
- void render_knob(GPainter&, const Rect&, bool state, const StringView&);
- void render_roll_note(GPainter&, int column, int row, PianoKey);
- void render_roll(GPainter&);
-
- void change_roll_column();
-
- enum SwitchNote {
- Off,
- On
- };
- void note(KeyCode, SwitchNote);
- void note(PianoKey, SwitchNote);
-
- int octave_base() const;
-
- int m_sample_count { 0 };
- Sample m_front[2048] { 0, 0 };
- Sample m_back[2048] { 0, 0 };
- Sample* m_front_buffer { m_front };
- Sample* m_back_buffer { m_back };
-
-#define note_count sizeof(note_frequency) / sizeof(double)
-
- u8 m_note_on[note_count] { 0 };
- double m_power[note_count];
- double m_sin_pos[note_count];
- double m_square_pos[note_count];
- double m_saw_pos[note_count];
- double m_triangle_pos[note_count];
-
- int m_octave_min { 1 };
- int m_octave_max { 6 };
- int m_octave { 4 };
-
- int m_width { 512 };
- int m_height { 512 };
-
- int m_wave_type { 0 };
- bool m_delay_enabled { false };
- bool m_decay_enabled { false };
-
- bool keys[256] { false };
-
- PianoKey m_piano_key_under_mouse { K_None };
- bool m_mouse_pressed { false };
-
- RollNote m_roll_notes[20][32] { { false, false } };
-
- int m_time { 0 };
- int m_tick { 10 };
-};
diff --git a/Applications/Piano/RollWidget.cpp b/Applications/Piano/RollWidget.cpp
new file mode 100644
index 0000000000..4f919ef0fc
--- /dev/null
+++ b/Applications/Piano/RollWidget.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RollWidget.h"
+#include "AudioEngine.h"
+#include <LibGUI/GPainter.h>
+#include <LibGUI/GScrollBar.h>
+
+constexpr int note_height = 20;
+constexpr int roll_height = note_count * note_height;
+
+RollWidget::RollWidget(GWidget* parent, AudioEngine& audio_engine)
+ : GScrollableWidget(parent)
+ , m_audio_engine(audio_engine)
+{
+ set_frame_thickness(2);
+ set_frame_shadow(FrameShadow::Sunken);
+ set_frame_shape(FrameShape::Container);
+
+ set_should_hide_unnecessary_scrollbars(true);
+ set_content_size({ 0, roll_height });
+ vertical_scrollbar().set_value(roll_height / 2);
+}
+
+RollWidget::~RollWidget()
+{
+}
+
+void RollWidget::paint_event(GPaintEvent& event)
+{
+ int roll_width = widget_inner_rect().width();
+ double note_width = static_cast<double>(roll_width) / m_horizontal_notes;
+
+ set_content_size({ roll_width, roll_height });
+
+ // This calculates the minimum number of rows needed. We account for a
+ // partial row at the top and/or bottom.
+ int y_offset = vertical_scrollbar().value();
+ int note_offset = y_offset / note_height;
+ int note_offset_remainder = y_offset % note_height;
+ int paint_area = widget_inner_rect().height() + note_offset_remainder;
+ if (paint_area % note_height != 0)
+ paint_area += note_height;
+ int notes_to_paint = paint_area / note_height;
+ int key_pattern_index = (notes_per_octave - 1) - (note_offset % notes_per_octave);
+
+ GPainter painter(*this);
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(0, -note_offset_remainder);
+
+ for (int y = 0; y < notes_to_paint; ++y) {
+ int y_pos = y * note_height;
+ for (int x = 0; x < m_horizontal_notes; ++x) {
+ // This is needed to avoid rounding errors. You can't just use
+ // note_width as the width.
+ int x_pos = x * note_width;
+ int next_x_pos = (x + 1) * note_width;
+ int distance_to_next_x = next_x_pos - x_pos;
+ Rect rect(x_pos, y_pos, distance_to_next_x, note_height);
+
+ if (m_roll_notes[y + note_offset][x] == On)
+ painter.fill_rect(rect, note_pressed_color);
+ else if (x == m_current_column)
+ painter.fill_rect(rect, column_playing_color);
+ else if (key_pattern[key_pattern_index] == Black)
+ painter.fill_rect(rect, Color::LightGray);
+ else
+ painter.fill_rect(rect, Color::White);
+
+ painter.draw_line(rect.top_right(), rect.bottom_right(), Color::Black);
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), Color::Black);
+ }
+
+ if (--key_pattern_index == -1)
+ key_pattern_index = notes_per_octave - 1;
+ }
+
+ GFrame::paint_event(event);
+}
+
+void RollWidget::mousedown_event(GMouseEvent& event)
+{
+ if (!widget_inner_rect().contains(event.x(), event.y()))
+ return;
+
+ int roll_width = widget_inner_rect().width();
+ double note_width = static_cast<double>(roll_width) / m_horizontal_notes;
+
+ int y = (event.y() + vertical_scrollbar().value()) - frame_thickness();
+ y /= note_height;
+
+ // There's a case where we can't just use x / note_width. For example, if
+ // your note_width is 3.1 you will have a rect starting at 3. When that
+ // leftmost pixel of the rect is clicked you will do 3 / 3.1 which is 0
+ // and not 1. We can avoid that case by shifting x by 1 if note_width is
+ // fractional, being careful not to shift out of bounds.
+ int x = event.x() - frame_thickness();
+ bool note_width_is_fractional = note_width - static_cast<int>(note_width) != 0;
+ bool x_is_not_last = x != widget_inner_rect().width() - 1;
+ if (note_width_is_fractional && x_is_not_last)
+ ++x;
+ x /= note_width;
+
+ if (m_roll_notes[y][x] == On) {
+ if (x == m_current_column) // If you turn off a note that is playing.
+ m_audio_engine.set_note((note_count - 1) - y, Off);
+ m_roll_notes[y][x] = Off;
+ } else {
+ m_roll_notes[y][x] = On;
+ }
+
+ update();
+}
+
+void RollWidget::update_roll()
+{
+ if (++m_current_column == m_horizontal_notes)
+ m_current_column = 0;
+ if (++m_previous_column == m_horizontal_notes)
+ m_previous_column = 0;
+
+ for (int note = 0; note < note_count; ++note) {
+ if (m_roll_notes[note][m_previous_column] == On)
+ m_audio_engine.set_note((note_count - 1) - note, Off);
+ if (m_roll_notes[note][m_current_column] == On)
+ m_audio_engine.set_note((note_count - 1) - note, On);
+ }
+
+ update();
+}
diff --git a/Applications/Piano/RollWidget.h b/Applications/Piano/RollWidget.h
new file mode 100644
index 0000000000..91c0c8aefd
--- /dev/null
+++ b/Applications/Piano/RollWidget.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Music.h"
+#include <LibGUI/GScrollableWidget.h>
+
+class AudioEngine;
+
+class RollWidget final : public GScrollableWidget {
+ C_OBJECT(RollWidget)
+public:
+ virtual ~RollWidget() override;
+
+ void update_roll();
+
+private:
+ RollWidget(GWidget* parent, AudioEngine&);
+
+ virtual void paint_event(GPaintEvent&) override;
+ virtual void mousedown_event(GMouseEvent& event) override;
+
+ AudioEngine& m_audio_engine;
+
+ int m_horizontal_notes { 32 };
+ Switch m_roll_notes[note_count][32] { Off };
+ int m_current_column { 0 };
+ int m_previous_column { m_horizontal_notes - 1 };
+};
diff --git a/Applications/Piano/WaveWidget.cpp b/Applications/Piano/WaveWidget.cpp
new file mode 100644
index 0000000000..2c0d014d6f
--- /dev/null
+++ b/Applications/Piano/WaveWidget.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "WaveWidget.h"
+#include "AudioEngine.h"
+#include <LibGUI/GPainter.h>
+#include <limits>
+
+WaveWidget::WaveWidget(GWidget* parent, AudioEngine& audio_engine)
+ : GFrame(parent)
+ , m_audio_engine(audio_engine)
+{
+ set_frame_thickness(2);
+ set_frame_shadow(FrameShadow::Sunken);
+ set_frame_shape(FrameShape::Container);
+}
+
+WaveWidget::~WaveWidget()
+{
+}
+
+static const Color wave_colors[] = {
+ // Sine
+ {
+ 255,
+ 192,
+ 0,
+ },
+ // Triangle
+ {
+ 35,
+ 171,
+ 35,
+ },
+ // Square
+ {
+ 128,
+ 160,
+ 255,
+ },
+ // Saw
+ {
+ 240,
+ 100,
+ 128,
+ },
+ // Noise
+ {
+ 197,
+ 214,
+ 225,
+ },
+};
+
+int WaveWidget::sample_to_y(int sample) const
+{
+ constexpr double sample_max = std::numeric_limits<i16>::max();
+ double percentage = sample / sample_max;
+ double portion_of_height = percentage * frame_inner_rect().height();
+ int y = (frame_inner_rect().height() / 2) + portion_of_height;
+ return y;
+}
+
+void WaveWidget::paint_event(GPaintEvent& event)
+{
+ GPainter painter(*this);
+ painter.fill_rect(frame_inner_rect(), Color::Black);
+ painter.translate(frame_thickness(), frame_thickness());
+
+ Color wave_color = wave_colors[m_audio_engine.wave()];
+ auto buffer = m_audio_engine.buffer();
+ double width_scale = static_cast<double>(frame_inner_rect().width()) / buffer.size();
+
+ int prev_x = 0;
+ int prev_y = sample_to_y(buffer[0].left);
+ painter.set_pixel({ prev_x, prev_y }, wave_color);
+
+ for (size_t x = 1; x < buffer.size(); ++x) {
+ int y = sample_to_y(buffer[x].left);
+
+ Point point1(prev_x * width_scale, prev_y);
+ Point point2(x * width_scale, y);
+ painter.draw_line(point1, point2, wave_color);
+
+ prev_x = x;
+ prev_y = y;
+ }
+
+ GFrame::paint_event(event);
+}
diff --git a/Applications/Piano/WaveWidget.h b/Applications/Piano/WaveWidget.h
new file mode 100644
index 0000000000..1a8d9f72cd
--- /dev/null
+++ b/Applications/Piano/WaveWidget.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/GFrame.h>
+
+class GPainter;
+class AudioEngine;
+
+class WaveWidget final : public GFrame {
+ C_OBJECT(WaveWidget)
+public:
+ virtual ~WaveWidget() override;
+
+private:
+ WaveWidget(GWidget* parent, AudioEngine&);
+
+ virtual void paint_event(GPaintEvent&) override;
+
+ int sample_to_y(int sample) const;
+
+ AudioEngine& m_audio_engine;
+};
diff --git a/Applications/Piano/main.cpp b/Applications/Piano/main.cpp
index 038a22e626..3d8f0a36b4 100644
--- a/Applications/Piano/main.cpp
+++ b/Applications/Piano/main.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -24,8 +25,8 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "Music.h"
-#include "PianoWidget.h"
+#include "AudioEngine.h"
+#include "MainWidget.h"
#include <LibAudio/AClientConnection.h>
#include <LibCore/CFile.h>
#include <LibDraw/PNGLoader.h>
@@ -39,34 +40,36 @@
int main(int argc, char** argv)
{
GApplication app(argc, argv);
+
auto audio_client = AClientConnection::construct();
audio_client->handshake();
+ AudioEngine audio_engine;
+
auto window = GWindow::construct();
+ auto main_widget = MainWidget::construct(audio_engine);
+ window->set_main_widget(main_widget);
window->set_title("Piano");
- window->set_rect(100, 100, 512, 512);
-
- auto piano_widget = PianoWidget::construct();
- window->set_main_widget(piano_widget);
- window->show();
+ window->set_rect(90, 90, 840, 600);
window->set_icon(load_png("/res/icons/16x16/app-piano.png"));
+ window->show();
- LibThread::Thread sound_thread([piano_widget = piano_widget.ptr()] {
+ LibThread::Thread audio_thread([&] {
auto audio = CFile::construct("/dev/audio");
if (!audio->open(CIODevice::WriteOnly)) {
dbgprintf("Can't open audio device: %s", audio->error_string());
return 1;
}
+ FixedArray<Sample> buffer(sample_count);
for (;;) {
- u8 buffer[4096];
- piano_widget->fill_audio_buffer(buffer, sizeof(buffer));
- audio->write(buffer, sizeof(buffer));
- CEventLoop::current().post_event(*piano_widget, make<CCustomEvent>(0));
+ audio_engine.fill_buffer(buffer);
+ audio->write(reinterpret_cast<u8*>(buffer.data()), buffer_size);
+ CEventLoop::current().post_event(*main_widget, make<CCustomEvent>(0));
CEventLoop::wake();
}
});
- sound_thread.start();
+ audio_thread.start();
auto menubar = make<GMenuBar>();