This commit is contained in:
RogueMaster
2022-10-16 18:33:37 -04:00
parent e25799a9f5
commit caa73ef28a
8 changed files with 163 additions and 28 deletions
+2 -2
View File
@@ -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();
}