我必须构建合成器,我正在使用C来编程我的ATmega128A。我需要记录按下的键盘并在一段时间后播放它们。对于键盘按下,我在main.c中使用轮询。为了播放键盘我使用的是Timer1。每次计时器到期时,我都会存储键盘频率和增量计数器。在比赛期间,我计算持续时间firstle,然后播放该间隔。当我想播放存储的歌曲时,它会嘀嗒一段时间并开始发出长长的声音。
此外,我希望能够按下,录制和播放同声键盘。你能为此建议一些算法吗?
的main.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "keypad.h"
unsigned char temp; // to get keyboard input to play a note
unsigned char option; //to choose the embedded music to play
#define DELAY 1000
int main(void)
{
DDRG = 0xff; // To send sound to BUZ speakers (BUZ is connected to PG.4)
DDRD = 0x00; // Make it input, to get corresponding key to play a note
PORTD = 0xff; // All bits are 1s, so no button is pressed in the beginning
sei(); //Set Interrupt flag as enabled in SREG register
option = no_music; //No music is played on startup, this is default mode for free playing
// This loop keeps playing forever, so the main functionality
// of the program is below
DDRB = 0xff;
DDRD = 0x00; //ready for input
while(1)
{
temp = PIND; //store keyboard input for temporary variable
//PORTB = PIND;
switch(temp)
{
case 254: { // if 1st pin of PORTD is pressed
play_note(notes5[0]); // play corresponding note from octave 5 for 200ms
break;
}
case 253: { // if 2nd pin of PORTD is pressed
play_note(notes5[1]);
break;
}
case 251: { // if 3rd pin of PORTD is pressed
play_note(notes5[2]);
break;
}
case 247: { // if 4th pin of PORTD is pressed
play_note(notes5[3]);
break;
}
case 239: { // if 5th pin of PORTD is pressed
play_note(notes5[4]);
break;
}
case 223: { // if 6th pin of PORTD is pressed
play_note(notes5[5]);
break;
}
case 191: { // if 7th pin of PORTD is pressed
play_note(notes5[6]);
break;
}
case 127: {
if(isRecordingEnabled){
disableRecording();
//toggling LED as the sign of playing the record
toggleLED();
custom_delay_ms(DELAY);
toggleLED();
custom_delay_ms(DELAY);
custom_delay_ms(DELAY);
play_record();
}else{
//toggling LED as the sign of record start
toggleLED();
enableRecording();
}
}
}
}
return 0;
}
keypad.c
#include "structs.h"
#include "play.h"
#define F_CPU 16000000UL // 16 MHz
#include <util/delay.h>
#define BUFFER_SIZE 100
struct played_note buffer[BUFFER_SIZE];
int i = 0;
int8_t isRecordingEnabled = 0;
int8_t recordIndex = 0;
int8_t pressedNote;
int8_t isPressed = 0;
int8_t isPlaying = 0;
unsigned int ms_count = 0;
#define INTERVAL 100
#define DELAY_VALUE 0xFF
ISR(TIMER1_COMPA_vect){
// every time when timer0 reaches corresponding frequency,
// invert the output signal for BUZ, so it creates reflection, which leads to sound generation
//check whether the key was pressed because
//when the recording is enabled the interrupt is working make sound
if(isPressed || isPlaying)
PORTG = ~(PORTG);
if(isRecordingEnabled){
if(PIND == DELAY_VALUE)
pressedNote = DELAY_VALUE;
if(i == 0){
buffer[i].note = pressedNote;
buffer[i].counter = 0;
i++;
}else{
if(buffer[i - 1].note == pressedNote){
//the same note is being pressed
buffer[i - 1].counter++;
}else{
buffer[i++].note = pressedNote;
buffer[i].counter = 0;
}
}
}
}
void initTimer1(){
TIMSK = (1 << OCIE1A); //Timer1 Comparator Interrupt is enabled
TCCR1B |= (1 << WGM12) | (1 << CS12); //CTC mode, prescale = 256
}
void stopTimer1(){
TIMSK &= ~(1UL << OCIE1A);
TCCR1A = 0; //stop the timer1
TIFR = (1 << OCF1A); //Clear the timer1 Comparator Match flag
}
void enableRecording(){
isRecordingEnabled = 1;
i = 0;
ms_count = 0;
initTimer1();
}
void disableRecording(){
isRecordingEnabled = 0;
stopTimer1();
}
//Timer1A
void play_note_during(unsigned int note, unsigned int duration){
OCR1A = note;
pressedNote = note;
isPressed = 1;
initTimer1();
custom_delay_ms(duration);
stopTimer1();
isPressed = 0;
}
//Timer1A
void play_note(unsigned int note){
play_note_during(note, INTERVAL);
}
void play_record(){
isPlaying = 1;
recordIndex = 0;
int duration;
while(recordIndex < i){
PORTB = buffer[return].counter << 8;
duration = INTERVAL * buffer[recordIndex].counter;
if(buffer[recordIndex].note == DELAY_VALUE)
custom_delay_ms(duration);
else
play_note_during(buffer[recordIndex].note, duration);
recordIndex++;
}
isPlaying = 0;
}
可以在以下github存储库中找到更多参考: https://github.com/bedilbek/music_simulation
答案 0 :(得分:0)
实际上,关于如何录制和重放按键的问题,应该通过另一个关于如何同时播放多个声音的问题来预测。 现在您只使用可变频率的PWM输出。但是这允许您只生成一个方形波形。您不能播放两个音符(使用另一个定时器和另一个PWM输出除外)。 取而代之的是,我建议您使用最高频率的PWM并应用RC或LC滤波器将高频PWM信号平滑为波形,然后将该波形应用于放大器和扬声器以发出声音。 采用这种方法,您可以生成不同的波形,将它们混合在一起,使它们更响亮或更安静,甚至可以应用“淡出”#34;使它们听起来像钢琴的效果。 但你的问题不在于如何产生这种波形,所以如果你想知道你应该开始另一个问题。
所以,回到你的问题。
而不是单个程序,即开始记录,暂停并且仅返回;我建议你有几个程序,一个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&#34;或者&#34;密钥X被释放&#34;或&#34; X计时器周期到期&#34;。
在回放时,您只需执行向后过程,扫描阵列并执行命令:调用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)在同一时刻按下多少音符并不重要。由于暂停和按键事件是分开记录的,因此在重放期间,所有同时按下的按键将在同一时间重放,而没有&#34;琶音&#34;效果