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