PingPong/Core/Src/Sound.c
2024-10-02 22:59:51 +03:00

143 lines
4.8 KiB
C

/*
* Sound.c
*
* Created on: Oct 1, 2024
* Author: DashyFox
*/
#include "Sound.h"
#include "stm32f103xb.h"
#include "SimpleTimer.h"
static uint32_t bpm = 120; // Default BPM
static uint32_t note_end_time_ch[4] = {0, 0, 0, 0}; // End times for each channel (1-4)
static Melody_t melodies[4]; // Array of melodies for each channel
// Initialize the sound system
#define SOUND_TIMER TIM4
#define SOUND_TIMER_RCC RCC_APB1ENR_TIM4EN
// Initialize the sound system
void sound_init(void) {
// Enable timer clock
RCC->APB1ENR |= SOUND_TIMER_RCC;
// Configure GPIO pins for timer output (assuming GPIOB pins for TIM4 CH3 and CH4)
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
// Configure only channels 3 and 4
// Channel 3
GPIOB->CRH &= ~(0xF << (3 * 4)); // Clear configuration for PB10 (TIM4_CH3)
GPIOB->CRH |= (0xB << (3 * 4)); // Set to alternate function push-pull
// Channel 4
GPIOB->CRH &= ~(0xF << (4 * 4)); // Clear configuration for PB11 (TIM4_CH4)
GPIOB->CRH |= (0xB << (4 * 4)); // Set to alternate function push-pull
// Initialize the timer for PWM output
SOUND_TIMER->PSC = 0;
SOUND_TIMER->ARR = 0xFFFF;
// Configure only channels 3 and 4 for PWM output
SOUND_TIMER->CCMR2 &= ~(TIM_CCMR2_OC3M | TIM_CCMR2_OC4M); // Clear channel 3 and 4 mode
SOUND_TIMER->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos); // Set channel 3 to PWM mode 1
SOUND_TIMER->CCER |= (TIM_CCER_CC3E | TIM_CCER_CC4E); // Enable output for channels 3 and 4
SOUND_TIMER->EGR = TIM_EGR_UG; // Update the timer
SOUND_TIMER->CR1 |= TIM_CR1_CEN; // Enable the timer
}
// Set BPM
void sound_set_bpm(uint32_t new_bpm) {
bpm = new_bpm;
}
// Play a note on a specific channel
void sound_play_note(Note_t note, uint8_t channel) {
if (channel < 1 || channel > 4) return;
uint32_t *ccr = &SOUND_TIMER->CCR1 + (channel - 1); // Select correct CCR for the channel
uint32_t *ccmr = (channel <= 2) ? &SOUND_TIMER->CCMR1 : &SOUND_TIMER->CCMR2;
uint32_t *ccer = &SOUND_TIMER->CCER;
uint32_t enable_cc = TIM_CCER_CC1E << ((channel - 1) * 4);
if (note.frequency == 0) {
*ccer &= ~enable_cc;
note_end_time_ch[channel - 1] = millis() + note.duration;
} else {
uint32_t timer_clock = SystemCoreClock / 2;
uint32_t prescaler = (timer_clock / (note.frequency * 65536)) + 1;
uint32_t arr = (timer_clock / (prescaler * note.frequency)) - 1;
uint32_t ccr_val = ((arr + 1) * note.duty_cycle) / 100 - 1;
SOUND_TIMER->PSC = prescaler - 1;
SOUND_TIMER->ARR = arr;
*ccr = ccr_val;
*ccmr &= ~(TIM_CCMR1_OC1M << ((channel - 1) * 8));
*ccmr |= (6 << TIM_CCMR1_OC1M_Pos) << ((channel - 1) * 8);
*ccer |= enable_cc;
SOUND_TIMER->EGR = TIM_EGR_UG;
SOUND_TIMER->CR1 |= TIM_CR1_CEN;
note_end_time_ch[channel - 1] = (note.duration == 0) ? 0 : millis() + note.duration;
}
}
// Play a melody on a specific channel with repeat count
void sound_play_melody(Note_t* melody, uint32_t size, uint8_t channel, uint32_t repeat_count) {
if (channel < 1 || channel > 4 || melody == NULL || size == 0) return;
uint8_t ch_idx = channel - 1;
melodies[ch_idx].notes = melody;
melodies[ch_idx].size = size;
melodies[ch_idx].current_note = 0;
melodies[ch_idx].repeat_count = repeat_count;
melodies[ch_idx].repeat_left = repeat_count;
sound_play_note(melody[0], channel); // Play the first note
}
// Function to play the next note in the melody for a specific channel
void play_next_note(uint8_t channel) {
uint8_t ch_idx = channel - 1;
Melody_t* melody = &melodies[ch_idx];
if (melody->current_note >= melody->size) {
// If end of melody, check repeat
if (melody->repeat_left > 0) {
melody->repeat_left--;
melody->current_note = 0; // Restart melody
} else {
// Melody finished, no more repeats
melody->notes = NULL;
return;
}
}
sound_play_note(melody->notes[melody->current_note], channel);
melody->current_note++;
}
// Function to be called periodically to handle note duration
void sound_tick(void) {
for (uint8_t ch = 0; ch < 4; ++ch) {
if (note_end_time_ch[ch] != 0 && millis() >= note_end_time_ch[ch]) {
SOUND_TIMER->CCER &= ~(TIM_CCER_CC1E << (ch * 4)); // Disable output
note_end_time_ch[ch] = 0;
if (melodies[ch].notes != NULL) {
play_next_note(ch + 1); // Play next note for channel
}
}
}
}
// Create a note
Note_t sound_create_note(MusicalNote_t note, NoteDuration_t duration, uint8_t duty_cycle) {
uint32_t note_duration = (duration == T0) ? 0 : (60000 * 4 / (bpm * duration));
return (Note_t){ .frequency = note, .duration = note_duration, .duty_cycle = duty_cycle };
}