@@ -35,6 +35,13 @@ typedef struct {
3535 NoteSequenceState note_seq ;
3636 SpeakerNote * note_buf ; // kernel_malloc'd copy of notes
3737
38+ // Single tone source (raw frequency, no MIDI quantization)
39+ uint32_t tone_samples_remaining ;
40+ uint32_t tone_phase_acc ; // 16.16 fixed-point
41+ uint32_t tone_phase_inc ; // per-sample phase increment
42+ uint8_t tone_waveform ;
43+ uint8_t tone_velocity ;
44+
3845 // PCM stream source
3946 PcmStreamState pcm_stream ;
4047 SpeakerPcmFormat pcm_format ;
@@ -174,6 +181,9 @@ static void prv_stop_internal(SpeakerFinishReason reason) {
174181 pcm_stream_deinit (& s_state .pcm_stream );
175182 } else if (s_state .source_type == SpeakerSourceTracks ) {
176183 prv_free_tracks ();
184+ } else if (s_state .source_type == SpeakerSourceTone ) {
185+ s_state .tone_samples_remaining = 0 ;
186+ s_state .tone_phase_inc = 0 ;
177187 }
178188
179189 s_state .state = SpeakerStateIdle ;
@@ -330,6 +340,27 @@ static void prv_refill_bg(void *data) {
330340 memset (s_state .refill_buf , 0 , SPEAKER_REFILL_SAMPLES * sizeof (int16_t ));
331341 samples_generated = SPEAKER_REFILL_SAMPLES ;
332342 }
343+ } else if (s_state .source_type == SpeakerSourceTone ) {
344+ uint32_t to_gen = s_state .tone_samples_remaining ;
345+ if (to_gen > SPEAKER_REFILL_SAMPLES ) {
346+ to_gen = SPEAKER_REFILL_SAMPLES ;
347+ }
348+ if (to_gen == 0 ) {
349+ prv_stop_internal (SpeakerFinishReasonDone );
350+ return ;
351+ }
352+ if (s_state .tone_phase_inc == 0 ) {
353+ memset (s_state .refill_buf , 0 , to_gen * sizeof (int16_t ));
354+ } else {
355+ for (uint32_t i = 0 ; i < to_gen ; i ++ ) {
356+ s_state .refill_buf [i ] = note_synth_sample (s_state .tone_waveform ,
357+ s_state .tone_phase_acc ,
358+ s_state .tone_velocity );
359+ s_state .tone_phase_acc += s_state .tone_phase_inc ;
360+ }
361+ }
362+ s_state .tone_samples_remaining -= to_gen ;
363+ samples_generated = to_gen ;
333364 } else if (s_state .source_type == SpeakerSourceTracks ) {
334365 memset (s_state .mix_buf , 0 , sizeof (int32_t ) * SPEAKER_REFILL_SAMPLES );
335366 uint32_t max_generated = 0 ;
@@ -401,6 +432,41 @@ bool speaker_service_play_note_seq(const SpeakerNote *notes, uint32_t num_notes,
401432 return true;
402433}
403434
435+ bool speaker_service_play_tone (uint16_t freq_hz , uint16_t duration_ms ,
436+ uint8_t waveform , uint8_t velocity ,
437+ SpeakerPriority pri , uint8_t vol ) {
438+ if (!s_state .initialized || duration_ms == 0 ) {
439+ return false;
440+ }
441+
442+ if (!prv_can_preempt (pri )) {
443+ return false;
444+ }
445+
446+ if (s_state .state != SpeakerStateIdle ) {
447+ prv_stop_internal (SpeakerFinishReasonPreempted );
448+ }
449+
450+ s_state .tone_samples_remaining =
451+ ((uint32_t )duration_ms * SPEAKER_SAMPLE_RATE ) / 1000 ;
452+ s_state .tone_phase_acc = 0 ;
453+ // phase_inc = freq_hz * 65536 / sample_rate (16.16 fixed-point per sample)
454+ s_state .tone_phase_inc = (freq_hz != 0 )
455+ ? ((uint32_t )freq_hz * 65536u ) / SPEAKER_SAMPLE_RATE : 0 ;
456+ s_state .tone_waveform = waveform ;
457+ s_state .tone_velocity = velocity ;
458+
459+ s_state .state = SpeakerStatePlaying ;
460+ s_state .source_type = SpeakerSourceTone ;
461+ s_state .priority = pri ;
462+ s_state .volume = vol ;
463+
464+ prv_start_audio (vol );
465+ prv_refill_bg (NULL );
466+
467+ return true;
468+ }
469+
404470bool speaker_service_play_tracks (const SpeakerTrack * tracks , uint32_t num_tracks ,
405471 SpeakerPriority pri , uint8_t vol ) {
406472 if (!s_state .initialized || !tracks || num_tracks == 0 ||
@@ -611,6 +677,12 @@ bool speaker_service_play_note_seq(const SpeakerNote *notes, uint32_t num_notes,
611677 return false;
612678}
613679
680+ bool speaker_service_play_tone (uint16_t freq_hz , uint16_t duration_ms ,
681+ uint8_t waveform , uint8_t velocity ,
682+ SpeakerPriority pri , uint8_t vol ) {
683+ return false;
684+ }
685+
614686bool speaker_service_play_tracks (const SpeakerTrack * tracks , uint32_t num_tracks ,
615687 SpeakerPriority pri , uint8_t vol ) {
616688 return false;
0 commit comments