add tap tempo to metronomic app (#2605)

* _

* format

* fix new tree in Arch
This commit is contained in:
sommermorgentraum 2025-04-03 04:15:12 +08:00 committed by GitHub
parent ecd1a217d7
commit 4bbe1175c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 175 additions and 1 deletions

View File

@ -9,7 +9,9 @@
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
[<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
[<img src="https://camo.githubusercontent.com/c1f7dd1e7672324f60a513f0de23de76da6a669e63896a9de535d8c8093fc3c7/68747470733a2f2f7261772e6769746875622e636f6d2f7368617265627261696e65642f706f7274617061636b2d6861636b72662f6d61737465722f646f632f696d616765732f68617264776172652f706f7274617061636b5f68315f6f7065726174696e672e6a7067" height="310">]([https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h1r1r2))
# What is this?

View File

@ -23,6 +23,7 @@
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
#include "ui_textentry.hpp"
using namespace portapack;
@ -42,6 +43,7 @@ MetronomeView::MetronomeView(NavigationView& nav)
&field_unaccent_beep_tune,
&field_beep_flash_duration,
&field_bpm,
&button_enter_tap_tempo,
&progressbar,
});
@ -59,6 +61,14 @@ MetronomeView::MetronomeView(NavigationView& nav)
}
};
button_enter_tap_tempo.on_select = [this](Button&) {
auto tap_tempo_view = nav_.push<MetronomeTapTempoView>(field_bpm.value());
tap_tempo_view->on_apply = [this](uint16_t bpm) {
field_bpm.set_value(bpm);
};
};
field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
field_volume.set_value(99);
@ -174,4 +184,115 @@ void MetronomeView::run() {
}
}
MetronomeTapTempoView::MetronomeTapTempoView(NavigationView& nav, uint16_t bpm)
: nav_{nav},
bpm_{bpm} {
add_children({
&button_input,
&button_tap,
&button_cancel,
&button_apply,
});
bpm_when_entered_ = bpm; // save for if user cancel
// im aware that we have duplicated painter which means in this app, weo have two painter instances
// here is the reason why this is necessary:
// We need to draw the bpm big font once when enter, which would be at bad timing in constructor,
// cuz it happened before the view is pushed to nav, which casued it actually didn't draw
// which leads me have to override the paint func from father and draw inside of it.
//
// BUT I can't completely package the draw logic inside of the paint func,
// cuz set_dirty has flaw and cause screen flicker during the char changes, if i just package there and use set_dirty()
Painter painter_instance_2;
button_input.on_select = [this](Button&) {
input_buffer = to_string_dec_uint(bpm_);
text_prompt(
nav_,
input_buffer,
3,
[this](std::string& buffer) {
if (buffer.empty()) {
return;
}
bpm_ = atoi(buffer.c_str());
if (on_apply) {
on_apply(bpm_);
}
});
};
button_tap.on_select = [&](Button&) {
on_tap(painter_instance_2);
};
button_apply.on_select = [this](Button&) {
// it's dynamically applied in tap handler
// the design allow user to hear changes before apply
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
bpm_ = bpm_when_entered_;
if (on_apply) {
on_apply(bpm_);
}
nav_.pop();
};
}
void MetronomeTapTempoView::focus() {
button_tap.focus();
}
void MetronomeTapTempoView::paint(Painter& painter) {
View::paint(painter);
painter.draw_char({(0 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ / 100, 4);
painter.draw_char({(1 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + (bpm_ / 10) % 10, 4);
painter.draw_char({(2 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ % 10, 4);
}
/*
NB: i don't really know if the cpu clock is 1000Hz AKA 1ms per tick for chTimeNow()
but it should be, refering to the stop watch app.
and also i compared with my real metronome and it's very close
so I assume it's 1ms per tick
*/
void MetronomeTapTempoView::on_tap(Painter& painter) {
/* ^ NB: this painter accepted from painter_instance_2*/
systime_t current_time = chTimeNow();
if (last_tap_time > 0) {
uint32_t interval_ms = current_time - last_tap_time;
if (interval_ms > 100) {
uint16_t this_time_bpm = 60000 / interval_ms;
if (this_time_bpm > 0 && this_time_bpm < 400) {
bpms_deque.push_back(this_time_bpm);
if (bpms_deque.size() > 4) { // one bar length cuz most music tempo is quarter note as 1 beat
bpms_deque.pop_front();
}
// avg
uint32_t sum = 0;
for (auto& bpm : bpms_deque) {
sum += bpm;
}
bpm_ = sum / bpms_deque.size();
if (on_apply) {
on_apply(bpm_);
}
painter.draw_char({(0 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ / 100, 4);
painter.draw_char({(1 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + (bpm_ / 10) % 10, 4);
painter.draw_char({(2 * 16) * 4 + 2 * 16, 3 * 16}, *Theme::getInstance()->fg_light, '0' + bpm_ % 10, 4);
}
}
}
last_tap_time = current_time;
}
} // namespace ui::external_app::metronome

View File

@ -27,6 +27,8 @@
#include "audio.hpp"
#include "ch.h"
#include <deque>
namespace ui::external_app::metronome {
class MetronomeView : public View {
@ -73,6 +75,11 @@ class MetronomeView : public View {
1,
' '};
Button button_enter_tap_tempo{
{(sizeof("BPM:") + 6) * 8, 1 * 16, (sizeof("Tap Tempo") + 3) * 8, 16},
"Tap Tempo",
};
NumberField field_rythm_unaccent_time{// e.g. 3 in 3/4 beat
{(sizeof("Rhythm:") + 1) * 8, 4 * 16},
2,
@ -121,6 +128,45 @@ class MetronomeView : public View {
{0 * 16, 8 * 16, screen_width, screen_height - 14 * 16}};
};
class MetronomeTapTempoView : public View {
public:
std::function<void(uint16_t)> on_apply{};
MetronomeTapTempoView(NavigationView& nav, uint16_t bpm);
std::string title() const override { return "Tap.T"; };
void focus() override;
void paint(Painter& painter) override;
private:
void on_tap(Painter& painter);
NavigationView& nav_;
uint16_t bpm_{0};
uint16_t bpm_when_entered_{0}; // this pass from MetronomeView and need to restore if user cancel
std::deque<uint16_t> bpms_deque = {0}; // take average for recent taps to debounce
uint32_t last_tap_time{0};
std::string input_buffer{""}; // needed by text_prompt
Button button_input{
{0, 0, screen_width, 2 * 16},
"Input BPM"};
Button button_tap{
{0, 8 * 16, screen_width, 7 * 16},
"Tap BPM"};
Button button_cancel{
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
"Cancel"};
Button button_apply{
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
"Apply"};
};
} // namespace ui::external_app::metronome
#endif /*__UI_METRONOME_H__*/

View File

@ -137,7 +137,12 @@ def get_pp_device_linux():
parts = clean_line.split()
if len(parts) >= 2 and SDCARD_LABEL in parts[1]: # checker
print("found pp sd:")
device_path = parts[0]
#remove `- in it
device_path = device_path.replace('-', '')
device_path = device_path.replace('`', '')
print(device_path)
# if path valid
if not os.path.exists(device_path):
continue