0

I have to build synthesizer and I am using C for programing my ATmega128A. I need to record the keypads pressed and play them after some time. For keypad press I am using polling in the main.c. For playing the keypads I am using Timer1. Every time when the timer expires I am storing the keypad frequency and increment counter for it. During play, I calculate the duration firstle, then play it for that interval. When I want to play the stored song, it ticks for some time and starts to make a long sound.

Also, I want to make possible to press, record and palay simultanous keypads. Can you suggest some algorithm for this?

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;
}

Further references can be found in the following github repository: https://github.com/bedilbek/music_simulation

greg-449
  • 109,219
  • 232
  • 102
  • 145
Madina
  • 25
  • 7

1 Answers1

0

Actually your question about how to record and replay key presses, should be anticipated by another question about how to play several sounds simultaneously. Now you're using just PWM output with variable frequency. But this allows you to generate only a single wave of a square form. You cannot play two notes (except of using another timer and another PWM output). Instead of that, I suggest you to use PWM at the highest frequency and apply a RC or LC filters to smooth high-frequency PWM signal into a waveform, and then apply that waveform to the amplifier and to the speaker to make a sound. Having this approach you make generate different waveforms, mix them together, make them louder or more silent and even apply a "fade-out" effect to make them sound like a piano. But your question is not about how to generate that kind of wave-forms, so if you want to know you should start another question.

So, returning to your question.

Instead of having single procedure, which is starting note, holding a pause and only then returning back; I suggest you to have a several procedures, one play_note(note) - which will start playing a note and returns immediately (while note continue playing). And, of course, stop_note(note) - which will stop specified note, if it is played. Also I suggest you to pass a note number, rather than frequency or timer period to the play function. Let's assume 0 is the lowest possible note (e.g. C2), and then they go sequentially by semitones: 1 - C#2, 2 - D2, .... 11 - B2, 12 - C3 ... etc.

For the first time, you may remake your single-note playing procedures to match that.

 // #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
    }
 }

so, now your task is very simple: you should just pull the state of the keys periodically, let's say 61 times per second (based on the some 8-bit timer with 1:1024 prescaler overflow), and they to note: which keys are changed their state. If some key is pressed, you call play_note to start coressponding note. If the key is released, you call stop_note, also you're counting how many timer cycles passed since the last event. When recording, you just push those events into array "key X is pressed" or "key X is released" or "X timer cycles expired". When playing back, you just performing a backward process, scaning your array, and executing commands: calling play_note, stop_note, or waiting exact amount of timer cycles, if it is a pause.

Instead of writing giant switch statement, you may also use a table to scan the buttons

// 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_... implement as you wish.

the playback would be the same simple (below just the template, you'll suggest from the function name what they should do)

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();
        }
    }
}

This approach gives you two benefits:

1) It doesn't matter how many notes the playing module can play, the keys are recorder while they are pressed and released, so, all simultaneous keys will be recorded and replayed in the same time.

2) It doesn't matter how many notes are pressed in exact the same moment. Since pause and key events are recorded separately, during the replay all the same-time key presses will be replayed in the same time, without an "arpeggio" effect

AterLux
  • 4,566
  • 2
  • 10
  • 13