From 4754832d12c33ed3f9e3e4625308408c65a76e64 Mon Sep 17 00:00:00 2001 From: Aria Burrell Date: Thu, 15 Sep 2022 18:59:56 -0600 Subject: [PATCH] Migrated existing state to a new repo. - Dialer semi-functional - DTMF is untuned, code needs refactor - There is definitely a memory leak. - Bluebox and redbox functionality awaiting code refactor. --- README.md | 5 + application.fam | 13 ++ dtmf_dolphin.c | 136 ++++++++++++++ dtmf_dolphin_event.h | 14 ++ dtmf_dolphin_hal.c | 52 ++++++ dtmf_dolphin_hal.h | 32 ++++ dtmf_dolphin_i.h | 52 ++++++ dtmf_dolphin_player.c | 156 ++++++++++++++++ dtmf_dolphin_player.h | 47 +++++ dtmf_dolphin_tone.c | 84 +++++++++ dtmf_dolphin_tone.h | 25 +++ scenes/dtmf_dolphin_scene.c | 30 +++ scenes/dtmf_dolphin_scene.h | 29 +++ scenes/dtmf_dolphin_scene_bluebox.c | 16 ++ scenes/dtmf_dolphin_scene_config.h | 3 + scenes/dtmf_dolphin_scene_dialer.c | 24 +++ scenes/dtmf_dolphin_scene_start.c | 61 +++++++ tones/dtmf_dolphin_bluebox_tones.h | 29 +++ tones/dtmf_dolphin_dialer_tones.h | 43 +++++ views/dtmf_dolphin_bluebox.c | 131 ++++++++++++++ views/dtmf_dolphin_bluebox.h | 15 ++ views/dtmf_dolphin_common.h | 8 + views/dtmf_dolphin_dialer.c | 271 ++++++++++++++++++++++++++++ views/dtmf_dolphin_dialer.h | 17 ++ 24 files changed, 1293 insertions(+) create mode 100644 README.md create mode 100644 application.fam create mode 100644 dtmf_dolphin.c create mode 100644 dtmf_dolphin_event.h create mode 100644 dtmf_dolphin_hal.c create mode 100644 dtmf_dolphin_hal.h create mode 100644 dtmf_dolphin_i.h create mode 100644 dtmf_dolphin_player.c create mode 100644 dtmf_dolphin_player.h create mode 100644 dtmf_dolphin_tone.c create mode 100644 dtmf_dolphin_tone.h create mode 100644 scenes/dtmf_dolphin_scene.c create mode 100644 scenes/dtmf_dolphin_scene.h create mode 100644 scenes/dtmf_dolphin_scene_bluebox.c create mode 100644 scenes/dtmf_dolphin_scene_config.h create mode 100644 scenes/dtmf_dolphin_scene_dialer.c create mode 100644 scenes/dtmf_dolphin_scene_start.c create mode 100644 tones/dtmf_dolphin_bluebox_tones.h create mode 100644 tones/dtmf_dolphin_dialer_tones.h create mode 100644 views/dtmf_dolphin_bluebox.c create mode 100644 views/dtmf_dolphin_bluebox.h create mode 100644 views/dtmf_dolphin_common.h create mode 100644 views/dtmf_dolphin_dialer.c create mode 100644 views/dtmf_dolphin_dialer.h diff --git a/README.md b/README.md new file mode 100644 index 00000000000..64453d7da7e --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## DTMF Dolphin + +Dialer, and future Bluebox and Redbox for the Flipper Zero. + +Documentation and code completion pending. This is a work in progress. \ No newline at end of file diff --git a/application.fam b/application.fam new file mode 100644 index 00000000000..977b373498b --- /dev/null +++ b/application.fam @@ -0,0 +1,13 @@ +App( + appid="dtmf_dolphin", + name="DTMF Dolphin", + apptype=FlipperAppType.EXTERNAL, + entry_point="dtmf_dolphin_app", + cdefines=["DTMF_DOLPHIN"], + requires=[ + "gui", + "dialogs", + ], + stack_size=4 * 1024, + order=20, +) diff --git a/dtmf_dolphin.c b/dtmf_dolphin.c new file mode 100644 index 00000000000..0833ba6f72e --- /dev/null +++ b/dtmf_dolphin.c @@ -0,0 +1,136 @@ +#include "dtmf_dolphin_i.h" + +#include +#include + +static bool dtmf_dolphin_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + DTMFDolphinApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool dtmf_dolphin_app_back_event_callback(void* context) { + furi_assert(context); + DTMFDolphinApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void dtmf_dolphin_app_tick_event_callback(void* context) { + furi_assert(context); + DTMFDolphinApp* app = context; + + // Needed to handle queueing to ISR and prioritization of audio + if (app->player.playing) { + dtmf_dolphin_player_handle_tick(); + } else { + scene_manager_handle_tick_event(app->scene_manager); + } +} + +static DTMFDolphinApp* app_alloc() { + DTMFDolphinApp* app = malloc(sizeof(DTMFDolphinApp)); + app->player.half_samples = 4 * 1024; + app->player.sample_count = 8 * 1024; + app->player.sample_buffer = malloc(sizeof(uint16_t) * app->player.sample_count); + app->player.buffer_buffer = malloc(sizeof(uint8_t) * app->player.sample_count); + app->player.wf1_period = 0; + app->player.wf2_period = 0; + app->player.wf1_freq = 0; + app->player.wf2_freq = 0; + app->player.wf1_pos = 0; + app->player.wf2_pos = 0; + app->player.queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinEvent)); + app->player.volume = 2.0f; + app->player.playing = false; + + app->gui = furi_record_open(RECORD_GUI); + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&dtmf_dolphin_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, dtmf_dolphin_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, dtmf_dolphin_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, dtmf_dolphin_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->main_menu_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DTMFDolphinViewMainMenu, + variable_item_list_get_view(app->main_menu_list)); + + app->dtmf_dolphin_dialer = dtmf_dolphin_dialer_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DTMFDolphinViewDialer, + dtmf_dolphin_dialer_get_view(app->dtmf_dolphin_dialer)); + + app->dtmf_dolphin_bluebox = dtmf_dolphin_bluebox_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DTMFDolphinViewBluebox, + dtmf_dolphin_bluebox_get_view(app->dtmf_dolphin_bluebox)); + + app->dtmf_dolphin_play = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + DTMFDolphinViewPlay, + widget_get_view(app->dtmf_dolphin_play)); + + // app->dialer_button_panel = button_panel_alloc(); + // app->bluebox_button_panel = button_panel_alloc(); + // app->redbox_button_panel = button_panel_alloc(); + + app->notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(app->notification, &sequence_display_backlight_enforce_on); + + scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneStart); + + return app; +} + +static void app_free(DTMFDolphinApp* app) { + furi_assert(app); + view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewMainMenu); + view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewBluebox); + view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewDialer); + view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewPlay); + variable_item_list_free(app->main_menu_list); + + dtmf_dolphin_bluebox_free(app->dtmf_dolphin_bluebox); + dtmf_dolphin_dialer_free(app->dtmf_dolphin_dialer); + widget_free(app->dtmf_dolphin_play); + + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + furi_message_queue_free(app->player.queue); + free(app->player.sample_buffer); + + // button_panel_free(app->dialer_button_panel); + // button_panel_free(app->bluebox_button_panel); + // button_panel_free(app->redbox_button_panel); + + notification_message(app->notification, &sequence_display_backlight_enforce_auto); + + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + free(app); +} + +int32_t dtmf_dolphin_app(void *p) { + UNUSED(p); + DTMFDolphinApp* app = app_alloc(); + + dtmf_dolphin_player_init(&(app->player)); + + view_dispatcher_run(app->view_dispatcher); + + app_free(app); + return 0; +} \ No newline at end of file diff --git a/dtmf_dolphin_event.h b/dtmf_dolphin_event.h new file mode 100644 index 00000000000..26412da7965 --- /dev/null +++ b/dtmf_dolphin_event.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + DTMFDolphinEventVolumeUp = 0, + DTMFDolphinEventVolumeDown, + DTMFDolphinBlueboxOkCB, + DTMFDolphinEventStartDialer, + DTMFDolphinEventStartBluebox, + DTMFDolphinEventStartRedbox, + DTMFDolphinEventPlayTones, + DTMFDolphinEventStopTones, + DTMFDolphinPlayerEventHalfTransfer, + DTMFDolphinPlayerEventFullTransfer, +} DTMFDolphinEvent; \ No newline at end of file diff --git a/dtmf_dolphin_hal.c b/dtmf_dolphin_hal.c new file mode 100644 index 00000000000..4de9860cfee --- /dev/null +++ b/dtmf_dolphin_hal.c @@ -0,0 +1,52 @@ +#include "dtmf_dolphin_hal.h" + +void dtmf_dolphin_speaker_init() { + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = 4; + TIM_InitStruct.Autoreload = 255; + LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct); + + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = 127; + LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct); +} + +void dtmf_dolphin_speaker_start() { + LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER); +} + +void dtmf_dolphin_speaker_stop() { + LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); +} + +void dtmf_dolphin_dma_init(uint32_t address, size_t size) { + uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1); + + LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); + LL_DMA_SetDataLength(DMA_INSTANCE, size); + + LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM16_UP); + LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); + LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH); + LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR); + LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT); + LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT); + LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD); + LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD); + + LL_DMA_EnableIT_TC(DMA_INSTANCE); + LL_DMA_EnableIT_HT(DMA_INSTANCE); +} + +void dtmf_dolphin_dma_start() { + LL_DMA_EnableChannel(DMA_INSTANCE); + LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_SPEAKER_TIMER); +} + +void dtmf_dolphin_dma_stop() { + LL_DMA_DisableChannel(DMA_INSTANCE); +} diff --git a/dtmf_dolphin_hal.h b/dtmf_dolphin_hal.h new file mode 100644 index 00000000000..a5dab1a6ea3 --- /dev/null +++ b/dtmf_dolphin_hal.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#define FURI_HAL_SPEAKER_TIMER TIM16 +#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 +#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1 + +#ifdef __cplusplus +extern "C" { +#endif + +void dtmf_dolphin_speaker_init(); + +void dtmf_dolphin_speaker_start(); + +void dtmf_dolphin_speaker_stop(); + +void dtmf_dolphin_dma_init(uint32_t address, size_t size); + +void dtmf_dolphin_dma_start(); + +void dtmf_dolphin_dma_stop(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/dtmf_dolphin_i.h b/dtmf_dolphin_i.h new file mode 100644 index 00000000000..c07b8b63e6b --- /dev/null +++ b/dtmf_dolphin_i.h @@ -0,0 +1,52 @@ +#pragma once + +#include "scenes/dtmf_dolphin_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dtmf_dolphin_event.h" +#include "dtmf_dolphin_player.h" + +#include "views/dtmf_dolphin_dialer.h" +#include "views/dtmf_dolphin_bluebox.h" + +#define TAG "DTMFDolphin" + + +enum DTMFDolphinItem { + DTMFDolphinItemDialer, + DTMFDolphinItemBluebox, + DTMFDolphinItemRedbox, + DTMFDolphinItemPlay +}; + +typedef struct { + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + VariableItemList* main_menu_list; + DTMFDolphinDialer* dtmf_dolphin_dialer; + DTMFDolphinBluebox* dtmf_dolphin_bluebox; + DTMFDolphinPlayer player; + Widget* dtmf_dolphin_play; + + Gui* gui; + // ButtonPanel* dialer_button_panel; + // ButtonPanel* bluebox_button_panel; + // ButtonPanel* redbox_button_panel; + NotificationApp* notification; +} DTMFDolphinApp; + +typedef enum { + DTMFDolphinViewMainMenu, + DTMFDolphinViewDialer, + DTMFDolphinViewBluebox, + DTMFDolphinViewRedbox, + DTMFDolphinViewPlay, +} DTMFDolphinView; diff --git a/dtmf_dolphin_player.c b/dtmf_dolphin_player.c new file mode 100644 index 00000000000..ead58af0e86 --- /dev/null +++ b/dtmf_dolphin_player.c @@ -0,0 +1,156 @@ +#include "dtmf_dolphin_player.h" + +#define DTMF_DOLPHIN_SAMPLE_RATE (8000) + +typedef struct { + DTMFDolphinEvent type; +} DTMFDolphinPlayerEvent; + +// Keep this here for accessibility in local scope event without context +DTMFDolphinPlayer* player; + +void dtmf_dolphin_dma_isr(void* ctx) { + FuriMessageQueue *event_queue = ctx; + + if (LL_DMA_IsActiveFlag_HT1(DMA1)) { + LL_DMA_ClearFlag_HT1(DMA1); + + DTMFDolphinPlayerEvent event = {.type = DTMFDolphinPlayerEventHalfTransfer}; + furi_message_queue_put(event_queue, &event, 0); + } + + if(LL_DMA_IsActiveFlag_TC1(DMA1)) { + LL_DMA_ClearFlag_TC1(DMA1); + + DTMFDolphinPlayerEvent event = {.type = DTMFDolphinPlayerEventFullTransfer}; + furi_message_queue_put(event_queue, &event, 0); + } +} + +bool dtmf_dolphin_player_init(void* context) { + player = context; + + return false; +} + +void dtmf_dolphin_player_clear_samples() { + for (size_t i = 0; i < player->sample_count; i++) { + player->sample_buffer[i] = 0; + } +} + +bool dtmf_dolphin_player_generate_waveform(size_t index) { + uint16_t* sample_buffer_start = &player->sample_buffer[index]; + if (!player->wf1_freq) + return false; + + // Generate basic sine wave sample to fill sample_count + for (size_t i = 0; i < player->half_samples; i++) { + // float data = sin(i * PERIOD_2_PI / player->wf1_period) + 1; + float data = sin(player->wf1_pos * PERIOD_2_PI / player->wf1_period) + 1; + player->wf1_pos = (player->wf1_pos + 1) % player->wf1_period; + + data *= player->volume; + + // Downmix second tone with the first + if (player->wf2_freq) { + data /= 2; + + float data_2 = sin(player->wf2_pos * PERIOD_2_PI / player->wf2_period) + 1; + player->wf2_pos = (player->wf2_pos + 1) % player->wf2_period; + + data_2 *= player->volume / 2; + + data += data_2; + } + + data = tanhf(data); + + data *= UINT8_MAX / 2; // scale -128..127 + data += UINT8_MAX / 2; // to unsigned + + if(data < 0) { + data = 0; + } + + if(data > 255) { + data = 255; + } + + player->buffer_buffer[i] = data; + sample_buffer_start[i] = data; + } + + return true; +} + +bool dtmf_dolphin_player_play_tones(float *freq) { + player->wf1_pos = 0; + player->wf2_pos = 0; + player->wf1_freq = 0; + player->wf2_freq = 0; + player->wf1_period = 0; + player->wf2_period = 0; + if (freq[0]) { + player->wf1_freq = freq[0]; + player->wf1_period = player->sample_count / freq[0] * 4; + } + if (freq[1]) { + player->wf2_freq = freq[1]; + player->wf2_period = player->sample_count / freq[1] * 4; + } + dtmf_dolphin_player_clear_samples(); + + dtmf_dolphin_player_generate_waveform(0); + dtmf_dolphin_player_generate_waveform(player->half_samples); + + dtmf_dolphin_speaker_init(); + dtmf_dolphin_dma_init((uint32_t)player->sample_buffer, player->sample_count); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, dtmf_dolphin_dma_isr, player->queue); + + dtmf_dolphin_dma_start(); + dtmf_dolphin_speaker_start(); + + player->playing = true; + + return true; +} + +bool dtmf_dolphin_player_stop_tones() { + player->playing = false; + + dtmf_dolphin_speaker_stop(); + dtmf_dolphin_dma_stop(); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + + return true; +} + +bool dtmf_dolphin_player_handle_tick() { + DTMFDolphinPlayerEvent event; + + if(furi_message_queue_get(player->queue, &event, FuriWaitForever) == FuriStatusOk) { + if (player->playing) { + if(event.type == DTMFDolphinPlayerEventHalfTransfer) { + dtmf_dolphin_player_generate_waveform(0); + // uint16_t* sample_buffer_start = &player->sample_buffer[0]; + // for (size_t i = 0; i < player->half_samples; i++) { + // sample_buffer_start[i] = player->buffer_buffer[i]; + // } + return true; + } else if (event.type == DTMFDolphinPlayerEventFullTransfer) { + dtmf_dolphin_player_generate_waveform(player->half_samples); + // uint16_t* sample_buffer_start = &player->sample_buffer[player->half_samples]; + // for (size_t i = 0; i < player->half_samples; i++) { + // sample_buffer_start[i] = player->buffer_buffer[i]; + // } + return true; + } + } else { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/dtmf_dolphin_player.h b/dtmf_dolphin_player.h new file mode 100644 index 00000000000..f33fb182fa5 --- /dev/null +++ b/dtmf_dolphin_player.h @@ -0,0 +1,47 @@ +#pragma once +#include "dtmf_dolphin_event.h" +#include "dtmf_dolphin_hal.h" +#include "dtmf_dolphin_tone.h" + +#define PERIOD_2_PI 6.2832 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + size_t half_samples; + size_t sample_count; + float wf1_freq; + float wf2_freq; + uint16_t wf1_period; + uint16_t wf2_period; + uint16_t wf1_pos; + uint16_t wf2_pos; + uint8_t *buffer_buffer; + uint16_t *sample_buffer; + float volume; + bool playing; + + FuriMessageQueue* queue; +} DTMFDolphinPlayer; + +void dtmf_dolphin_dma_isr(void* ctx); + +bool dtmf_dolphin_player_init(void* context); + +void dtmf_dolphin_player_clear_samples(); + +bool dtmf_dolphin_player_generate_waveform(size_t index); + +bool dtmf_dolphin_player_play_tones(float *freq); + +bool dtmf_dolphin_player_stop_tones(); + +bool dtmf_dolphin_player_handle_tick(); + +#ifdef __cplusplus +} +#endif + + diff --git a/dtmf_dolphin_tone.c b/dtmf_dolphin_tone.c new file mode 100644 index 00000000000..45491df68fc --- /dev/null +++ b/dtmf_dolphin_tone.c @@ -0,0 +1,84 @@ +#include "dtmf_dolphin_tone.h" + +const char* dtmf_dolphin_get_tone_name(uint8_t row, uint8_t col, DTMFDolphinToneSection block) { + if (block == DTMF_DOLPHIN_TONE_BLOCK_DIALER) { + for (int i = 0; i < DTMF_DOLPHIN_DIALER_TONE_COUNT; i++) { + if (dtmf_dolphin_dialer_tone[i].pos.row == row && dtmf_dolphin_dialer_tone[i].pos.col == col) { + return dtmf_dolphin_dialer_tone[i].name; + } + } + } else if (block == DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX) { + // if (index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + // return dtmf_dolphin_bluebox_tone[index].name; + // } + } + return "N"; +} + +uint8_t dtmf_dolphin_get_tone_span(uint8_t row, uint8_t col, DTMFDolphinToneSection block) { + if (block == DTMF_DOLPHIN_TONE_BLOCK_DIALER) { + for (int i = 0; i < DTMF_DOLPHIN_DIALER_TONE_COUNT; i++) { + if (dtmf_dolphin_dialer_tone[i].pos.row == row && dtmf_dolphin_dialer_tone[i].pos.col == col) { + return dtmf_dolphin_dialer_tone[i].pos.span; + } + } + } else if (block == DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX) { + // if (index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + // return dtmf_dolphin_bluebox_tone[index].name; + // } + } + return 1; // Default to 1 +} + +void dtmf_dolphin_get_tone_frequencies(float *freq, uint8_t row, uint8_t col, DTMFDolphinToneSection block) { + freq[0] = 0; + freq[1] = 0; + + if (block == DTMF_DOLPHIN_TONE_BLOCK_DIALER) { + for (int i = 0; i < DTMF_DOLPHIN_DIALER_TONE_COUNT; i++) { + if (dtmf_dolphin_dialer_tone[i].pos.row == row && dtmf_dolphin_dialer_tone[i].pos.col == col) { + freq[0] = dtmf_dolphin_dialer_tone[i].frequency_1; + freq[1] = dtmf_dolphin_dialer_tone[i].frequency_2; + } + } + } else if (block == DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX) { + // if (index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + // return dtmf_dolphin_bluebox_tone[index].name; + // } + } +} + +void dtmf_dolphin_tone_get_max_pos(uint8_t *max_rows, uint8_t *max_cols, uint8_t *max_span, DTMFDolphinToneSection block) { + max_rows[0] = 0; + max_cols[0] = 0; + max_span[0] = 0; + uint8_t span[8] = { 0 }; + if (block == DTMF_DOLPHIN_TONE_BLOCK_DIALER) { + for (int i = 0; i < DTMF_DOLPHIN_DIALER_TONE_COUNT; i++) { + if (dtmf_dolphin_dialer_tone[i].pos.row > max_rows[0]) + max_rows[0] = dtmf_dolphin_dialer_tone[i].pos.row; + if (dtmf_dolphin_dialer_tone[i].pos.col > max_cols[0]) + max_cols[0] = dtmf_dolphin_dialer_tone[i].pos.col; + span[dtmf_dolphin_dialer_tone[i].pos.row] += dtmf_dolphin_dialer_tone[i].pos.span; + } + max_rows[0]++; + max_cols[0]++; + for (int i = 0; i < max_rows[0]; i++) { + if (span[i] > max_span[0]) + max_span[0] = span[i]; + } + } else if (block == DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX) { + // if (index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + // for (int i; i < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT; i++) { + // if (dtmf_dolphin_bluebox_tone[i].pos.row > max_rows) + // max_rows = dtmf_dolphin_bluebox_tone[i].pos.row; + // if (dtmf_dolphin_bluebox_tone[i].pos.col > max_cols) + // max_cols = dtmf_dolphin_bluebox_tone[i].pos.col; + // } + } +} + +// void dtmf_dolphin_bluebox_generate(uint8_t index, uint8_t *buffer) { + +// // TODO: Generate the waveform +// } diff --git a/dtmf_dolphin_tone.h b/dtmf_dolphin_tone.h new file mode 100644 index 00000000000..9c0befe55a5 --- /dev/null +++ b/dtmf_dolphin_tone.h @@ -0,0 +1,25 @@ +#pragma once +#include "tones/dtmf_dolphin_dialer_tones.h" +#include "tones/dtmf_dolphin_bluebox_tones.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + DTMF_DOLPHIN_TONE_BLOCK_DIALER, + DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX, + DTMF_DOLPHIN_TONE_BLOCK_REDBOX, +} DTMFDolphinToneSection; + +const char* dtmf_dolphin_get_tone_name(uint8_t row, uint8_t col, DTMFDolphinToneSection block); + +uint8_t dtmf_dolphin_get_tone_span(uint8_t row, uint8_t col, DTMFDolphinToneSection block); + +void dtmf_dolphin_get_tone_frequencies(float *freq, uint8_t row, uint8_t col, DTMFDolphinToneSection block); + +void dtmf_dolphin_tone_get_max_pos(uint8_t *max_rows, uint8_t *max_cols, uint8_t *max_span, DTMFDolphinToneSection block); + +#ifdef __cplusplus +} +#endif diff --git a/scenes/dtmf_dolphin_scene.c b/scenes/dtmf_dolphin_scene.c new file mode 100644 index 00000000000..bfb8da1daf1 --- /dev/null +++ b/scenes/dtmf_dolphin_scene.c @@ -0,0 +1,30 @@ +#include "dtmf_dolphin_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const dtmf_dolphin_scene_on_enter_handlers[])(void*) = { +#include "dtmf_dolphin_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const dtmf_dolphin_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "dtmf_dolphin_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const dtmf_dolphin_scene_on_exit_handlers[])(void* context) = { +#include "dtmf_dolphin_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers dtmf_dolphin_scene_handlers = { + .on_enter_handlers = dtmf_dolphin_scene_on_enter_handlers, + .on_event_handlers = dtmf_dolphin_scene_on_event_handlers, + .on_exit_handlers = dtmf_dolphin_scene_on_exit_handlers, + .scene_num = DTMFDolphinSceneNum, +}; diff --git a/scenes/dtmf_dolphin_scene.h b/scenes/dtmf_dolphin_scene.h new file mode 100644 index 00000000000..e45dc68ae35 --- /dev/null +++ b/scenes/dtmf_dolphin_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) DTMFDolphinScene##id, +typedef enum { +#include "dtmf_dolphin_scene_config.h" + DTMFDolphinSceneNum, +} DTMFDolphinScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers dtmf_dolphin_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "dtmf_dolphin_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "dtmf_dolphin_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "dtmf_dolphin_scene_config.h" +#undef ADD_SCENE diff --git a/scenes/dtmf_dolphin_scene_bluebox.c b/scenes/dtmf_dolphin_scene_bluebox.c new file mode 100644 index 00000000000..3af221280eb --- /dev/null +++ b/scenes/dtmf_dolphin_scene_bluebox.c @@ -0,0 +1,16 @@ +#include "../dtmf_dolphin_i.h" + +void dtmf_dolphin_scene_bluebox_on_enter(void *context) { + DTMFDolphinApp* app = context; + view_dispatcher_switch_to_view(app->view_dispatcher, DTMFDolphinViewBluebox); +} + +bool dtmf_dolphin_scene_bluebox_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void dtmf_dolphin_scene_bluebox_on_exit(void* context) { + UNUSED(context); +} diff --git a/scenes/dtmf_dolphin_scene_config.h b/scenes/dtmf_dolphin_scene_config.h new file mode 100644 index 00000000000..644525cd003 --- /dev/null +++ b/scenes/dtmf_dolphin_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(dtmf_dolphin, start, Start) +ADD_SCENE(dtmf_dolphin, dialer, Dialer) +ADD_SCENE(dtmf_dolphin, bluebox, Bluebox) \ No newline at end of file diff --git a/scenes/dtmf_dolphin_scene_dialer.c b/scenes/dtmf_dolphin_scene_dialer.c new file mode 100644 index 00000000000..faf3d617f28 --- /dev/null +++ b/scenes/dtmf_dolphin_scene_dialer.c @@ -0,0 +1,24 @@ +#include "../dtmf_dolphin_i.h" + +void dtmf_dolphin_scene_dialer_on_enter(void *context) { + DTMFDolphinApp* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, DTMFDolphinViewDialer); +} + +bool dtmf_dolphin_scene_dialer_on_event(void* context, SceneManagerEvent event) { + DTMFDolphinApp* app = context; + UNUSED(app); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + } + + return consumed; + return false; +} + +void dtmf_dolphin_scene_dialer_on_exit(void* context) { + UNUSED(context); +} diff --git a/scenes/dtmf_dolphin_scene_start.c b/scenes/dtmf_dolphin_scene_start.c new file mode 100644 index 00000000000..d61ceda6eb0 --- /dev/null +++ b/scenes/dtmf_dolphin_scene_start.c @@ -0,0 +1,61 @@ +#include "../dtmf_dolphin_i.h" + +static void dtmf_dolphin_scene_start_main_menu_enter_callback(void* context, uint32_t index) { + DTMFDolphinApp* app = context; + if (index == DTMFDolphinItemDialer) { + view_dispatcher_send_custom_event( + app->view_dispatcher, + DTMFDolphinEventStartDialer + ); + } else if (index == DTMFDolphinItemBluebox) { + view_dispatcher_send_custom_event( + app->view_dispatcher, + DTMFDolphinEventStartBluebox + ); + } +} + +void dtmf_dolphin_scene_start_on_enter(void* context) { + DTMFDolphinApp* app = context; + VariableItemList* var_item_list = app->main_menu_list; + + // VariableItem* item; + variable_item_list_set_enter_callback( + var_item_list, + dtmf_dolphin_scene_start_main_menu_enter_callback, + app); + + variable_item_list_add(var_item_list, "Dialer", 0, NULL, NULL); + // variable_item_list_add(var_item_list, "Bluebox", 0, NULL, NULL); + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, DTMFDolphinSceneStart)); + + view_dispatcher_switch_to_view( + app->view_dispatcher, + DTMFDolphinViewMainMenu); +} + +bool dtmf_dolphin_scene_start_on_event(void* context, SceneManagerEvent event) { + DTMFDolphinApp* app = context; + UNUSED(app); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if (event.event == DTMFDolphinEventStartDialer) { + scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinItemDialer); + scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer); + } else if (event.event == DTMFDolphinEventStartBluebox) { + scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneBluebox, DTMFDolphinItemBluebox); + scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneBluebox); + } + consumed = true; + } + return consumed; +} + +void dtmf_dolphin_scene_start_on_exit(void* context) { + DTMFDolphinApp* app = context; + variable_item_list_reset(app->main_menu_list); +} diff --git a/tones/dtmf_dolphin_bluebox_tones.h b/tones/dtmf_dolphin_bluebox_tones.h new file mode 100644 index 00000000000..4ac053ea82e --- /dev/null +++ b/tones/dtmf_dolphin_bluebox_tones.h @@ -0,0 +1,29 @@ +#pragma once +#include + +#define DTMF_DOLPHIN_BLUEBOX_TONE_COUNT 16 + +typedef struct { + const char *name; + const float frequency_1; + const float frequency_2; +} DTMFDolphinBlueboxTones; + +static const DTMFDolphinBlueboxTones dtmf_dolphin_bluebox_tone[DTMF_DOLPHIN_BLUEBOX_TONE_COUNT] = { + {"2600 Hz", 2600.0, 0.0}, + {"1", 700.0, 900.0}, + {"2", 700.0, 1100.0}, + {"3", 900.0, 1100.0}, + {"4", 700.0, 1300.0}, + {"5", 900.0, 1300.0}, + {"6", 1100.0, 1300.0}, + {"7", 700.0, 1500.0}, + {"8", 900.0, 1500.0}, + {"9", 1100.0, 1500.0}, + {"0", 1300.0, 1500.0}, + {"Key Pulse (KP)", 1100.0, 1700.0}, + {"Start (ST)", 1500.0, 1700.0}, + {"CCITT 11", 700.0, 1700.0}, + {"CCITT 12", 900.0, 1700.0}, + {"CCITT KP2", 1300.0, 1700.0}, +}; diff --git a/tones/dtmf_dolphin_dialer_tones.h b/tones/dtmf_dolphin_dialer_tones.h new file mode 100644 index 00000000000..e9e462aaf77 --- /dev/null +++ b/tones/dtmf_dolphin_dialer_tones.h @@ -0,0 +1,43 @@ +#pragma once +#include + +#define DTMF_DOLPHIN_DIALER_TONE_COUNT 16 + +typedef struct DTMFDolphinDialerTonePos { + const uint8_t row; + const uint8_t col; + const uint8_t span; +} DTMFDolphinDialerTonePos; + +typedef struct { + const char *name; + const float frequency_1; + const float frequency_2; + const struct DTMFDolphinDialerTonePos pos; +} DTMFDolphinDialerTones; + +/* Via https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling + 1209 Hz 1336 Hz 1477 Hz 1633 Hz +697 Hz 1 2 3 A +770 Hz 4 5 6 B +852 Hz 7 8 9 C +941 Hz * 0 # D */ + +static const DTMFDolphinDialerTones dtmf_dolphin_dialer_tone[DTMF_DOLPHIN_DIALER_TONE_COUNT] = { + {"1", 697.0, 1209.0, {0, 0, 1}}, + {"2", 697.0, 1336.0, {0, 1, 1}}, + {"3", 697.0, 1477.0, {0, 2, 1}}, + {"A", 697.0, 1633.0, {0, 3, 1}}, + {"4", 770.0, 1209.0, {1, 0, 1}}, + {"5", 770.0, 1336.0, {1, 1, 1}}, + {"6", 770.0, 1477.0, {1, 2, 1}}, + {"B", 770.0, 1633.0, {1, 3, 1}}, + {"7", 852.0, 1209.0, {2, 0, 1}}, + {"8", 852.0, 1336.0, {2, 1, 1}}, + {"9", 852.0, 1477.0, {2, 2, 1}}, + {"C", 852.0, 1633.0, {2, 3, 1}}, + {"*", 941.0, 1209.0, {3, 0, 1}}, + {"0", 941.0, 1336.0, {3, 1, 1}}, + {"#", 941.0, 1477.0, {3, 2, 1}}, + {"D", 941.0, 1633.0, {3, 3, 1}}, +}; diff --git a/views/dtmf_dolphin_bluebox.c b/views/dtmf_dolphin_bluebox.c new file mode 100644 index 00000000000..ff2943ae220 --- /dev/null +++ b/views/dtmf_dolphin_bluebox.c @@ -0,0 +1,131 @@ +#include "dtmf_dolphin_bluebox.h" + +#include + +typedef struct DTMFDolphinBluebox { + View* view; + DTMFDolphinBlueboxOkCallback callback; + void* context; +} DTMFDolphinBluebox; + +typedef struct { + uint8_t index; +} DTMFDolphinBlueboxModel; + +static bool dtmf_dolphin_bluebox_process_left(DTMFDolphinBluebox* dtmf_dolphin_bluebox); +static bool dtmf_dolphin_bluebox_process_right(DTMFDolphinBluebox* dtmf_dolphin_bluebox); +static bool dtmf_dolphin_bluebox_process_ok(DTMFDolphinBluebox* dtmf_dolphin_bluebox, InputEvent* event); + +static void dtmf_dolphin_bluebox_draw_callback(Canvas* canvas, void* _model) { + DTMFDolphinBlueboxModel* model = _model; + UNUSED(model); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Bluebox Mode"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to select"); + // elements_multiline_text_aligned( + // canvas, 64, 32, AlignCenter, AlignTop, dtmf_dolphin_get_tone_name(model->index, DTMF_DOLPHIN_TONE_BLOCK_BLUEBOX)); +} + +static bool dtmf_dolphin_bluebox_input_callback(InputEvent* event, void* context) { + furi_assert(context); + DTMFDolphinBluebox* dtmf_dolphin_bluebox = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = dtmf_dolphin_bluebox_process_right(dtmf_dolphin_bluebox); + } else if(event->key == InputKeyLeft) { + consumed = dtmf_dolphin_bluebox_process_left(dtmf_dolphin_bluebox); + } + } else if(event->key == InputKeyOk) { + consumed = dtmf_dolphin_bluebox_process_ok(dtmf_dolphin_bluebox, event); + } + + return consumed; +} + +static bool dtmf_dolphin_bluebox_process_left(DTMFDolphinBluebox* dtmf_dolphin_bluebox) { + with_view_model( + dtmf_dolphin_bluebox->view, (DTMFDolphinBlueboxModel * model) { + if(model->index) { + model->index--; + } + return true; + }); + return true; +} + +static bool dtmf_dolphin_bluebox_process_right(DTMFDolphinBluebox* dtmf_dolphin_bluebox) { + with_view_model( + dtmf_dolphin_bluebox->view, (DTMFDolphinBlueboxModel * model) { + if(model->index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + model->index++; + } + return true; + }); + return true; +} + +static bool dtmf_dolphin_bluebox_process_ok(DTMFDolphinBluebox* dtmf_dolphin_bluebox, InputEvent* event) { + bool consumed = false; + + with_view_model( + dtmf_dolphin_bluebox->view, (DTMFDolphinBlueboxModel* model) { + if(event->type == InputTypePress) { + if(model->index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + // TODO: Do the thing + } else { + // TODO: Do the thing + } + consumed = true; + } else if(event->type == InputTypeRelease) { + if(model->index < DTMF_DOLPHIN_BLUEBOX_TONE_COUNT) { + // gpio_item_set_pin(Model->pin_idx, false); + } else { + // gpio_item_set_all_pins(false); + } + consumed = true; + } + dtmf_dolphin_bluebox->callback(event->type, dtmf_dolphin_bluebox->context); + return true; + }); + + return consumed; +} + +DTMFDolphinBluebox* dtmf_dolphin_bluebox_alloc() { + DTMFDolphinBluebox* dtmf_dolphin_bluebox = malloc(sizeof(DTMFDolphinBluebox)); + + dtmf_dolphin_bluebox->view = view_alloc(); + view_allocate_model(dtmf_dolphin_bluebox->view, ViewModelTypeLocking, sizeof(DTMFDolphinBlueboxModel)); + view_set_context(dtmf_dolphin_bluebox->view, dtmf_dolphin_bluebox); + view_set_draw_callback(dtmf_dolphin_bluebox->view, dtmf_dolphin_bluebox_draw_callback); + view_set_input_callback(dtmf_dolphin_bluebox->view, dtmf_dolphin_bluebox_input_callback); + + return dtmf_dolphin_bluebox; +} + +void dtmf_dolphin_bluebox_free(DTMFDolphinBluebox* dtmf_dolphin_bluebox) { + furi_assert(dtmf_dolphin_bluebox); + view_free(dtmf_dolphin_bluebox->view); + free(dtmf_dolphin_bluebox); +} + +View* dtmf_dolphin_bluebox_get_view(DTMFDolphinBluebox* dtmf_dolphin_bluebox) { + furi_assert(dtmf_dolphin_bluebox); + return dtmf_dolphin_bluebox->view; +} + +void dtmf_dolphin_bluebox_set_ok_callback(DTMFDolphinBluebox* dtmf_dolphin_bluebox, DTMFDolphinBlueboxOkCallback callback, void* context) { + furi_assert(dtmf_dolphin_bluebox); + furi_assert(callback); + with_view_model( + dtmf_dolphin_bluebox->view, (DTMFDolphinBlueboxModel * model) { + UNUSED(model); + dtmf_dolphin_bluebox->callback = callback; + dtmf_dolphin_bluebox->context = context; + return false; + }); +} diff --git a/views/dtmf_dolphin_bluebox.h b/views/dtmf_dolphin_bluebox.h new file mode 100644 index 00000000000..2f997595859 --- /dev/null +++ b/views/dtmf_dolphin_bluebox.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "../dtmf_dolphin_tone.h" + +typedef struct DTMFDolphinBluebox DTMFDolphinBluebox; +typedef void (*DTMFDolphinBlueboxOkCallback)(InputType type, void* context); + +DTMFDolphinBluebox* dtmf_dolphin_bluebox_alloc(); + +void dtmf_dolphin_bluebox_free(DTMFDolphinBluebox* dtmf_dolphin_bluebox); + +View* dtmf_dolphin_bluebox_get_view(DTMFDolphinBluebox* dtmf_dolphin_bluebox); + +void dtmf_dolphin_bluebox_set_ok_callback(DTMFDolphinBluebox* dtmf_dolphin_bluebox, DTMFDolphinBlueboxOkCallback callback, void* context); diff --git a/views/dtmf_dolphin_common.h b/views/dtmf_dolphin_common.h new file mode 100644 index 00000000000..e1ffa01b03a --- /dev/null +++ b/views/dtmf_dolphin_common.h @@ -0,0 +1,8 @@ +#pragma once +#include "../dtmf_dolphin_player.h" + +#define DTMF_DOLPHIN_NUMPAD_X 1 +#define DTMF_DOLPHIN_NUMPAD_Y 14 +#define DTMF_DOLPHIN_BUTTON_WIDTH 13 +#define DTMF_DOLPHIN_BUTTON_HEIGHT 13 +#define DTMF_DOLPHIN_BUTTON_PADDING 1 // all sides diff --git a/views/dtmf_dolphin_dialer.c b/views/dtmf_dolphin_dialer.c new file mode 100644 index 00000000000..eddfa0dde1a --- /dev/null +++ b/views/dtmf_dolphin_dialer.c @@ -0,0 +1,271 @@ +#include "dtmf_dolphin_dialer.h" + +#include + +typedef struct DTMFDolphinDialer { + View* view; + DTMFDolphinDialerOkCallback callback; + void* context; +} DTMFDolphinDialer; + +typedef struct { + uint8_t row; + uint8_t col; + float *freq; +} DTMFDolphinDialerModel; + +static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer); +static bool dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event); + +void draw_button(Canvas* canvas, uint8_t row, uint8_t col, bool invert) { + + uint8_t left = DTMF_DOLPHIN_NUMPAD_X + \ + // ((col + 1) * DTMF_DOLPHIN_BUTTON_PADDING) + + (col * DTMF_DOLPHIN_BUTTON_WIDTH); + // (col * DTMF_DOLPHIN_BUTTON_PADDING); + uint8_t top = DTMF_DOLPHIN_NUMPAD_Y + \ + // ((row + 1) * DTMF_DOLPHIN_BUTTON_PADDING) + + (row * DTMF_DOLPHIN_BUTTON_HEIGHT); + // (row * DTMF_DOLPHIN_BUTTON_PADDING); + + uint8_t span = dtmf_dolphin_get_tone_span(row, col, DTMF_DOLPHIN_TONE_BLOCK_DIALER); + + canvas_set_color(canvas, ColorBlack); + + if (invert) + canvas_draw_rbox(canvas, left, top, + (DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + DTMF_DOLPHIN_BUTTON_HEIGHT - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + 2); + else + canvas_draw_rframe(canvas, left, top, + (DTMF_DOLPHIN_BUTTON_WIDTH * span) - (DTMF_DOLPHIN_BUTTON_PADDING * 2), + DTMF_DOLPHIN_BUTTON_HEIGHT- (DTMF_DOLPHIN_BUTTON_PADDING * 2), + 2); + + if (invert) + canvas_invert_color(canvas); + + + canvas_set_font(canvas, FontSecondary); + // canvas_set_color(canvas, invert ? ColorWhite : ColorBlack); + canvas_draw_str_aligned(canvas, + left - 1 + (int) ((DTMF_DOLPHIN_BUTTON_WIDTH * span) / 2), + top + (int) (DTMF_DOLPHIN_BUTTON_HEIGHT / 2), + AlignCenter, + AlignCenter, + dtmf_dolphin_get_tone_name(row, col, DTMF_DOLPHIN_TONE_BLOCK_DIALER)); + + if (invert) + canvas_invert_color(canvas); +} + +void draw_dialer(Canvas* canvas, void* _model) { + DTMFDolphinDialerModel* model = _model; + uint8_t max_rows; + uint8_t max_cols; + uint8_t max_span; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span, DTMF_DOLPHIN_TONE_BLOCK_DIALER); + + canvas_set_font(canvas, FontSecondary); + + for (int r = 0; r < max_rows; r++) { + for (int c = 0; c < max_cols; c++) { + if (model->row == r && model->col == c) + draw_button(canvas, r, c, true); + else + draw_button(canvas, r, c, false); + } + } +} + +void update_frequencies(DTMFDolphinDialerModel *model) { + dtmf_dolphin_get_tone_frequencies(model->freq, model->row, model->col, DTMF_DOLPHIN_TONE_BLOCK_DIALER); +} + +static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) { + DTMFDolphinDialerModel* model = _model; + uint8_t max_rows; + uint8_t max_cols; + uint8_t max_span; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span, DTMF_DOLPHIN_TONE_BLOCK_DIALER); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, 2, 10, "Dialer"); + canvas_draw_line(canvas, + (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1, 0, + (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 1, canvas_height(canvas)); + elements_multiline_text(canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 10, "Detail"); + canvas_draw_line(canvas, 0, DTMF_DOLPHIN_NUMPAD_Y - 3, canvas_width(canvas), DTMF_DOLPHIN_NUMPAD_Y - 3); + // elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Dialer Mode"); + + draw_dialer(canvas, model); + + string_t output; + string_init(output); + + string_cat_printf( + output, + "F1: %u Hz\nF2: %u Hz", + model->freq[0] ? (unsigned int) model->freq[0] : 0, + model->freq[1] ? (unsigned int) model->freq[1] : 0); + + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + elements_multiline_text(canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, string_get_cstr(output)); + + string_clear(output); +} + +static bool dtmf_dolphin_dialer_input_callback(InputEvent* event, void* context) { + furi_assert(context); + DTMFDolphinDialer* dtmf_dolphin_dialer = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = dtmf_dolphin_dialer_process_right(dtmf_dolphin_dialer); + } else if(event->key == InputKeyLeft) { + consumed = dtmf_dolphin_dialer_process_left(dtmf_dolphin_dialer); + } else if(event->key == InputKeyUp) { + consumed = dtmf_dolphin_dialer_process_up(dtmf_dolphin_dialer); + } else if(event->key == InputKeyDown) { + consumed = dtmf_dolphin_dialer_process_down(dtmf_dolphin_dialer); + } + + } else if(event->key == InputKeyOk) { + consumed = dtmf_dolphin_dialer_process_ok(dtmf_dolphin_dialer, event); + } + + return consumed; +} + +static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer) { + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + if(model->row > 0) { + model->row--; + update_frequencies(model); + } + return true; + }); + return true; +} + +static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dialer) { + uint8_t max_rows = 0; + uint8_t max_cols = 0; + uint8_t max_span = 0; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span, DTMF_DOLPHIN_TONE_BLOCK_DIALER); + + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + if(model->row < max_rows - 1) { + model->row++; + update_frequencies(model); + } + return true; + }); + return true; +} + +static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer) { + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + if(model->col > 0) { + model->col--; + update_frequencies(model); + } + return true; + }); + return true; +} + +static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_dialer) { + uint8_t max_rows = 0; + uint8_t max_cols = 0; + uint8_t max_span = 0; + dtmf_dolphin_tone_get_max_pos(&max_rows, &max_cols, &max_span, DTMF_DOLPHIN_TONE_BLOCK_DIALER); + + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + if(model->col < max_cols - 1) { + model->col++; + update_frequencies(model); + } + return true; + }); + return true; +} + +static bool dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_dialer, InputEvent* event) { + bool consumed = false; + + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + if (event->type == InputTypePress) { + dtmf_dolphin_player_play_tones(model->freq); + } else if (event->type == InputTypeRelease) { + dtmf_dolphin_player_stop_tones(); + } + + return true; + }); + + return consumed; +} + +DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() { + DTMFDolphinDialer* dtmf_dolphin_dialer = malloc(sizeof(DTMFDolphinDialer)); + + dtmf_dolphin_dialer->view = view_alloc(); + view_allocate_model(dtmf_dolphin_dialer->view, ViewModelTypeLocking, sizeof(DTMFDolphinDialerModel)); + + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + model->col = 0; + model->row = 0; + model->freq = malloc(sizeof(float) * 2); + update_frequencies(model); + return true; + } + ); + + view_set_context(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer); + view_set_draw_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_draw_callback); + view_set_input_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_input_callback); + + return dtmf_dolphin_dialer; +} + +void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer) { + furi_assert(dtmf_dolphin_dialer); + with_view_model( + dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { + free(model->freq); + return true; + } + ); + view_free(dtmf_dolphin_dialer->view); + free(dtmf_dolphin_dialer); +} + +View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer) { + furi_assert(dtmf_dolphin_dialer); + return dtmf_dolphin_dialer->view; +} + +// void dtmf_dolphin_dialer_set_ok_callback(DTMFDolphinDialer* dtmf_dolphin_dialer, DTMFDolphinDialerOkCallback callback, void* context) { +// furi_assert(dtmf_dolphin_dialer); +// furi_assert(callback); +// with_view_model( +// dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) { +// UNUSED(model); +// dtmf_dolphin_dialer->callback = callback; +// dtmf_dolphin_dialer->context = context; +// return false; +// }); +// } diff --git a/views/dtmf_dolphin_dialer.h b/views/dtmf_dolphin_dialer.h new file mode 100644 index 00000000000..80c0aef8115 --- /dev/null +++ b/views/dtmf_dolphin_dialer.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include "../dtmf_dolphin_event.h" +#include "../dtmf_dolphin_tone.h" +#include "dtmf_dolphin_common.h" + +typedef struct DTMFDolphinDialer DTMFDolphinDialer; +typedef void (*DTMFDolphinDialerOkCallback)(InputType type, void* context); + +DTMFDolphinDialer* dtmf_dolphin_dialer_alloc(); + +void dtmf_dolphin_dialer_free(DTMFDolphinDialer* dtmf_dolphin_dialer); + +View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer); + +void dtmf_dolphin_dialer_set_ok_callback(DTMFDolphinDialer* dtmf_dolphin_dialer, DTMFDolphinDialerOkCallback callback, void* context);