合成器 - 录制按键盘

时间:2018-04-28 10:43:30

标签: c audio avr atmega synthesizer

我必须构建合成器,我正在使用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

1 个答案:

答案 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(&note_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_notestop_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;效果