add tuner app (#2429)

* _

* icon
This commit is contained in:
sommermorgentraum 2024-12-18 17:06:25 +08:00 committed by GitHub
parent fff63e056a
commit ead9449609
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 499 additions and 4 deletions

View File

@ -134,11 +134,14 @@ set(EXTCPPSRC
#mcu_temperature
external/mcu_temperature/main.cpp
external/mcu_temperature/mcu_temperature.cpp
#fmradio
external/fmradio/main.cpp
external/fmradio/ui_fmradio.cpp
#tuner
external/tuner/main.cpp
external/tuner/ui_tuner.cpp
)
set(EXTAPPLIST
@ -172,7 +175,8 @@ set(EXTAPPLIST
ook_editor
shoppingcart_lock
flippertx
remote
remote
mcu_temperature
fmradio
tuner
)

View File

@ -55,7 +55,9 @@ MEMORY
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
ram_external_app_fmradio(rwx) : org = 0xADE00000, len = 32k
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
}
SECTIONS
@ -252,11 +254,16 @@ SECTIONS
KEEP(*(.external_app.app_mcu_temperature.application_information));
*(*ui*external_app*mcu_temperature*);
} > ram_external_app_mcu_temperature
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_fmradio.application_information));
*(*ui*external_app*fmradio*);
} > ram_external_app_fmradio
.external_app_tuner : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tuner.application_information));
*(*ui*external_app*tuner*);
} > ram_external_app_tuner
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 Bernd
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_tuner.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::tuner {
void initialize_app(ui::NavigationView& nav) {
nav.push<TunerView>();
}
} // namespace ui::external_app::tuner
extern "C" {
__attribute__((section(".external_app.app_tuner.application_information"), used)) application_information_t _application_information_tuner = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::tuner::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Tuner",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x22,
0x44,
0x21,
0x84,
0x2D,
0xB4,
0x25,
0xA4,
0x25,
0xA4,
0x2D,
0xB4,
0x61,
0x86,
0xC2,
0x43,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}

View File

@ -0,0 +1,256 @@
/*
* copyleft 2024 sommermorgentraum
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_tuner.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
using namespace portapack;
namespace ui::external_app::tuner {
TunerView::TunerView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // proc_audio_beep baseband is external too
add_children({
&labels,
&field_volume,
&options_instrument,
&options_note,
&button_play_stop,
&text_note_frequency,
&text_note_octave_shift,
});
audio::set_rate(audio::Rate::Hz_24000);
options_instrument.on_change = [this](size_t, int32_t value) {
const Instrument* selected_instrument = nullptr;
switch (value) {
case 0:
selected_instrument = &guitar;
break;
case 1:
selected_instrument = &violin;
break;
case 2:
selected_instrument = &pitch_fork;
break;
}
if (selected_instrument) {
update_notes_for_instrument(*selected_instrument);
}
update_current_note();
};
options_note.on_change = [this](size_t, int32_t index) {
const Instrument* current_instrument = nullptr;
switch (options_instrument.selected_index_value()) {
case 0:
current_instrument = &guitar;
break;
case 1:
current_instrument = &violin;
break;
case 2:
current_instrument = &pitch_fork;
break;
}
if (current_instrument) {
auto it = current_instrument->notes.begin();
std::advance(it, index);
if (it != current_instrument->notes.end()) {
current_note_frequency = it->second.frequency;
current_note_sample_rate = it->second.sample_rate;
current_note_octave_shift = it->second.octave_shift;
}
}
update_current_note();
};
button_play_stop.on_select = [this]() {
if (current_note_playing) {
stop_play();
} else {
play_change_note();
}
};
options_instrument.set_selected_index(0);
update_notes_for_instrument(guitar);
update_current_note();
field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
field_volume.set_value(99);
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
TunerView::~TunerView() {
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
}
void TunerView::focus() {
options_instrument.focus();
}
void TunerView::update_notes_for_instrument(const Instrument& instrument) {
std::vector<OptionsField::option_t> note_options;
size_t index = 0;
for (const auto& note_pair : instrument.notes) {
note_options.emplace_back(OptionsField::option_t{
note_pair.first,
index++});
}
options_note.set_options(note_options);
if (!note_options.empty()) {
options_note.set_selected_index(0);
}
}
void TunerView::update_current_note() {
const Instrument* current_instrument = nullptr;
switch (options_instrument.selected_index_value()) {
case 0:
current_instrument = &guitar;
break;
case 1:
current_instrument = &violin;
break;
case 2:
current_instrument = &pitch_fork;
break;
}
if (current_instrument) {
std::string note_name = options_note.selected_index_name();
// map
auto note_it = current_instrument->notes.find(note_name);
if (note_it != current_instrument->notes.end()) {
current_note_frequency = note_it->second.frequency;
current_note_sample_rate = note_it->second.sample_rate;
current_note_octave_shift = note_it->second.octave_shift;
text_note_frequency.set(std::to_string(current_note_frequency));
text_note_octave_shift.set(std::to_string(current_note_octave_shift));
set_dirty();
if (current_note_playing) {
play_change_note();
}
}
}
}
void TunerView::stop_play() {
baseband::request_beep_stop();
current_note_playing = false;
button_play_stop.set_bitmap(&bitmap_icon_replay);
}
void TunerView::play_change_note() {
if (current_note_playing) {
baseband::request_beep_stop();
audio::set_rate(current_note_sample_rate);
baseband::request_audio_beep(current_note_frequency, protected_sample_rate(current_note_sample_rate), 0);
} else {
audio::set_rate(current_note_sample_rate);
baseband::request_audio_beep(current_note_frequency, protected_sample_rate(current_note_sample_rate), 0);
}
current_note_playing = true;
button_play_stop.set_bitmap(&bitmap_icon_sleep);
set_dirty();
}
uint32_t TunerView::protected_sample_rate(audio::Rate r) {
switch (r) {
case audio::Rate::Hz_12000:
return 12000;
case audio::Rate::Hz_24000:
return 24000;
case audio::Rate::Hz_48000:
return 48000;
default:
return 24000;
}
}
void TunerView::paint(Painter& painter) {
View::paint(painter);
painter.fill_rectangle(
{screen_width / 4, 8 * 16, screen_width / 2, 6 * 16},
Theme::getInstance()->bg_darkest->background);
if (!current_note_playing) return;
painter.fill_rectangle(
{screen_width / 4, 10 * 16, 2, 2 * 16},
Theme::getInstance()->fg_light->foreground);
painter.fill_rectangle(
{screen_width / 4, 12 * 16, screen_width / 4 * 2 + 2, 2},
Theme::getInstance()->fg_light->foreground);
painter.fill_rectangle(
{(screen_width / 4) * 3, 10 * 16, 2, 2 * 16},
Theme::getInstance()->fg_light->foreground);
painter.draw_string(
{screen_width / 4 - 2 * 8, 8 * 16},
(current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
std::to_string(current_note_frequency));
int16_t real_frequency = current_note_frequency;
if (current_note_octave_shift > 0) {
for (int i = 0; i < current_note_octave_shift; i++) {
real_frequency *= 2;
}
} else if (current_note_octave_shift < 0) {
for (int i = 0; i > current_note_octave_shift; i--) {
real_frequency /= 2;
}
}
painter.draw_string({(screen_width / 4) * 3 - 2 * 8, 8 * 16},
(current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
std::to_string(real_frequency));
painter.draw_string({screen_width / 2 - 3 * 16, 13 * 16},
(current_note_octave_shift == 0) ? *Theme::getInstance()->bg_darkest : *Theme::getInstance()->fg_red,
std::to_string(current_note_octave_shift) + " * 8ev");
}
} // namespace ui::external_app::tuner

View File

@ -0,0 +1,145 @@
/*
* copyleft 2024 sommermorgentraum
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_TUNER_H__
#define __UI_TUNER_H__
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "audio.hpp"
namespace ui::external_app::tuner {
struct Instrument {
std::string name;
struct NoteInfo {
uint16_t frequency;
audio::Rate sample_rate; // tune samplerate to allow for freqs
int8_t octave_shift;
// PP hardware can't handle some extremely low/high frequencies, this indicates how much the freq that were played is shifted up/down,
// for example, if shift is -2 and note is A3, it means the real string should play A5 but PP's speaker plays A3
};
std::map<std::string, NoteInfo> notes; // this is for fast looking : O(log(n)) , usage `auto note = guitar.notes["A4"];`
};
class TunerView : public View {
public:
TunerView(NavigationView& nav);
~TunerView();
TunerView(const TunerView& other) = delete;
TunerView& operator=(const TunerView& other) = delete;
void focus() override;
void update_audio_beep();
std::string title() const override { return "Tuner"; };
private:
NavigationView& nav_;
bool beep{false};
void update_notes_for_instrument(const Instrument& instrument);
void play_change_note();
void stop_play();
void update_current_note();
uint32_t protected_sample_rate(audio::Rate r);
void paint(Painter& painter) override;
uint16_t current_note_frequency{440};
audio::Rate current_note_sample_rate{audio::Rate::Hz_12000};
int8_t current_note_octave_shift{0};
bool current_note_playing{false};
Labels labels{
{{0 * 8, 1 * 16}, "Instrument:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Note:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Note Frequency:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Note Octave Shift:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
Text text_note_frequency{
{(sizeof("Note Frequency:") + 1) * 8, 3 * 16, screen_width - (sizeof("Note Frequency:") + 1) * 8, 16},
"",
};
Text text_note_octave_shift{
{(sizeof("Note Octave Shift:") + 1) * 8, 4 * 16, screen_width - (sizeof("Note Octave Shift:") + 1) * 8, 16},
"",
};
AudioVolumeField field_volume{
{(sizeof("Volume:") + 1) * 8, 5 * 16}};
// TODO: load list runtime
OptionsField options_instrument{
{(sizeof("Instrument:") + 1) * 8, 1 * 16},
screen_width - (sizeof("Instrument:") + 1) * 8,
{{"Guitar", 0},
{"Violin", 1},
{"Pitch Fork", 2}}};
OptionsField options_note{
{(sizeof("Note:") + 1) * 8, 2 * 16},
screen_width - (sizeof("Note:") + 1) * 8,
{}};
NewButton button_play_stop{
{0 * 8, 16 * 16, screen_width, screen_height - 16 * 16},
{},
&bitmap_icon_replay,
Theme::getInstance()->fg_red->foreground};
// TODO: load from file DB
const Instrument guitar = {
.name = "Folk Guitar",
.notes = {
{"E2", {165, audio::Rate::Hz_12000, -1}}, // actual: E2, PP speaker: E3
{"A2", {110, audio::Rate::Hz_12000, 0}},
{"D3", {147, audio::Rate::Hz_12000, 0}},
{"G3", {196, audio::Rate::Hz_24000, 0}},
{"B3", {247, audio::Rate::Hz_24000, 0}},
{"E4", {330, audio::Rate::Hz_24000, 0}}}};
const Instrument violin = {
.name = "Violin 440 Standard, 12ET",
.notes = {
{"G3", {196, audio::Rate::Hz_12000, 0}},
{"D4", {294, audio::Rate::Hz_24000, 0}},
{"A4", {440, audio::Rate::Hz_48000, 0}},
{"E5", {659, audio::Rate::Hz_48000, 0}}}};
const Instrument pitch_fork = {// freq copied from flipper app
.name = "Pitch Fork",
.notes = {
{"12ET A4", {440, audio::Rate::Hz_48000, 0}},
{"Sarti's A4", {436, audio::Rate::Hz_48000, 0}},
{"1858 A4", {435, audio::Rate::Hz_48000, 0}},
{"Verdi's A4", {432, audio::Rate::Hz_48000, 0}}}};
std::map<std::string, Instrument> instruments{
{"Guitar", guitar},
{"Violin", violin},
{"Pitch Fork", pitch_fork}};
};
} // namespace ui::external_app::tuner
#endif /*__UI_TUNER_H__*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B