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
+ }
0 commit comments