Quellcode durchsuchen

Now functional as dialer and bluebox.

Aria Burrell vor 3 Jahren
Ursprung
Commit
cb02b72807
7 geänderte Dateien mit 175 neuen und 99 gelöschten Zeilen
  1. 3 3
      README.md
  2. 0 1
      dtmf_dolphin.c
  3. 70 52
      dtmf_dolphin_audio.c
  4. 1 3
      dtmf_dolphin_audio.h
  5. 9 9
      dtmf_dolphin_data.c
  6. 4 0
      scenes/dtmf_dolphin_scene_start.c
  7. 88 31
      views/dtmf_dolphin_dialer.c

+ 3 - 3
README.md

@@ -2,8 +2,8 @@
 
 
 ## DTMF Dolphin
 ## DTMF Dolphin
 
 
-DTMF (Dual-Tone Multi-Frequency) dialer, and future Bluebox and Redbox for the Flipper Zero.
+DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and future Redbox.
 
 
-Documentation and code completion pending. This is a work in progress.
+Now in a release-ready state for both Dialer and Bluebox functionality. Redbox functionality awaits some changes for modulation of pitch.
 
 
-Warning: may induce feelings of nostalgia and/or actual timetravel to the '80s/'90s.
+Please note that using the current tone output method, the 2600 tone is scaled about 33 Hz higher than it should be. This is a limitation of the current sample rate.

+ 0 - 1
dtmf_dolphin.c

@@ -19,7 +19,6 @@ static void dtmf_dolphin_app_tick_event_callback(void* context) {
     furi_assert(context);
     furi_assert(context);
     DTMFDolphinApp* app = context;
     DTMFDolphinApp* app = context;
 
 
-    // dtmf_dolphin_audio_handle_tick();
     scene_manager_handle_tick_event(app->scene_manager);
     scene_manager_handle_tick_event(app->scene_manager);
 }
 }
 
 

+ 70 - 52
dtmf_dolphin_audio.c

@@ -27,52 +27,66 @@ void dtmf_dolphin_audio_clear_samples(DTMFDolphinAudio* player) {
 }
 }
 
 
 DTMFDolphinOsc* dtmf_dolphin_osc_alloc() {
 DTMFDolphinOsc* dtmf_dolphin_osc_alloc() {
-    DTMFDolphinOsc *osc = { 0 };
+    DTMFDolphinOsc *osc = malloc(sizeof(DTMFDolphinOsc));
     osc->cached_freq = 0;
     osc->cached_freq = 0;
     osc->offset = 0;
     osc->offset = 0;
     osc->period = 0;
     osc->period = 0;
+    osc->lookup_table = NULL;
     return osc;
     return osc;
 }
 }
 
 
 DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
 DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
-    DTMFDolphinAudio player;
-    player.buffer_length = SAMPLE_BUFFER_LENGTH;
-    player.half_buffer_length = SAMPLE_BUFFER_LENGTH / 2;
-    player.buffer_buffer = malloc(sizeof(uint8_t) * player.buffer_length);
-    player.sample_buffer = malloc(sizeof(uint16_t) * player.buffer_length);
-    player.osc1 = dtmf_dolphin_osc_alloc();
-    player.osc2 = dtmf_dolphin_osc_alloc();
-    player.playing = false;
-    player.volume = 2.0f;
-    player.queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent));
-    dtmf_dolphin_audio_clear_samples(&player);
-
-    return false;
+    DTMFDolphinAudio *player = malloc(sizeof(DTMFDolphinAudio));
+    player->buffer_length = SAMPLE_BUFFER_LENGTH;
+    player->half_buffer_length = SAMPLE_BUFFER_LENGTH / 2;
+    player->sample_buffer = malloc(sizeof(uint16_t) * player->buffer_length);
+    player->osc1 = dtmf_dolphin_osc_alloc();
+    player->osc2 = dtmf_dolphin_osc_alloc();
+    player->volume = 1.0f;
+    player->queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent));
+    dtmf_dolphin_audio_clear_samples(player);
+
+    return player;
 }
 }
 
 
 size_t calc_waveform_period(float freq) {
 size_t calc_waveform_period(float freq) {
     if (!freq) {
     if (!freq) {
         return 0;
         return 0;
     }
     }
-    // DMA Rate constant, thanks to Dr_Zlo
+    // DMA Rate calculation, thanks to Dr_Zlo
     float dma_rate = CPU_CLOCK_FREQ \
     float dma_rate = CPU_CLOCK_FREQ \
         / 2 \
         / 2 \
         / DTMF_DOLPHIN_HAL_DMA_PRESCALER \
         / DTMF_DOLPHIN_HAL_DMA_PRESCALER \
         / (DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1);
         / (DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1);
 
 
-    return (uint16_t) (dma_rate / freq);
+    // Using a constant scaling modifier, which likely represents
+    // the combined system overhead and isr latency.
+    return (uint16_t) dma_rate * 2 / freq * 0.801923;
 }
 }
 
 
-float sample_frame(DTMFDolphinOsc* osc, float freq) {
-    float frame = 0.0;
+void osc_generate_lookup_table(DTMFDolphinOsc* osc, float freq) {
+    if (osc->lookup_table != NULL) {
+        free(osc->lookup_table);
+    }
+    osc->offset = 0;
+    osc->cached_freq = freq;
+    osc->period = calc_waveform_period(freq);
+    if (!osc->period) {
+        osc->lookup_table = NULL;
+        return;
+    }
+    osc->lookup_table = malloc(sizeof(float) * osc->period);
 
 
-    if (freq != osc->cached_freq || !osc->period) {
-        osc->cached_freq = freq;
-        osc->period = calc_waveform_period(freq);
-        osc->offset = 0;
+    for (size_t i = 0; i < osc->period; i++) {
+        osc->lookup_table[i] = sin(i * PERIOD_2_PI / osc->period) + 1;
     }
     }
+}
+
+float sample_frame(DTMFDolphinOsc* osc) {
+    float frame = 0.0;
+
     if (osc->period) {
     if (osc->period) {
-        frame = tanhf(sin(osc->offset * PERIOD_2_PI / osc->period) + 1);
+        frame = osc->lookup_table[osc->offset];
         osc->offset = (osc->offset + 1) % osc->period;
         osc->offset = (osc->offset + 1) % osc->period;
     }
     }
 
 
@@ -83,20 +97,30 @@ void dtmf_dolphin_audio_free(DTMFDolphinAudio* player) {
     furi_message_queue_free(player->queue);
     furi_message_queue_free(player->queue);
     dtmf_dolphin_osc_free(player->osc1);
     dtmf_dolphin_osc_free(player->osc1);
     dtmf_dolphin_osc_free(player->osc2);
     dtmf_dolphin_osc_free(player->osc2);
-    free(player->buffer_buffer);
     free(player->sample_buffer);
     free(player->sample_buffer);
+    free(player);
+    current_player = NULL;
 }
 }
 
 
 void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
 void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
-    UNUSED(osc);
-    // Nothing to free now, but keeping this here in case I reimplement caching
+    if (osc->lookup_table != NULL) {
+        free(osc->lookup_table);
+    }
+    free(osc);
 }
 }
 
 
-bool generate_waveform(DTMFDolphinAudio* player, float freq1, float freq2, uint16_t buffer_index) {
+bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
     uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index];
     uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index];
 
 
     for (size_t i = 0; i < player->half_buffer_length; i++) {
     for (size_t i = 0; i < player->half_buffer_length; i++) {
-        float data = (sample_frame(player->osc1, freq1) / 2) + (sample_frame(player->osc2, freq2) / 2);
+        float data = 0;
+        if (player->osc2->period) {
+            data = \
+                (sample_frame(player->osc1) / 2) + \
+                (sample_frame(player->osc2) / 2);
+        } else {
+            data = (sample_frame(player->osc1));
+        }
         data *= player->volume;
         data *= player->volume;
         data *= UINT8_MAX / 2;  // scale -128..127
         data *= UINT8_MAX / 2;  // scale -128..127
         data += UINT8_MAX / 2;  // to unsigned
         data += UINT8_MAX / 2;  // to unsigned
@@ -109,7 +133,6 @@ bool generate_waveform(DTMFDolphinAudio* player, float freq1, float freq2, uint1
             data = 255;
             data = 255;
         }
         }
 
 
-        player->buffer_buffer[i] = data;
         sample_buffer_start[i] = data;
         sample_buffer_start[i] = data;
     }
     }
 
 
@@ -119,8 +142,11 @@ bool generate_waveform(DTMFDolphinAudio* player, float freq1, float freq2, uint1
 bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
 bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
     current_player = dtmf_dolphin_audio_alloc();
     current_player = dtmf_dolphin_audio_alloc();
 
 
-    generate_waveform(current_player, freq1, freq2, 0);
-    generate_waveform(current_player, freq1, freq2, current_player->half_buffer_length);
+    osc_generate_lookup_table(current_player->osc1, freq1);
+    osc_generate_lookup_table(current_player->osc2, freq2);
+
+    generate_waveform(current_player, 0);
+    generate_waveform(current_player, current_player->half_buffer_length);
 
 
     dtmf_dolphin_speaker_init();
     dtmf_dolphin_speaker_init();
     dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length);
     dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length);
@@ -129,13 +155,10 @@ bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
 
 
     dtmf_dolphin_dma_start();
     dtmf_dolphin_dma_start();
     dtmf_dolphin_speaker_start();
     dtmf_dolphin_speaker_start();
-
     return true;
     return true;
 }
 }
 
 
 bool dtmf_dolphin_audio_stop_tones() {
 bool dtmf_dolphin_audio_stop_tones() {
-    current_player->playing = false;
-
     dtmf_dolphin_speaker_stop();
     dtmf_dolphin_speaker_stop();
     dtmf_dolphin_dma_stop();
     dtmf_dolphin_dma_stop();
 
 
@@ -147,24 +170,19 @@ bool dtmf_dolphin_audio_stop_tones() {
 }
 }
 
 
 bool dtmf_dolphin_audio_handle_tick() {
 bool dtmf_dolphin_audio_handle_tick() {
-    DTMFDolphinCustomEvent event;
-
-    if(furi_message_queue_get(current_player->queue, &event, FuriWaitForever) == FuriStatusOk) {
-        if(event.type == DTMFDolphinEventDMAHalfTransfer) {
-            generate_waveform(
-                current_player, 
-                (double) current_player->osc1->cached_freq, 
-                (double) current_player->osc2->cached_freq, 
-                0);
-            return true;
-        } else if (event.type == DTMFDolphinEventDMAFullTransfer) {
-            generate_waveform(
-                current_player, 
-                (double) current_player->osc1->cached_freq, 
-                (double) current_player->osc2->cached_freq, 
-                current_player->half_buffer_length);
-            return true;
+    bool handled = false;
+
+    if (current_player) {
+        DTMFDolphinCustomEvent event;
+        if(furi_message_queue_get(current_player->queue, &event, 250) == FuriStatusOk) {
+            if(event.type == DTMFDolphinEventDMAHalfTransfer) {
+                generate_waveform(current_player, 0);
+                handled = true;
+            } else if (event.type == DTMFDolphinEventDMAFullTransfer) {
+                generate_waveform(current_player, current_player->half_buffer_length);
+                handled = true;
+            }
         }
         }
     }
     }
-    return false;
+    return handled;
 }
 }

+ 1 - 3
dtmf_dolphin_audio.h

@@ -10,8 +10,7 @@
 typedef struct {
 typedef struct {
     float cached_freq;
     float cached_freq;
     size_t period;
     size_t period;
-    size_t buffer_length;
-    float* sample_buffer;
+    float* lookup_table;
     uint16_t offset;
     uint16_t offset;
 } DTMFDolphinOsc;
 } DTMFDolphinOsc;
 
 
@@ -21,7 +20,6 @@ typedef struct {
     uint8_t *buffer_buffer;
     uint8_t *buffer_buffer;
     uint16_t *sample_buffer;
     uint16_t *sample_buffer;
     float volume;
     float volume;
-    bool playing;
     FuriMessageQueue *queue;
     FuriMessageQueue *queue;
     DTMFDolphinOsc *osc1;
     DTMFDolphinOsc *osc1;
     DTMFDolphinOsc *osc2;
     DTMFDolphinOsc *osc2;

+ 9 - 9
dtmf_dolphin_data.c

@@ -64,7 +64,7 @@ DTMFDolphinSceneData DTMFDolphinSceneDataBluebox = {
         {"0",     1300.0, 1500.0, {3, 1, 1}, 0, 0, 0},
         {"0",     1300.0, 1500.0, {3, 1, 1}, 0, 0, 0},
         {"KP",    1100.0, 1700.0, {0, 3, 2}, 0, 0, 0},
         {"KP",    1100.0, 1700.0, {0, 3, 2}, 0, 0, 0},
         {"ST",    1500.0, 1700.0, {1, 3, 2}, 0, 0, 0},
         {"ST",    1500.0, 1700.0, {1, 3, 2}, 0, 0, 0},
-        {"2600",  2600.0,    0.0, {3, 3, 2}, 0, 0, 0},
+        {"2600",  2600.0,    0.0, {3, 2, 3}, 0, 0, 0},
     }
     }
 };
 };
 
 
@@ -73,10 +73,10 @@ DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUS = {
     .block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US,
     .block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US,
     .tone_count = 4,
     .tone_count = 4,
     .tones = {
     .tones = {
-        {"Nickel",  1700.0, 2200.0, {0, 0, 4}, 1, 66, 0},
-        {"Dime",    1700.0, 2200.0, {1, 0, 4}, 2, 66, 66},
-        {"Quarter", 1700.0, 2200.0, {2, 0, 4}, 5, 33, 33},
-        {"Dollar",  1700.0, 2200.0, {3, 0, 4}, 1, 650, 0},
+        {"Nickel",  1700.0, 2200.0, {0, 0, 5}, 1, 66, 0},
+        {"Dime",    1700.0, 2200.0, {1, 0, 5}, 2, 66, 66},
+        {"Quarter", 1700.0, 2200.0, {2, 0, 5}, 5, 33, 33},
+        {"Dollar",  1700.0, 2200.0, {3, 0, 5}, 1, 650, 0},
     }
     }
 };
 };
 
 
@@ -95,9 +95,9 @@ DTMFDolphinSceneData DTMFDolphinSceneDataMisc = {
     .block = DTMF_DOLPHIN_TONE_BLOCK_MISC,
     .block = DTMF_DOLPHIN_TONE_BLOCK_MISC,
     .tone_count = 3,
     .tone_count = 3,
     .tones = {
     .tones = {
-        {"CCITT 11",   700.0, 1700.0, {0, 0, 4}, 0, 0, 0},
-        {"CCITT 12",   900.0, 1700.0, {1, 0, 4}, 0, 0, 0},
-        {"CCITT KP2", 1300.0, 1700.0, {2, 0, 4}, 0, 0, 0},
+        {"CCITT 11",   700.0, 1700.0, {0, 0, 5}, 0, 0, 0},
+        {"CCITT 12",   900.0, 1700.0, {1, 0, 5}, 0, 0, 0},
+        {"CCITT KP2", 1300.0, 1700.0, {2, 0, 5}, 0, 0, 0},
     }
     }
 };
 };
 
 
@@ -179,7 +179,7 @@ void dtmf_dolphin_tone_get_max_pos(uint8_t* max_rows, uint8_t* max_cols, uint8_t
         }
         }
         tmp_rowspan[tones.pos.row] += tones.pos.span;
         tmp_rowspan[tones.pos.row] += tones.pos.span;
         if (tmp_rowspan[tones.pos.row] > max_span[0])
         if (tmp_rowspan[tones.pos.row] > max_span[0])
-            max_span[0] = max_span[tones.pos.row];
+            max_span[0] = tmp_rowspan[tones.pos.row];
     }
     }
     max_rows[0]++;
     max_rows[0]++;
     max_cols[0]++;
     max_cols[0]++;

+ 4 - 0
scenes/dtmf_dolphin_scene_start.c

@@ -27,6 +27,7 @@ void dtmf_dolphin_scene_start_on_enter(void* context) {
 
 
     variable_item_list_add(var_item_list, "Dialer", 0, NULL, NULL);
     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_add(var_item_list, "Bluebox", 0, NULL, NULL);
+    variable_item_list_add(var_item_list, "Misc", 0, NULL, NULL);
 
 
     variable_item_list_set_selected_item(
     variable_item_list_set_selected_item(
         var_item_list,
         var_item_list,
@@ -49,6 +50,9 @@ bool dtmf_dolphin_scene_start_on_event(void* context, SceneManagerEvent event) {
         } else if (event.event == DTMFDolphinEventStartBluebox) {
         } else if (event.event == DTMFDolphinEventStartBluebox) {
             scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateBluebox);
             scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateBluebox);
             scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
             scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
+        } else if (event.event == DTMFDolphinEventStartMisc) {
+            scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateMisc);
+            scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
         }
         }
         consumed = true;
         consumed = true;
     }
     }

+ 88 - 31
views/dtmf_dolphin_dialer.c

@@ -14,7 +14,7 @@ typedef struct {
     uint8_t col;
     uint8_t col;
     float freq1;
     float freq1;
     float freq2;
     float freq2;
-    DTMFDolphinAudio player;
+    bool playing;
 } DTMFDolphinDialerModel;
 } DTMFDolphinDialerModel;
 
 
 static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer);
 static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer);
@@ -36,6 +36,10 @@ void draw_button(Canvas* canvas, uint8_t row, uint8_t col, bool invert) {
 
 
     uint8_t span = dtmf_dolphin_get_tone_span(row, col);
     uint8_t span = dtmf_dolphin_get_tone_span(row, col);
 
 
+    if (span == 0) {
+        return;
+    }
+
     canvas_set_color(canvas, ColorBlack);
     canvas_set_color(canvas, ColorBlack);
     
     
     if (invert)
     if (invert)
@@ -91,10 +95,25 @@ void update_frequencies(DTMFDolphinDialerModel *model) {
 
 
 static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
 static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
     DTMFDolphinDialerModel* model = _model;
     DTMFDolphinDialerModel* model = _model;
+    if (model->playing) {
+        // Leverage the prioritized draw callback to handle
+        // the DMA so that it doesn't skip.
+        dtmf_dolphin_audio_handle_tick();
+        // Don't do any drawing if audio is playing.
+        canvas_set_font(canvas, FontPrimary);
+        elements_multiline_text_aligned(
+            canvas, 
+            canvas_width(canvas) / 2,
+            canvas_height(canvas) / 2,
+            AlignCenter,
+            AlignCenter,
+            "Playing Tones");
+        return;
+    }
     update_frequencies(model);
     update_frequencies(model);
-    uint8_t max_rows;
-    uint8_t max_cols;
-    uint8_t max_span;
+    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_get_max_pos(&max_rows, &max_cols, &max_span);
 
 
     canvas_set_font(canvas, FontPrimary);
     canvas_set_font(canvas, FontPrimary);
@@ -111,11 +130,18 @@ static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
     string_t output;
     string_t output;
     string_init(output);
     string_init(output);
 
 
-    string_cat_printf(
-        output,
-        "F1: %u Hz\nF2: %u Hz",
-        model->freq1 ? (unsigned int) model->freq1 : 0,
-        model->freq2 ? (unsigned int) model->freq2 : 0);
+    if (model->freq1 && model->freq2) {
+        string_cat_printf(
+            output,
+            "Dual Tone\nF1: %u Hz\nF2: %u Hz\n",
+            (unsigned int) model->freq1,
+            (unsigned int) model->freq2);
+    } else if (model->freq1) {
+        string_cat_printf(
+            output,
+            "Single Tone\nF: %u Hz\n",
+            (unsigned int) model->freq1);
+    }
 
 
     canvas_set_font(canvas, FontSecondary);
     canvas_set_font(canvas, FontSecondary);
     canvas_set_color(canvas, ColorBlack);
     canvas_set_color(canvas, ColorBlack);
@@ -150,8 +176,14 @@ static bool dtmf_dolphin_dialer_input_callback(InputEvent* event, void* context)
 static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer) {
 static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer) {
     with_view_model(
     with_view_model(
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
-            if(model->row > 0) {
-                model->row--;
+            uint8_t span = 0;
+            uint8_t cursor = model->row;
+            while (span == 0 && cursor > 0) {
+                cursor--;
+                span = dtmf_dolphin_get_tone_span(cursor, model->col);
+            }
+            if (span != 0) {
+                model->row = cursor;
             }
             }
             return true;
             return true;
         });
         });
@@ -166,8 +198,14 @@ static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dia
 
 
     with_view_model(
     with_view_model(
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
-            if(model->row < max_rows - 1) {
-                model->row++;
+            uint8_t span = 0;
+            uint8_t cursor = model->row;
+            while(span == 0 && cursor < max_rows - 1) {
+                cursor++;
+                span = dtmf_dolphin_get_tone_span(cursor, model->col);
+            }
+            if (span != 0) {
+                model->row = cursor;
             }
             }
             return true;
             return true;
         });
         });
@@ -177,8 +215,14 @@ static bool dtmf_dolphin_dialer_process_down(DTMFDolphinDialer* dtmf_dolphin_dia
 static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer) {
 static bool dtmf_dolphin_dialer_process_left(DTMFDolphinDialer* dtmf_dolphin_dialer) {
     with_view_model(
     with_view_model(
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
-            if(model->col > 0) {
-                model->col--;
+            uint8_t span = 0;
+            uint8_t cursor = model->col;
+            while (span == 0 && cursor > 0) {
+                cursor--;
+                span = dtmf_dolphin_get_tone_span(model->row, cursor);
+            }
+            if (span != 0) {
+                model->col = cursor;
             }
             }
             return true;
             return true;
         });
         });
@@ -193,8 +237,14 @@ static bool dtmf_dolphin_dialer_process_right(DTMFDolphinDialer* dtmf_dolphin_di
 
 
     with_view_model(
     with_view_model(
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
-            if(model->col < max_cols - 1) {
-                model->col++;
+            uint8_t span = 0;
+            uint8_t cursor = model->col;
+            while(span == 0 && cursor < max_cols - 1) {
+                cursor++;
+                span = dtmf_dolphin_get_tone_span(model->row, cursor);
+            }
+            if (span != 0) {
+                model->col = cursor;
             }
             }
             return true;
             return true;
         });
         });
@@ -207,9 +257,9 @@ static bool dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_diale
     with_view_model(
     with_view_model(
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
         dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
             if (event->type == InputTypePress) {
             if (event->type == InputTypePress) {
-                dtmf_dolphin_audio_play_tones(model->freq1, model->freq2);
+                model->playing = dtmf_dolphin_audio_play_tones(model->freq1, model->freq2);
             } else if (event->type == InputTypeRelease) {
             } else if (event->type == InputTypeRelease) {
-                dtmf_dolphin_audio_stop_tones();
+                model->playing = !dtmf_dolphin_audio_stop_tones();
             }
             }
 
 
             return true;
             return true;
@@ -218,6 +268,23 @@ static bool dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_diale
     return consumed;
     return consumed;
 }
 }
 
 
+static void dtmf_dolphin_dialer_enter_callback(void* context) {
+    furi_assert(context);
+    DTMFDolphinDialer* dtmf_dolphin_dialer = context;
+
+    with_view_model(
+        dtmf_dolphin_dialer->view, (DTMFDolphinDialerModel * model) {
+            model->col = 0;
+            model->row = 0;
+            model->section = 0;
+            model->freq1 = 0.0;
+            model->freq2 = 0.0;
+            model->playing = false;
+            return true;
+        }
+    );
+}
+
 DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() {
 DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() {
     DTMFDolphinDialer* dtmf_dolphin_dialer = malloc(sizeof(DTMFDolphinDialer));
     DTMFDolphinDialer* dtmf_dolphin_dialer = malloc(sizeof(DTMFDolphinDialer));
 
 
@@ -231,6 +298,7 @@ DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() {
             model->section = 0;
             model->section = 0;
             model->freq1 = 0.0;
             model->freq1 = 0.0;
             model->freq2 = 0.0;
             model->freq2 = 0.0;
+            model->playing = false;
             return true;
             return true;
         }
         }
     );
     );
@@ -238,7 +306,7 @@ DTMFDolphinDialer* dtmf_dolphin_dialer_alloc() {
     view_set_context(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer);
     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_draw_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_draw_callback);
     view_set_input_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_input_callback);
     view_set_input_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_input_callback);
-
+    view_set_enter_callback(dtmf_dolphin_dialer->view, dtmf_dolphin_dialer_enter_callback);
     return dtmf_dolphin_dialer;
     return dtmf_dolphin_dialer;
 }
 }
 
 
@@ -253,14 +321,3 @@ View* dtmf_dolphin_dialer_get_view(DTMFDolphinDialer* dtmf_dolphin_dialer) {
     return dtmf_dolphin_dialer->view;
     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;
-//         });
-// }