На самом деле ваш вопрос о том, как записывать и воспроизводить нажатия клавиш, должен ожидаться другим вопросом о том, как воспроизводить несколько звуков одновременно.
Теперь вы используете только ШИМ-выход с переменной частотой. Но это позволяет генерировать только одну волну квадратной формы. Вы не можете играть две ноты (кроме использования другого таймера и другого выхода ШИМ).
Вместо этого я предлагаю вам использовать ШИМ на самой высокой частоте и применять RC или LC-фильтры для сглаживания высокочастотного ШИМ-сигнала в виде сигнала, а затем подавать этот сигнал на усилитель и на динамик для создания звука.
Используя этот подход, вы генерируете различные формы волны, смешиваете их вместе, делаете их громче или тише и даже применяете эффект «затухания», чтобы они звучали как пианино.
Но ваш вопрос не в том, как генерировать подобные волновые формы, поэтому, если вы хотите знать, вам следует задать другой вопрос.
Итак, возвращаясь к вашему вопросу.
Вместо единой процедуры, которая начинается с примечания, удерживается пауза и только потом возвращается назад; Я предлагаю вам выполнить несколько процедур, одну play_note(note)
, которая начнет воспроизведение ноты и немедленно вернется (пока нота продолжит играть). И, конечно же, stop_note(note)
- что остановит указанную ноту, если она сыграна. Также я предлагаю вам передать номер ноты, а не частоту или период таймера в функцию play
. Давайте предположим, что 0 - это самая низкая возможная нота (например, C2), а затем они идут последовательно по полутонам: 1 - C # 2, 2 - D2, .... 11 - B2, 12 - C3 ... и т. Д.
Впервые вы можете переделать свои процедуры игры на одной ноте, чтобы соответствовать этому.
// #include <avr/pgmspace.h> to store tables in the flash memory
PROGMEM uint16_t const note_ocr_table[] = {
OCR1A_VALUE_FOR_C2, OCR1A_VALUE_FOR_C2_SHARP, ... etc
}; // a table to map note number into OCR1A value
#define NOTE_NONE 0xFF
static uint8_t current_note = NOTE_NONE;
void play_note(uint8_t note) {
if (note >= (sizeof(note_ocr_table) / sizeof(note_ocr_table[0])) return; // do nothing on the wrong parameter;
uint16_t ocr1a_val = pgm_read_word(¬e_ocr_table[note]);
TIMSK = (1 << OCIE1A); //Timer1 Comparator Interrupt is enabled // why you need this? May be you want to use just inverting OC1A output?
TCCR1B |= (1 << WGM12) | (1 << CS12); //CTC mode, prescale = 256 // you may want to use lesser prescalers and higher OCR1A values ?
OCR1A = ocr1a_val;
if (TCNT1 >= ocr1a_val) TCNT1 = 0; // do not miss the compare match when ORC1A is changed to lower values;
current_note = note;
}
void stop_note(uint8_t note) {
if (note == current_note) { // ignore stop for non-current note.
TIMSK &= ~(1UL << OCIE1A);
TCCR1A = 0; //stop the timer1
TIFR = (1 << OCF1A); //Clear the timer1 Comparator Match flag
current_note = NOTE_NONE; // No note is playing
}
}
Итак, теперь ваша задача очень проста: вам нужно просто периодически проверять состояние ключей, скажем, 61 раз в секунду (основываясь на некотором 8-битном таймере с переполнением прескалера 1: 1024), и они должны заметить : какие ключи меняют свое состояние. Если какая-то клавиша нажата, вы звоните play_note
, чтобы начать ответную заметку. Если ключ отпущен, вы звоните stop_note
, а также подсчитываете, сколько циклов таймера прошло с момента последнего события.
При записи вы просто помещаете эти события в массив «клавиша X нажата», или «клавиша X отпущена», или «истекло количество циклов таймера X».
При воспроизведении вы просто выполняете обратный процесс, сканируете свой массив и выполняете команды: вызов play_note
, stop_note
или ожидание точного количества циклов таймера, если это пауза.
Вместо написания гигантского оператора switch
вы также можете использовать таблицу для сканирования кнопок
// number of element in the port-state arrays
#define A 0
#define B 1
#define C 2
#define D 3
#define E 4
#define F 5
typedef struct {
port_index uint8_t;
mask uint8_t;
} KeyLocation;
PROGMEM KeyLocation const key_location[] = {
{ B, (1 << 1) }, // where C2 is located, e.g. PB1
{ E, (1 << 3) }, // where C#2 is located, e.g. PE3
...
}
uint16_t ticks_from_prev_event = 0;
uint8_t port_state_prev[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0XFF};
for (;;) { // main loop
wait_tick_timer_to_overflow();
// latching state of the pins
uint8_t port_state[6] = {PINA, PINB, PINC, PIND, PINE, PINF};
for (uint8_t i = 0 ; i < (sizeof(key_location) / sizeof(key_location[0])) ; i++) {
uint8_t port_idx = pgm_read_byte(&key_location[i].port_index);
uint8_t mask = pgm_read_byte(&key_location[i].mask);
if ((port_state[port_idx] & mask) != (port_state_prev[port_idx] & mask)) { // if pin state was changed
if (is_recording && (ticks_from_prev_event > 0)) {
put_into_record_pause(ticks_from_prev_event); // implement it on your own
}
if ((port_state[port_idx] & mask) == 0) { // key is pressed
play_note(i);
if (is_recording) {
put_into_record_play_note(i); // implement
}
} else { // key is released
stop_note(i);
if (is_recording) {
put_into_record_stop_note(i); // implement
}
}
}
}
// the current state of the pins now becomes a previous
for (uint8_t i = 0 ; i < (sizeof(port_state) / sizeof(port_state[0])) ; i++) {
port_state_prev[i] = port_state[i];
}
if (ticks_from_prev_event < 65535) ticks_from_prev_event++;
}
put_into_record_...
Реализуйте как хотите.
Воспроизведение будет таким же простым (под шаблоном ниже вы можете указать из названия функции, что они должны делать)
while (has_more_data_in_the_recording()) {
if (next_is_play()) {
play_note(get_note_from_recording())
} else if (next_is_stop()) {
play_note(get_note_from_recording())
} else {
uint16_t pause = get_pause_value_from_recording();
while (pause > 0) {
pause--;
wait_tick_timer_to_overflow();
}
}
}
Этот подход дает вам два преимущества:
1) Неважно, сколько нот может воспроизводить воспроизводящий модуль, клавиши записываются, когда они нажаты и отпущены, поэтому все одновременные клавиши будут записаны и воспроизведены одновременно.
2) Неважно, сколько нот нажимается в один и тот же момент. Так как события паузы и клавиш записываются отдельно, во время воспроизведения все нажатия клавиш в одно и то же время будут воспроизводиться в одно и то же время без эффекта «арпеджио»