From 6e20ffb990a7a2e0b289c6e6553c38e6af87a228 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 31 Jan 2023 09:22:49 +0100 Subject: [PATCH] gems: simple oscilloscope for audio input --- repos/gems/recipes/pkg/osci/README | 2 + repos/gems/recipes/pkg/osci/archives | 1 + repos/gems/recipes/pkg/osci/hash | 1 + repos/gems/recipes/pkg/osci/runtime | 10 ++ repos/gems/recipes/src/osci/content.mk | 2 + repos/gems/recipes/src/osci/hash | 1 + repos/gems/recipes/src/osci/used_apis | 10 ++ repos/gems/src/app/osci/main.cc | 197 +++++++++++++++++++++++++ repos/gems/src/app/osci/target.mk | 3 + 9 files changed, 227 insertions(+) create mode 100644 repos/gems/recipes/pkg/osci/README create mode 100644 repos/gems/recipes/pkg/osci/archives create mode 100644 repos/gems/recipes/pkg/osci/hash create mode 100644 repos/gems/recipes/pkg/osci/runtime create mode 100644 repos/gems/recipes/src/osci/content.mk create mode 100644 repos/gems/recipes/src/osci/hash create mode 100644 repos/gems/recipes/src/osci/used_apis create mode 100644 repos/gems/src/app/osci/main.cc create mode 100644 repos/gems/src/app/osci/target.mk diff --git a/repos/gems/recipes/pkg/osci/README b/repos/gems/recipes/pkg/osci/README new file mode 100644 index 0000000000..3f9fae42c5 --- /dev/null +++ b/repos/gems/recipes/pkg/osci/README @@ -0,0 +1,2 @@ + + Virtual print button for a touch-screen device diff --git a/repos/gems/recipes/pkg/osci/archives b/repos/gems/recipes/pkg/osci/archives new file mode 100644 index 0000000000..47c0e0d066 --- /dev/null +++ b/repos/gems/recipes/pkg/osci/archives @@ -0,0 +1 @@ +_/src/osci diff --git a/repos/gems/recipes/pkg/osci/hash b/repos/gems/recipes/pkg/osci/hash new file mode 100644 index 0000000000..af2bc27260 --- /dev/null +++ b/repos/gems/recipes/pkg/osci/hash @@ -0,0 +1 @@ +2023-01-31 0d3ec33511f8d3c157580a734dfed41504dc34c6 diff --git a/repos/gems/recipes/pkg/osci/runtime b/repos/gems/recipes/pkg/osci/runtime new file mode 100644 index 0000000000..01da299aaa --- /dev/null +++ b/repos/gems/recipes/pkg/osci/runtime @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/repos/gems/recipes/src/osci/content.mk b/repos/gems/recipes/src/osci/content.mk new file mode 100644 index 0000000000..d4b6d53fe0 --- /dev/null +++ b/repos/gems/recipes/src/osci/content.mk @@ -0,0 +1,2 @@ +SRC_DIR = src/app/osci +include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/repos/gems/recipes/src/osci/hash b/repos/gems/recipes/src/osci/hash new file mode 100644 index 0000000000..5e81b6c093 --- /dev/null +++ b/repos/gems/recipes/src/osci/hash @@ -0,0 +1 @@ +2023-01-31 4b14ae81ac3638c5db44bf52ba6853620c820072 diff --git a/repos/gems/recipes/src/osci/used_apis b/repos/gems/recipes/src/osci/used_apis new file mode 100644 index 0000000000..46a59c18e9 --- /dev/null +++ b/repos/gems/recipes/src/osci/used_apis @@ -0,0 +1,10 @@ +base +os +blit +gems +framebuffer_session +input_session +gui_session +timer_session +audio_in_session +polygon_gfx diff --git a/repos/gems/src/app/osci/main.cc b/repos/gems/src/app/osci/main.cc new file mode 100644 index 0000000000..4ad0c0f92c --- /dev/null +++ b/repos/gems/src/app/osci/main.cc @@ -0,0 +1,197 @@ +/* + * \brief Oscilloscope showing audio input + * \author Norman Feske + * \date 2023-01-29 + */ + +/* + * Copyright (C) 2023 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Osci { + using namespace Genode; + struct Main; +} + + +struct Osci::Main +{ + Env &_env; + + using Point = Gui_buffer::Point; + using Area = Gui_buffer::Area; + using Rect = Gui_buffer::Rect; + + Area _size { }; + Color _background { }; + Color _color { }; + int _v_scale { }; + + Gui::Connection _gui { _env }; + + Timer::Connection _timer { _env }; + + Audio_in::Connection _audio_in { _env, "left" }; + + Constructible _gui_buffer { }; + + struct View + { + Gui::Connection &_gui; + + Gui::Session::View_handle _handle { _gui.create_view() }; + + View(Gui::Connection &gui, Point position, Area size) : _gui(gui) + { + using Command = Gui::Session::Command; + _gui.enqueue(_handle, Rect(position, size)); + _gui.enqueue(_handle, Gui::Session::View_handle()); + _gui.execute(); + } + + ~View() { _gui.destroy_view(_handle); } + }; + + Constructible _view { }; + + Signal_handler
_timer_handler { _env.ep(), *this, &Main::_handle_timer }; + + Attached_rom_dataspace _config { _env, "config" }; + + Signal_handler
_config_handler { _env.ep(), *this, &Main::_handle_config }; + + struct Captured_audio + { + enum { SIZE_LOG2 = 10, SIZE = 1 << SIZE_LOG2, MASK = SIZE - 1 }; + + float _samples[SIZE] { }; + + unsigned _pos = 0; + + void _insert(float value) + { + _pos = (_pos + 1) & MASK; + _samples[_pos] = value; + } + + float past_value(unsigned past) const + { + return _samples[(_pos - past) & MASK]; + } + + void capture_from_audio_in(Audio_in::Session &audio_in) + { + Audio_in::Stream &stream = *audio_in.stream(); + + while (!stream.empty()) { + + Audio_in::Packet &p = *stream.get(stream.pos()); + + if (p.valid()) { + float *data_ptr = p.content(); + + for (unsigned i = 0; i < Audio_in::PERIOD; i++) + _insert(*data_ptr++); + + p.invalidate(); + p.mark_as_recorded(); + } + + stream.increment_position(); + } + } + } _captured_audio { }; + + void _handle_config() + { + _config.update(); + + Xml_node const config = _config.xml(); + + _size = Area::from_xml(config); + _background = config.attribute_value("background", Color { 0, 0, 0 }); + _color = config.attribute_value("color", Color { 255, 255, 255 }); + _v_scale = config.attribute_value("v_scale", 3000); + + _gui_buffer.construct(_gui, _size, _env.ram(), _env.rm(), + Gui_buffer::Alpha::OPAQUE, _background); + + _view.construct(_gui, Point::from_xml(config), _size); + + _timer.trigger_periodic(1000*config.attribute_value("period_ms", 20)); + } + + Line_painter const _line_painter { }; + + void _render(Gui_buffer::Pixel_surface &pixel, Gui_buffer::Alpha_surface &) + { + /* + * Draw captured audio from right to left. + */ + + Point const centered { 0, int(pixel.size().h()/2) }; + + Point previous_p { }; + + bool first_iteration = true; + + unsigned const w = pixel.size().w(); + + for (unsigned i = 0; i < w; i++) { + + Point p { int(w - i), + int(float(_v_scale)*_captured_audio.past_value(i)) }; + + p = p + centered; + + if (!first_iteration) + _line_painter.paint(pixel, p, previous_p, _color); + + previous_p = p; + first_iteration = false; + } + } + + void _handle_timer() + { + _captured_audio.capture_from_audio_in(_audio_in); + + _gui_buffer->reset_surface(); + _gui_buffer->apply_to_surface([&] (auto &pixel, auto &alpha) { + _render(pixel, alpha); }); + + _gui_buffer->flush_surface(); + + _gui.framebuffer()->refresh(0, 0, _size.w(), _size.h()); + } + + Main(Env &env) : _env(env) + { + _config.sigh(_config_handler); + _handle_config(); + + _timer.sigh(_timer_handler); + + _audio_in.start(); + } +}; + + +void Component::construct(Genode::Env &env) +{ + static Osci::Main main(env); +} diff --git a/repos/gems/src/app/osci/target.mk b/repos/gems/src/app/osci/target.mk new file mode 100644 index 0000000000..120068b254 --- /dev/null +++ b/repos/gems/src/app/osci/target.mk @@ -0,0 +1,3 @@ +TARGET = osci +SRC_CC = main.cc +LIBS = base blit