mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-10 19:23:31 -07:00
dtmf upd
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
## DTMF Dolphin
|
||||
|
||||
DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and future Redbox.
|
||||
DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and 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.
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 956 KiB After Width: | Height: | Size: 1.9 MiB |
@@ -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,13 +126,19 @@ float sample_frame(DTMFDolphinOsc* osc) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
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);
|
||||
free(player->sample_buffer);
|
||||
free(player);
|
||||
current_player = NULL;
|
||||
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) {
|
||||
@@ -109,6 +148,24 @@ void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user