diff --git a/README.md b/README.md index 60f5135..b4adfdc 100644 --- a/README.md +++ b/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. diff --git a/assets/dialer.jpg b/assets/dialer.jpg index 4d86df3..ff6fad7 100644 Binary files a/assets/dialer.jpg and b/assets/dialer.jpg differ diff --git a/dtmf_dolphin_audio.c b/dtmf_dolphin_audio.c index 6d6341b..2bc1628 100644 --- a/dtmf_dolphin_audio.c +++ b/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(); diff --git a/dtmf_dolphin_audio.h b/dtmf_dolphin_audio.h index 071323a..6109eed 100644 --- a/dtmf_dolphin_audio.h +++ b/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(); diff --git a/dtmf_dolphin_data.c b/dtmf_dolphin_data.c index d0a8580..dd8ae4e 100644 --- a/dtmf_dolphin_data.c +++ b/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]; diff --git a/dtmf_dolphin_data.h b/dtmf_dolphin_data.h index 65fb9c7..fcf072c 100644 --- a/dtmf_dolphin_data.h +++ b/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(); diff --git a/scenes/dtmf_dolphin_scene_start.c b/scenes/dtmf_dolphin_scene_start.c index 8bd5344..cfd85b2 100644 --- a/scenes/dtmf_dolphin_scene_start.c +++ b/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; diff --git a/views/dtmf_dolphin_dialer.c b/views/dtmf_dolphin_dialer.c index 96d9e46..427efb6 100644 --- a/views/dtmf_dolphin_dialer.c +++ b/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(); }