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 = { 0 };
31
+ osc -> cached_freq = 0 ;
32
+ osc -> offset = 0 ;
33
+ osc -> period = 0 ;
34
+ return osc ;
35
+ }
36
+
37
+ 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;
51
+ }
52
+
53
+ size_t calc_waveform_period (float freq ) {
54
+ if (!freq ) {
55
+ return 0 ;
56
+ }
57
+ // DMA Rate constant, thanks to Dr_Zlo
58
+ float dma_rate = CPU_CLOCK_FREQ \
59
+ / 2 \
60
+ / DTMF_DOLPHIN_HAL_DMA_PRESCALER \
61
+ / (DTMF_DOLPHIN_HAL_DMA_AUTORELOAD + 1 );
62
+
63
+ return (uint16_t ) (dma_rate / freq );
64
+ }
65
+
66
+ float sample_frame (DTMFDolphinOsc * osc , float freq ) {
67
+ float frame = 0.0 ;
68
+
69
+ if (freq != osc -> cached_freq || !osc -> period ) {
70
+ osc -> cached_freq = freq ;
71
+ osc -> period = calc_waveform_period (freq );
72
+ osc -> offset = 0 ;
73
+ }
74
+ if (osc -> period ) {
75
+ frame = tanhf (sin (osc -> offset * PERIOD_2_PI / osc -> period ) + 1 );
76
+ osc -> offset = (osc -> offset + 1 ) % osc -> period ;
77
+ }
78
+
79
+ return frame ;
80
+ }
81
+
82
+ void dtmf_dolphin_audio_free (DTMFDolphinAudio * player ) {
83
+ furi_message_queue_free (player -> queue );
84
+ dtmf_dolphin_osc_free (player -> osc1 );
85
+ dtmf_dolphin_osc_free (player -> osc2 );
86
+ free (player -> buffer_buffer );
87
+ free (player -> sample_buffer );
88
+ }
89
+
90
+ void dtmf_dolphin_osc_free (DTMFDolphinOsc * osc ) {
91
+ UNUSED (osc );
92
+ // Nothing to free now, but keeping this here in case I reimplement caching
93
+ }
94
+
95
+ bool generate_waveform (DTMFDolphinAudio * player , float freq1 , float freq2 , uint16_t buffer_index ) {
96
+ uint16_t * sample_buffer_start = & player -> sample_buffer [buffer_index ];
97
+
98
+ 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 );
100
+ data *= player -> volume ;
101
+ data *= UINT8_MAX / 2 ; // scale -128..127
102
+ data += UINT8_MAX / 2 ; // to unsigned
103
+
104
+ if (data < 0 ) {
105
+ data = 0 ;
106
+ }
107
+
108
+ if (data > 255 ) {
109
+ data = 255 ;
110
+ }
111
+
112
+ player -> buffer_buffer [i ] = data ;
113
+ sample_buffer_start [i ] = data ;
114
+ }
115
+
116
+ return true;
117
+ }
118
+
119
+ bool dtmf_dolphin_audio_play_tones (float freq1 , float freq2 ) {
120
+ current_player = dtmf_dolphin_audio_alloc ();
121
+
122
+ generate_waveform (current_player , freq1 , freq2 , 0 );
123
+ generate_waveform (current_player , freq1 , freq2 , current_player -> half_buffer_length );
124
+
125
+ dtmf_dolphin_speaker_init ();
126
+ dtmf_dolphin_dma_init ((uint32_t )current_player -> sample_buffer , current_player -> buffer_length );
127
+
128
+ furi_hal_interrupt_set_isr (FuriHalInterruptIdDma1Ch1 , dtmf_dolphin_audio_dma_isr , current_player -> queue );
129
+
130
+ dtmf_dolphin_dma_start ();
131
+ dtmf_dolphin_speaker_start ();
132
+
133
+ return true;
134
+ }
135
+
136
+ bool dtmf_dolphin_audio_stop_tones () {
137
+ current_player -> playing = false;
138
+
139
+ dtmf_dolphin_speaker_stop ();
140
+ dtmf_dolphin_dma_stop ();
141
+
142
+ furi_hal_interrupt_set_isr (FuriHalInterruptIdDma1Ch1 , NULL , NULL );
143
+
144
+ dtmf_dolphin_audio_free (current_player );
145
+
146
+ return true;
147
+ }
148
+
149
+ 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;
167
+ }
168
+ }
169
+ return false;
170
+ }
0 commit comments