Просмотр исходного кода

Redbox functionality complete!

Aria Burrell 3 лет назад
Родитель
Сommit
a443d346b8

+ 1 - 1
README.md

@@ -4,7 +4,7 @@
 
 DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and future Redbox.
 
-Now in a release-ready state for both Dialer and Bluebox functionality. Redbox functionality awaits some changes for modulation.
+Now in a release-ready state for both Dialer, Bluebox, and Redbox (US/UK) functionality!
 
 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.
 

BIN
assets/dialer.jpg


+ 79 - 8
dtmf_dolphin_audio.c

@@ -35,6 +35,15 @@ DTMFDolphinOsc* dtmf_dolphin_osc_alloc() {
     return osc;
 }
 
+DTMFDolphinPulseFilter* dtmf_dolphin_pulse_filter_alloc() {
+    DTMFDolphinPulseFilter *pf = malloc(sizeof(DTMFDolphinPulseFilter));
+    pf->duration = 0;
+    pf->period = 0;
+    pf->offset = 0;
+    pf->lookup_table = NULL;
+    return pf;
+}
+
 DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
     DTMFDolphinAudio *player = malloc(sizeof(DTMFDolphinAudio));
     player->buffer_length = SAMPLE_BUFFER_LENGTH;
@@ -44,6 +53,8 @@ DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
     player->osc2 = dtmf_dolphin_osc_alloc();
     player->volume = 1.0f;
     player->queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent));
+    player->filter = dtmf_dolphin_pulse_filter_alloc();
+    player->playing = false;
     dtmf_dolphin_audio_clear_samples(player);
 
     return player;
@@ -82,6 +93,28 @@ void osc_generate_lookup_table(DTMFDolphinOsc* osc, float freq) {
     }
 }
 
+void filter_generate_lookup_table(DTMFDolphinPulseFilter* pf, uint16_t pulses, uint16_t pulse_ms, uint16_t gap_ms) {
+    if (pf->lookup_table != NULL) {
+        free(pf->lookup_table);
+    }
+    pf->offset = 0;
+
+    uint16_t gap_period = calc_waveform_period(1000 / (float) gap_ms);
+    uint16_t pulse_period = calc_waveform_period(1000 / (float) pulse_ms);
+    pf->period = pulse_period + gap_period;
+
+    if (!pf->period) {
+        pf->lookup_table = NULL;
+        return;
+    }
+    pf->duration = pf->period * pulses;
+    pf->lookup_table = malloc(sizeof(bool) * pf->duration);
+
+    for (size_t i = 0; i < pf->duration; i++) {
+        pf->lookup_table[i] = i % pf->period < pulse_period;
+    }
+}
+
 float sample_frame(DTMFDolphinOsc* osc) {
     float frame = 0.0;
 
@@ -93,21 +126,45 @@ float sample_frame(DTMFDolphinOsc* osc) {
     return frame;
 }
 
+bool sample_filter(DTMFDolphinPulseFilter* pf) {
+    bool frame = true;
+
+    if (pf->duration) {
+        if (pf->offset < pf->duration) {
+            frame = pf->lookup_table[pf->offset];
+            pf->offset = pf->offset + 1;
+        } else {
+            frame = false;
+        }
+    }
+
+    return frame;
+}
+
+void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
+    if (osc->lookup_table != NULL) {
+        free(osc->lookup_table);
+    }
+    free(osc);
+}
+
+void dtmf_dolphin_filter_free(DTMFDolphinPulseFilter* pf) {
+    if (pf->lookup_table != NULL) {
+        free(pf->lookup_table);
+    }
+    free(pf);
+}
+
 void dtmf_dolphin_audio_free(DTMFDolphinAudio* player) {
     furi_message_queue_free(player->queue);
     dtmf_dolphin_osc_free(player->osc1);
     dtmf_dolphin_osc_free(player->osc2);
+    dtmf_dolphin_filter_free(player->filter);
     free(player->sample_buffer);
     free(player);
     current_player = NULL;
 }
 
-void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
-    if (osc->lookup_table != NULL) {
-        free(osc->lookup_table);
-    }
-    free(osc);
-}
 
 bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
     uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index];
@@ -121,7 +178,7 @@ bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
         } else {
             data = (sample_frame(player->osc1));
         }
-        data *= player->volume;
+        data *= sample_filter(player->filter) ? player->volume : 0.0;
         data *= UINT8_MAX / 2;  // scale -128..127
         data += UINT8_MAX / 2;  // to unsigned
 
@@ -139,11 +196,16 @@ bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
     return true;
 }
 
-bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
+bool dtmf_dolphin_audio_play_tones(float freq1, float freq2, uint16_t pulses, uint16_t pulse_ms, uint16_t gap_ms) {
+    if (current_player != NULL && current_player->playing) {
+        // Cannot start playing while still playing something else
+        return false;
+    }
     current_player = dtmf_dolphin_audio_alloc();
 
     osc_generate_lookup_table(current_player->osc1, freq1);
     osc_generate_lookup_table(current_player->osc2, freq2);
+    filter_generate_lookup_table(current_player->filter, pulses, pulse_ms, gap_ms);
 
     generate_waveform(current_player, 0);
     generate_waveform(current_player, current_player->half_buffer_length);
@@ -155,10 +217,19 @@ bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
 
     dtmf_dolphin_dma_start();
     dtmf_dolphin_speaker_start();
+    current_player->playing = true;
     return true;
 }
 
 bool dtmf_dolphin_audio_stop_tones() {
+    if (current_player != NULL && !current_player->playing) {
+        // Can't stop a player that isn't playing.
+        return false;
+    }
+    while(current_player->filter->offset > 0 && current_player->filter->offset < current_player->filter->duration) {
+        // run remaining ticks if needed to complete filter sequence
+        dtmf_dolphin_audio_handle_tick();
+    }
     dtmf_dolphin_speaker_stop();
     dtmf_dolphin_dma_stop();
 

+ 10 - 1
dtmf_dolphin_audio.h

@@ -14,6 +14,13 @@ typedef struct {
     uint16_t offset;
 } DTMFDolphinOsc;
 
+typedef struct {
+    float duration;
+    size_t period;
+    bool* lookup_table;
+    uint16_t offset;
+} DTMFDolphinPulseFilter;
+
 typedef struct {
     size_t buffer_length;
     size_t half_buffer_length;
@@ -23,6 +30,8 @@ typedef struct {
     FuriMessageQueue *queue;
     DTMFDolphinOsc *osc1;
     DTMFDolphinOsc *osc2;
+    DTMFDolphinPulseFilter *filter;
+    bool playing;
 } DTMFDolphinAudio;
 
 DTMFDolphinOsc* dtmf_dolphin_osc_alloc();
@@ -33,7 +42,7 @@ void dtmf_dolphin_audio_free(DTMFDolphinAudio* player);
 
 void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc);
 
-bool dtmf_dolphin_audio_play_tones(float freq1, float freq2);
+bool dtmf_dolphin_audio_play_tones(float freq1, float freq2, uint16_t pulses, uint16_t pulse_ms, uint16_t gap_ms);
 
 bool dtmf_dolphin_audio_stop_tones();
 

+ 15 - 2
dtmf_dolphin_data.c

@@ -85,8 +85,8 @@ DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUK = {
     .block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_UK,
     .tone_count = 2,
     .tones = {
-        {"10p", 1000.0, 0.0, {0, 0, 3}, 1, 200, 0},
-        {"50p", 1000.0, 0.0, {1, 0, 3}, 1, 350, 0},
+        {"10p", 1000.0, 0.0, {0, 0, 5}, 1, 200, 0},
+        {"50p", 1000.0, 0.0, {1, 0, 5}, 1, 350, 0},
     }
 };
 
@@ -147,6 +147,19 @@ bool dtmf_dolphin_data_get_tone_frequencies(float *freq1, float *freq2, uint8_t
     return false;
 }
 
+bool dtmf_dolphin_data_get_filter_data(uint16_t *pulses, uint16_t *pulse_ms, uint16_t *gap_ms, uint8_t row, uint8_t col) {
+    for (size_t i = 0; i < current_scene_data->tone_count; i++) {
+        DTMFDolphinTones tones = current_scene_data->tones[i];
+        if (tones.pos.row == row && tones.pos.col == col) {
+            pulses[0] = tones.pulses;
+            pulse_ms[0] = tones.pulse_ms;
+            gap_ms[0] = tones.gap_duration;
+            return true;
+        }
+    }
+    return false;
+}
+
 const char* dtmf_dolphin_data_get_tone_name(uint8_t row, uint8_t col) {
     for (size_t i = 0; i < current_scene_data->tone_count; i++) {
         DTMFDolphinTones tones = current_scene_data->tones[i];

+ 2 - 0
dtmf_dolphin_data.h

@@ -19,6 +19,8 @@ DTMFDolphinToneSection dtmf_dolphin_data_get_current_section();
 
 bool dtmf_dolphin_data_get_tone_frequencies(float *freq1, float *freq2, uint8_t row, uint8_t col);
 
+bool dtmf_dolphin_data_get_filter_data(uint16_t *pulses, uint16_t *pulse_ms, uint16_t *gap_ms, uint8_t row, uint8_t col);
+
 const char* dtmf_dolphin_data_get_tone_name(uint8_t row, uint8_t col);
 
 const char* dtmf_dolphin_data_get_current_section_name();

+ 36 - 13
scenes/dtmf_dolphin_scene_start.c

@@ -12,12 +12,18 @@ static void dtmf_dolphin_scene_start_main_menu_enter_callback(void* context, uin
         cust_event = DTMFDolphinEventStartBluebox;
         break;
     case 2:
+        cust_event = DTMFDolphinEventStartRedboxUS;
+        break;
+    case 3:
+        cust_event = DTMFDolphinEventStartRedboxUK;
+        break;
+    case 4:
         cust_event = DTMFDolphinEventStartMisc;
         break;
     default:
         return;
     }
-    
+
     view_dispatcher_send_custom_event(
         app->view_dispatcher,
         cust_event
@@ -34,9 +40,11 @@ void dtmf_dolphin_scene_start_on_enter(void* context) {
         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_add(var_item_list, "Misc", 0, NULL, NULL);
+    variable_item_list_add(var_item_list, "Dialer", 0, NULL, context);
+    variable_item_list_add(var_item_list, "Bluebox", 0, NULL, context);
+    variable_item_list_add(var_item_list, "Redbox (US)", 0, NULL, context);
+    variable_item_list_add(var_item_list, "Redbox (UK)", 0, NULL, context);
+    variable_item_list_add(var_item_list, "Misc", 0, NULL, context);
 
     variable_item_list_set_selected_item(
         var_item_list,
@@ -53,16 +61,31 @@ bool dtmf_dolphin_scene_start_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if (event.event == DTMFDolphinEventStartDialer) {
-            scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateDialer);
-            scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
-        } else if (event.event == DTMFDolphinEventStartBluebox) {
-            scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateBluebox);
-            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);
+        uint8_t sc_state;
+
+        switch (event.event)
+        {
+        case DTMFDolphinEventStartDialer:
+            sc_state = DTMFDolphinSceneStateDialer;
+            break;
+        case DTMFDolphinEventStartBluebox:
+            sc_state = DTMFDolphinSceneStateBluebox;
+            break;
+        case DTMFDolphinEventStartRedboxUS:
+            sc_state = DTMFDolphinSceneStateRedboxUS;
+            break;
+        case DTMFDolphinEventStartRedboxUK:
+            sc_state = DTMFDolphinSceneStateRedboxUK;
+            break;
+        case DTMFDolphinEventStartMisc:
+            sc_state = DTMFDolphinSceneStateMisc;
+            break;
+        default:
+            return consumed;
         }
+        scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, sc_state);
+        scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
+
         consumed = true;
     }
     return consumed;

+ 18 - 1
views/dtmf_dolphin_dialer.c

@@ -15,6 +15,9 @@ typedef struct {
     float freq1;
     float freq2;
     bool playing;
+    uint16_t pulses;
+    uint16_t pulse_ms;
+    uint16_t gap_ms;
 } DTMFDolphinDialerModel;
 
 static bool dtmf_dolphin_dialer_process_up(DTMFDolphinDialer* dtmf_dolphin_dialer);
@@ -91,6 +94,7 @@ void draw_dialer(Canvas* canvas, void* _model) {
 
 void update_frequencies(DTMFDolphinDialerModel *model) {
     dtmf_dolphin_data_get_tone_frequencies(&model->freq1, &model->freq2, model->row, model->col);
+    dtmf_dolphin_data_get_filter_data(&model->pulses, &model->pulse_ms, &model->gap_ms, model->row, model->col);
 }
 
 static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
@@ -144,6 +148,19 @@ static void dtmf_dolphin_dialer_draw_callback(Canvas* canvas, void* _model) {
 
     canvas_set_font(canvas, FontSecondary);
     canvas_set_color(canvas, ColorBlack);
+    if (model->pulse_ms) {
+        furi_string_cat_printf(
+            output,
+            "P: %u * %u ms\n",
+            model->pulses,
+            model->pulse_ms);
+    }
+    if (model->gap_ms) {
+        furi_string_cat_printf(
+            output,
+            "Gaps: %u ms\n",
+            model->gap_ms);
+    }
     elements_multiline_text(canvas, (max_span * DTMF_DOLPHIN_BUTTON_WIDTH) + 4, 21, furi_string_get_cstr(output));
 
     furi_string_free(output);
@@ -266,7 +283,7 @@ static bool dtmf_dolphin_dialer_process_ok(DTMFDolphinDialer* dtmf_dolphin_diale
         DTMFDolphinDialerModel * model,
         {
             if (event->type == InputTypePress) {
-                model->playing = dtmf_dolphin_audio_play_tones(model->freq1, model->freq2);
+                model->playing = dtmf_dolphin_audio_play_tones(model->freq1, model->freq2, model->pulses, model->pulse_ms, model->gap_ms);
             } else if (event->type == InputTypeRelease) {
                 model->playing = !dtmf_dolphin_audio_stop_tones();
             }