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