diff --git a/README.md b/README.md
index 5b6951713..10f2986c8 100644
--- a/README.md
+++ b/README.md
@@ -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.
-[
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
+[
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
+[
](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
+[
]([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?
diff --git a/firmware/application/external/metronome/ui_metronome.cpp b/firmware/application/external/metronome/ui_metronome.cpp
index 75dca3b38..e97dbdcad 100644
--- a/firmware/application/external/metronome/ui_metronome.cpp
+++ b/firmware/application/external/metronome/ui_metronome.cpp
@@ -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(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
\ No newline at end of file
diff --git a/firmware/application/external/metronome/ui_metronome.hpp b/firmware/application/external/metronome/ui_metronome.hpp
index d813146da..04604b573 100644
--- a/firmware/application/external/metronome/ui_metronome.hpp
+++ b/firmware/application/external/metronome/ui_metronome.hpp
@@ -27,6 +27,8 @@
#include "audio.hpp"
#include "ch.h"
+#include
+
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 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 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__*/
\ No newline at end of file
diff --git a/firmware/tools/fast_flash_pp_and_copy_apps.py b/firmware/tools/fast_flash_pp_and_copy_apps.py
index 97b010767..7b460158a 100644
--- a/firmware/tools/fast_flash_pp_and_copy_apps.py
+++ b/firmware/tools/fast_flash_pp_and_copy_apps.py
@@ -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