Skip to content

Commit cb02b72

Browse files
committed
Now functional as dialer and bluebox.
1 parent 1f0730d commit cb02b72

File tree

7 files changed

+175
-99
lines changed

7 files changed

+175
-99
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
## DTMF Dolphin
44

5-
DTMF (Dual-Tone Multi-Frequency) dialer, and future Bluebox and Redbox for the Flipper Zero.
5+
DTMF (Dual-Tone Multi-Frequency) dialer, Bluebox, and future Redbox.
66

7-
Documentation and code completion pending. This is a work in progress.
7+
Now in a release-ready state for both Dialer and Bluebox functionality. Redbox functionality awaits some changes for modulation of pitch.
88

9-
Warning: may induce feelings of nostalgia and/or actual timetravel to the '80s/'90s.
9+
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.

dtmf_dolphin.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ static void dtmf_dolphin_app_tick_event_callback(void* context) {
1919
furi_assert(context);
2020
DTMFDolphinApp* app = context;
2121

22-
// dtmf_dolphin_audio_handle_tick();
2322
scene_manager_handle_tick_event(app->scene_manager);
2423
}
2524

dtmf_dolphin_audio.c

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -27,52 +27,66 @@ void dtmf_dolphin_audio_clear_samples(DTMFDolphinAudio* player) {
2727
}
2828

2929
DTMFDolphinOsc* dtmf_dolphin_osc_alloc() {
30-
DTMFDolphinOsc *osc = { 0 };
30+
DTMFDolphinOsc *osc = malloc(sizeof(DTMFDolphinOsc));
3131
osc->cached_freq = 0;
3232
osc->offset = 0;
3333
osc->period = 0;
34+
osc->lookup_table = NULL;
3435
return osc;
3536
}
3637

3738
DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
38-
DTMFDolphinAudio player;
39-
player.buffer_length = SAMPLE_BUFFER_LENGTH;
40-
player.half_buffer_length = SAMPLE_BUFFER_LENGTH / 2;
41-
player.buffer_buffer = malloc(sizeof(uint8_t) * player.buffer_length);
42-
player.sample_buffer = malloc(sizeof(uint16_t) * player.buffer_length);
43-
player.osc1 = dtmf_dolphin_osc_alloc();
44-
player.osc2 = dtmf_dolphin_osc_alloc();
45-
player.playing = false;
46-
player.volume = 2.0f;
47-
player.queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent));
48-
dtmf_dolphin_audio_clear_samples(&player);
49-
50-
return false;
39+
DTMFDolphinAudio *player = malloc(sizeof(DTMFDolphinAudio));
40+
player->buffer_length = SAMPLE_BUFFER_LENGTH;
41+
player->half_buffer_length = SAMPLE_BUFFER_LENGTH / 2;
42+
player->sample_buffer = malloc(sizeof(uint16_t) * player->buffer_length);
43+
player->osc1 = dtmf_dolphin_osc_alloc();
44+
player->osc2 = dtmf_dolphin_osc_alloc();
45+
player->volume = 1.0f;
46+
player->queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinCustomEvent));
47+
dtmf_dolphin_audio_clear_samples(player);
48+
49+
return player;
5150
}
5251

5352
size_t calc_waveform_period(float freq) {
5453
if (!freq) {
5554
return 0;
5655
}
57-
// DMA Rate constant, thanks to Dr_Zlo
56+
// DMA Rate calculation, thanks to Dr_Zlo
5857
float dma_rate = CPU_CLOCK_FREQ \
5958
/ 2 \
6059
/ DTMF_DOLPHIN_HAL_DMA_PRESCALER \
6160
/ (DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1);
6261

63-
return (uint16_t) (dma_rate / freq);
62+
// Using a constant scaling modifier, which likely represents
63+
// the combined system overhead and isr latency.
64+
return (uint16_t) dma_rate * 2 / freq * 0.801923;
6465
}
6566

66-
float sample_frame(DTMFDolphinOsc* osc, float freq) {
67-
float frame = 0.0;
67+
void osc_generate_lookup_table(DTMFDolphinOsc* osc, float freq) {
68+
if (osc->lookup_table != NULL) {
69+
free(osc->lookup_table);
70+
}
71+
osc->offset = 0;
72+
osc->cached_freq = freq;
73+
osc->period = calc_waveform_period(freq);
74+
if (!osc->period) {
75+
osc->lookup_table = NULL;
76+
return;
77+
}
78+
osc->lookup_table = malloc(sizeof(float) * osc->period);
6879

69-
if (freq != osc->cached_freq || !osc->period) {
70-
osc->cached_freq = freq;
71-
osc->period = calc_waveform_period(freq);
72-
osc->offset = 0;
80+
for (size_t i = 0; i < osc->period; i++) {
81+
osc->lookup_table[i] = sin(i * PERIOD_2_PI / osc->period) + 1;
7382
}
83+
}
84+
85+
float sample_frame(DTMFDolphinOsc* osc) {
86+
float frame = 0.0;
87+
7488
if (osc->period) {
75-
frame = tanhf(sin(osc->offset * PERIOD_2_PI / osc->period) + 1);
89+
frame = osc->lookup_table[osc->offset];
7690
osc->offset = (osc->offset + 1) % osc->period;
7791
}
7892

@@ -83,20 +97,30 @@ void dtmf_dolphin_audio_free(DTMFDolphinAudio* player) {
8397
furi_message_queue_free(player->queue);
8498
dtmf_dolphin_osc_free(player->osc1);
8599
dtmf_dolphin_osc_free(player->osc2);
86-
free(player->buffer_buffer);
87100
free(player->sample_buffer);
101+
free(player);
102+
current_player = NULL;
88103
}
89104

90105
void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
91-
UNUSED(osc);
92-
// Nothing to free now, but keeping this here in case I reimplement caching
106+
if (osc->lookup_table != NULL) {
107+
free(osc->lookup_table);
108+
}
109+
free(osc);
93110
}
94111

95-
bool generate_waveform(DTMFDolphinAudio* player, float freq1, float freq2, uint16_t buffer_index) {
112+
bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
96113
uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index];
97114

98115
for (size_t i = 0; i < player->half_buffer_length; i++) {
99-
float data = (sample_frame(player->osc1, freq1) / 2) + (sample_frame(player->osc2, freq2) / 2);
116+
float data = 0;
117+
if (player->osc2->period) {
118+
data = \
119+
(sample_frame(player->osc1) / 2) + \
120+
(sample_frame(player->osc2) / 2);
121+
} else {
122+
data = (sample_frame(player->osc1));
123+
}
100124
data *= player->volume;
101125
data *= UINT8_MAX / 2; // scale -128..127
102126
data += UINT8_MAX / 2; // to unsigned
@@ -109,7 +133,6 @@ bool generate_waveform(DTMFDolphinAudio* player, float freq1, float freq2, uint1
109133
data = 255;
110134
}
111135

112-
player->buffer_buffer[i] = data;
113136
sample_buffer_start[i] = data;
114137
}
115138

@@ -119,8 +142,11 @@ bool generate_waveform(DTMFDolphinAudio* player, float freq1, float freq2, uint1
119142
bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
120143
current_player = dtmf_dolphin_audio_alloc();
121144

122-
generate_waveform(current_player, freq1, freq2, 0);
123-
generate_waveform(current_player, freq1, freq2, current_player->half_buffer_length);
145+
osc_generate_lookup_table(current_player->osc1, freq1);
146+
osc_generate_lookup_table(current_player->osc2, freq2);
147+
148+
generate_waveform(current_player, 0);
149+
generate_waveform(current_player, current_player->half_buffer_length);
124150

125151
dtmf_dolphin_speaker_init();
126152
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) {
129155

130156
dtmf_dolphin_dma_start();
131157
dtmf_dolphin_speaker_start();
132-
133158
return true;
134159
}
135160

136161
bool dtmf_dolphin_audio_stop_tones() {
137-
current_player->playing = false;
138-
139162
dtmf_dolphin_speaker_stop();
140163
dtmf_dolphin_dma_stop();
141164

@@ -147,24 +170,19 @@ bool dtmf_dolphin_audio_stop_tones() {
147170
}
148171

149172
bool dtmf_dolphin_audio_handle_tick() {
150-
DTMFDolphinCustomEvent event;
151-
152-
if(furi_message_queue_get(current_player->queue, &event, FuriWaitForever) == FuriStatusOk) {
153-
if(event.type == DTMFDolphinEventDMAHalfTransfer) {
154-
generate_waveform(
155-
current_player,
156-
(double) current_player->osc1->cached_freq,
157-
(double) current_player->osc2->cached_freq,
158-
0);
159-
return true;
160-
} else if (event.type == DTMFDolphinEventDMAFullTransfer) {
161-
generate_waveform(
162-
current_player,
163-
(double) current_player->osc1->cached_freq,
164-
(double) current_player->osc2->cached_freq,
165-
current_player->half_buffer_length);
166-
return true;
173+
bool handled = false;
174+
175+
if (current_player) {
176+
DTMFDolphinCustomEvent event;
177+
if(furi_message_queue_get(current_player->queue, &event, 250) == FuriStatusOk) {
178+
if(event.type == DTMFDolphinEventDMAHalfTransfer) {
179+
generate_waveform(current_player, 0);
180+
handled = true;
181+
} else if (event.type == DTMFDolphinEventDMAFullTransfer) {
182+
generate_waveform(current_player, current_player->half_buffer_length);
183+
handled = true;
184+
}
167185
}
168186
}
169-
return false;
187+
return handled;
170188
}

dtmf_dolphin_audio.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
typedef struct {
1111
float cached_freq;
1212
size_t period;
13-
size_t buffer_length;
14-
float* sample_buffer;
13+
float* lookup_table;
1514
uint16_t offset;
1615
} DTMFDolphinOsc;
1716

@@ -21,7 +20,6 @@ typedef struct {
2120
uint8_t *buffer_buffer;
2221
uint16_t *sample_buffer;
2322
float volume;
24-
bool playing;
2523
FuriMessageQueue *queue;
2624
DTMFDolphinOsc *osc1;
2725
DTMFDolphinOsc *osc2;

dtmf_dolphin_data.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ DTMFDolphinSceneData DTMFDolphinSceneDataBluebox = {
6464
{"0", 1300.0, 1500.0, {3, 1, 1}, 0, 0, 0},
6565
{"KP", 1100.0, 1700.0, {0, 3, 2}, 0, 0, 0},
6666
{"ST", 1500.0, 1700.0, {1, 3, 2}, 0, 0, 0},
67-
{"2600", 2600.0, 0.0, {3, 3, 2}, 0, 0, 0},
67+
{"2600", 2600.0, 0.0, {3, 2, 3}, 0, 0, 0},
6868
}
6969
};
7070

@@ -73,10 +73,10 @@ DTMFDolphinSceneData DTMFDolphinSceneDataRedboxUS = {
7373
.block = DTMF_DOLPHIN_TONE_BLOCK_REDBOX_US,
7474
.tone_count = 4,
7575
.tones = {
76-
{"Nickel", 1700.0, 2200.0, {0, 0, 4}, 1, 66, 0},
77-
{"Dime", 1700.0, 2200.0, {1, 0, 4}, 2, 66, 66},
78-
{"Quarter", 1700.0, 2200.0, {2, 0, 4}, 5, 33, 33},
79-
{"Dollar", 1700.0, 2200.0, {3, 0, 4}, 1, 650, 0},
76+
{"Nickel", 1700.0, 2200.0, {0, 0, 5}, 1, 66, 0},
77+
{"Dime", 1700.0, 2200.0, {1, 0, 5}, 2, 66, 66},
78+
{"Quarter", 1700.0, 2200.0, {2, 0, 5}, 5, 33, 33},
79+
{"Dollar", 1700.0, 2200.0, {3, 0, 5}, 1, 650, 0},
8080
}
8181
};
8282

@@ -95,9 +95,9 @@ DTMFDolphinSceneData DTMFDolphinSceneDataMisc = {
9595
.block = DTMF_DOLPHIN_TONE_BLOCK_MISC,
9696
.tone_count = 3,
9797
.tones = {
98-
{"CCITT 11", 700.0, 1700.0, {0, 0, 4}, 0, 0, 0},
99-
{"CCITT 12", 900.0, 1700.0, {1, 0, 4}, 0, 0, 0},
100-
{"CCITT KP2", 1300.0, 1700.0, {2, 0, 4}, 0, 0, 0},
98+
{"CCITT 11", 700.0, 1700.0, {0, 0, 5}, 0, 0, 0},
99+
{"CCITT 12", 900.0, 1700.0, {1, 0, 5}, 0, 0, 0},
100+
{"CCITT KP2", 1300.0, 1700.0, {2, 0, 5}, 0, 0, 0},
101101
}
102102
};
103103

@@ -179,7 +179,7 @@ void dtmf_dolphin_tone_get_max_pos(uint8_t* max_rows, uint8_t* max_cols, uint8_t
179179
}
180180
tmp_rowspan[tones.pos.row] += tones.pos.span;
181181
if (tmp_rowspan[tones.pos.row] > max_span[0])
182-
max_span[0] = max_span[tones.pos.row];
182+
max_span[0] = tmp_rowspan[tones.pos.row];
183183
}
184184
max_rows[0]++;
185185
max_cols[0]++;

scenes/dtmf_dolphin_scene_start.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ void dtmf_dolphin_scene_start_on_enter(void* context) {
2727

2828
variable_item_list_add(var_item_list, "Dialer", 0, NULL, NULL);
2929
variable_item_list_add(var_item_list, "Bluebox", 0, NULL, NULL);
30+
variable_item_list_add(var_item_list, "Misc", 0, NULL, NULL);
3031

3132
variable_item_list_set_selected_item(
3233
var_item_list,
@@ -49,6 +50,9 @@ bool dtmf_dolphin_scene_start_on_event(void* context, SceneManagerEvent event) {
4950
} else if (event.event == DTMFDolphinEventStartBluebox) {
5051
scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateBluebox);
5152
scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
53+
} else if (event.event == DTMFDolphinEventStartMisc) {
54+
scene_manager_set_scene_state(app->scene_manager, DTMFDolphinSceneDialer, DTMFDolphinSceneStateMisc);
55+
scene_manager_next_scene(app->scene_manager, DTMFDolphinSceneDialer);
5256
}
5357
consumed = true;
5458
}

0 commit comments

Comments
 (0)