Skip to content

Commit 78050ff

Browse files
authored
Merge pull request #2 from litui/refactor
Refactor
2 parents 508d20a + cb02b72 commit 78050ff

26 files changed

+628
-685
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.

application.fam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ App(
88
"gui",
99
"dialogs",
1010
],
11-
stack_size=4 * 1024,
11+
stack_size=8 * 1024,
1212
order=20,
1313
)

dtmf_dolphin.c

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,11 @@ static void dtmf_dolphin_app_tick_event_callback(void* context) {
1919
furi_assert(context);
2020
DTMFDolphinApp* app = context;
2121

22-
// Needed to handle queueing to ISR and prioritization of audio
23-
if (app->player.playing) {
24-
dtmf_dolphin_player_handle_tick();
25-
} else {
26-
scene_manager_handle_tick_event(app->scene_manager);
27-
}
22+
scene_manager_handle_tick_event(app->scene_manager);
2823
}
2924

3025
static DTMFDolphinApp* app_alloc() {
3126
DTMFDolphinApp* app = malloc(sizeof(DTMFDolphinApp));
32-
app->player.half_samples = 4 * 1024;
33-
app->player.sample_count = 8 * 1024;
34-
app->player.sample_buffer = malloc(sizeof(uint16_t) * app->player.sample_count);
35-
app->player.buffer_buffer = malloc(sizeof(uint8_t) * app->player.sample_count);
36-
app->player.wf1_period = 0;
37-
app->player.wf2_period = 0;
38-
app->player.wf1_freq = 0;
39-
app->player.wf2_freq = 0;
40-
app->player.wf1_pos = 0;
41-
app->player.wf2_pos = 0;
42-
app->player.queue = furi_message_queue_alloc(10, sizeof(DTMFDolphinEvent));
43-
app->player.volume = 2.0f;
44-
app->player.playing = false;
4527

4628
app->gui = furi_record_open(RECORD_GUI);
4729
app->view_dispatcher = view_dispatcher_alloc();
@@ -70,22 +52,6 @@ static DTMFDolphinApp* app_alloc() {
7052
DTMFDolphinViewDialer,
7153
dtmf_dolphin_dialer_get_view(app->dtmf_dolphin_dialer));
7254

73-
app->dtmf_dolphin_bluebox = dtmf_dolphin_bluebox_alloc();
74-
view_dispatcher_add_view(
75-
app->view_dispatcher,
76-
DTMFDolphinViewBluebox,
77-
dtmf_dolphin_bluebox_get_view(app->dtmf_dolphin_bluebox));
78-
79-
app->dtmf_dolphin_play = widget_alloc();
80-
view_dispatcher_add_view(
81-
app->view_dispatcher,
82-
DTMFDolphinViewPlay,
83-
widget_get_view(app->dtmf_dolphin_play));
84-
85-
// app->dialer_button_panel = button_panel_alloc();
86-
// app->bluebox_button_panel = button_panel_alloc();
87-
// app->redbox_button_panel = button_panel_alloc();
88-
8955
app->notification = furi_record_open(RECORD_NOTIFICATION);
9056
notification_message(app->notification, &sequence_display_backlight_enforce_on);
9157

@@ -97,21 +63,15 @@ static DTMFDolphinApp* app_alloc() {
9763
static void app_free(DTMFDolphinApp* app) {
9864
furi_assert(app);
9965
view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewMainMenu);
100-
view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewBluebox);
10166
view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewDialer);
102-
view_dispatcher_remove_view(app->view_dispatcher, DTMFDolphinViewPlay);
10367
variable_item_list_free(app->main_menu_list);
10468

105-
dtmf_dolphin_bluebox_free(app->dtmf_dolphin_bluebox);
10669
dtmf_dolphin_dialer_free(app->dtmf_dolphin_dialer);
107-
widget_free(app->dtmf_dolphin_play);
70+
// widget_free(app->dtmf_dolphin_play);
10871

10972
view_dispatcher_free(app->view_dispatcher);
11073
scene_manager_free(app->scene_manager);
11174

112-
furi_message_queue_free(app->player.queue);
113-
free(app->player.sample_buffer);
114-
11575
// button_panel_free(app->dialer_button_panel);
11676
// button_panel_free(app->bluebox_button_panel);
11777
// button_panel_free(app->redbox_button_panel);
@@ -126,8 +86,6 @@ static void app_free(DTMFDolphinApp* app) {
12686
int32_t dtmf_dolphin_app(void *p) {
12787
UNUSED(p);
12888
DTMFDolphinApp* app = app_alloc();
129-
130-
dtmf_dolphin_player_init(&(app->player));
13189

13290
view_dispatcher_run(app->view_dispatcher);
13391

dtmf_dolphin_audio.c

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#include "dtmf_dolphin_audio.h"
2+
3+
DTMFDolphinAudio *current_player;
4+
5+
static void dtmf_dolphin_audio_dma_isr(void* ctx) {
6+
FuriMessageQueue *event_queue = ctx;
7+
8+
if (LL_DMA_IsActiveFlag_HT1(DMA1)) {
9+
LL_DMA_ClearFlag_HT1(DMA1);
10+
11+
DTMFDolphinCustomEvent event = {.type = DTMFDolphinEventDMAHalfTransfer};
12+
furi_message_queue_put(event_queue, &event, 0);
13+
}
14+
15+
if(LL_DMA_IsActiveFlag_TC1(DMA1)) {
16+
LL_DMA_ClearFlag_TC1(DMA1);
17+
18+
DTMFDolphinCustomEvent event = {.type = DTMFDolphinEventDMAFullTransfer};
19+
furi_message_queue_put(event_queue, &event, 0);
20+
}
21+
}
22+
23+
void dtmf_dolphin_audio_clear_samples(DTMFDolphinAudio* player) {
24+
for (size_t i = 0; i < player->buffer_length; i++) {
25+
player->sample_buffer[i] = 0;
26+
}
27+
}
28+
29+
DTMFDolphinOsc* dtmf_dolphin_osc_alloc() {
30+
DTMFDolphinOsc *osc = malloc(sizeof(DTMFDolphinOsc));
31+
osc->cached_freq = 0;
32+
osc->offset = 0;
33+
osc->period = 0;
34+
osc->lookup_table = NULL;
35+
return osc;
36+
}
37+
38+
DTMFDolphinAudio* dtmf_dolphin_audio_alloc() {
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;
50+
}
51+
52+
size_t calc_waveform_period(float freq) {
53+
if (!freq) {
54+
return 0;
55+
}
56+
// DMA Rate calculation, thanks to Dr_Zlo
57+
float dma_rate = CPU_CLOCK_FREQ \
58+
/ 2 \
59+
/ DTMF_DOLPHIN_HAL_DMA_PRESCALER \
60+
/ (DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1);
61+
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;
65+
}
66+
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);
79+
80+
for (size_t i = 0; i < osc->period; i++) {
81+
osc->lookup_table[i] = sin(i * PERIOD_2_PI / osc->period) + 1;
82+
}
83+
}
84+
85+
float sample_frame(DTMFDolphinOsc* osc) {
86+
float frame = 0.0;
87+
88+
if (osc->period) {
89+
frame = osc->lookup_table[osc->offset];
90+
osc->offset = (osc->offset + 1) % osc->period;
91+
}
92+
93+
return frame;
94+
}
95+
96+
void dtmf_dolphin_audio_free(DTMFDolphinAudio* player) {
97+
furi_message_queue_free(player->queue);
98+
dtmf_dolphin_osc_free(player->osc1);
99+
dtmf_dolphin_osc_free(player->osc2);
100+
free(player->sample_buffer);
101+
free(player);
102+
current_player = NULL;
103+
}
104+
105+
void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc) {
106+
if (osc->lookup_table != NULL) {
107+
free(osc->lookup_table);
108+
}
109+
free(osc);
110+
}
111+
112+
bool generate_waveform(DTMFDolphinAudio* player, uint16_t buffer_index) {
113+
uint16_t* sample_buffer_start = &player->sample_buffer[buffer_index];
114+
115+
for (size_t i = 0; i < player->half_buffer_length; i++) {
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+
}
124+
data *= player->volume;
125+
data *= UINT8_MAX / 2; // scale -128..127
126+
data += UINT8_MAX / 2; // to unsigned
127+
128+
if(data < 0) {
129+
data = 0;
130+
}
131+
132+
if(data > 255) {
133+
data = 255;
134+
}
135+
136+
sample_buffer_start[i] = data;
137+
}
138+
139+
return true;
140+
}
141+
142+
bool dtmf_dolphin_audio_play_tones(float freq1, float freq2) {
143+
current_player = dtmf_dolphin_audio_alloc();
144+
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);
150+
151+
dtmf_dolphin_speaker_init();
152+
dtmf_dolphin_dma_init((uint32_t)current_player->sample_buffer, current_player->buffer_length);
153+
154+
furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, dtmf_dolphin_audio_dma_isr, current_player->queue);
155+
156+
dtmf_dolphin_dma_start();
157+
dtmf_dolphin_speaker_start();
158+
return true;
159+
}
160+
161+
bool dtmf_dolphin_audio_stop_tones() {
162+
dtmf_dolphin_speaker_stop();
163+
dtmf_dolphin_dma_stop();
164+
165+
furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
166+
167+
dtmf_dolphin_audio_free(current_player);
168+
169+
return true;
170+
}
171+
172+
bool dtmf_dolphin_audio_handle_tick() {
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+
}
185+
}
186+
}
187+
return handled;
188+
}

dtmf_dolphin_audio.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#pragma once
2+
// #include "dtmf_dolphin_i.h"
3+
#include "dtmf_dolphin_event.h"
4+
#include "dtmf_dolphin_hal.h"
5+
6+
#define SAMPLE_BUFFER_LENGTH 8192
7+
#define PERIOD_2_PI 6.2832
8+
#define CPU_CLOCK_FREQ 64000000
9+
10+
typedef struct {
11+
float cached_freq;
12+
size_t period;
13+
float* lookup_table;
14+
uint16_t offset;
15+
} DTMFDolphinOsc;
16+
17+
typedef struct {
18+
size_t buffer_length;
19+
size_t half_buffer_length;
20+
uint8_t *buffer_buffer;
21+
uint16_t *sample_buffer;
22+
float volume;
23+
FuriMessageQueue *queue;
24+
DTMFDolphinOsc *osc1;
25+
DTMFDolphinOsc *osc2;
26+
} DTMFDolphinAudio;
27+
28+
DTMFDolphinOsc* dtmf_dolphin_osc_alloc();
29+
30+
DTMFDolphinAudio* dtmf_dolphin_audio_alloc();
31+
32+
void dtmf_dolphin_audio_free(DTMFDolphinAudio* player);
33+
34+
void dtmf_dolphin_osc_free(DTMFDolphinOsc* osc);
35+
36+
bool dtmf_dolphin_audio_play_tones(float freq1, float freq2);
37+
38+
bool dtmf_dolphin_audio_stop_tones();
39+
40+
bool dtmf_dolphin_audio_handle_tick();

0 commit comments

Comments
 (0)