[아두이노] 아두이노 피아노 만들기

IOT/아두이노|2019. 8. 16. 09:00

[아두이노] 아두이노 피아노 만들기



예전 post를 정리하면서 피에조부저의 응용 예제로 빼먹었던 아두이노 피아노 만들기를 이번에 post로 작성하게 되었습니다. 원리는 피에조부저 제어편에서 "도레미파솔라시도" 음을 순서대로 FOR문을 통해 멜로디를 출력했던 소스에서 이 멜로디 음을 스위치버턴을 이용하여 아두이노 피아노로 구현해 보았습니다. 실험은 가상시뮬레이터에서 이루어 집니다.

이제 어떻게 만들는지 살펴보도록 하죠.

1. 피아노 이해



피아노의 건반을 아두이노를 이용하여 표현을 해 볼까요. 피아노는 건반과 사운드로 나눌 수 있습니다. 건반을 누르면 해당 음으로 소리로 출력이 됩니다. 이 부분을 아두이노로 표현한다면 다음과 같습니다.

  • 건반 = 스위치 버턴
  • 사운드 = 피에조부조


스위치 버턴을 누르면 피에조부저로 해당 음을 출력하면 간단히 아두이노 피아노를 만들 수 있겠죠.

2. 아두이노 피아노 회로도


준비물 : 스위치버턴 8개, 피에조부저 1개, 아두이노우노
내용 : 스위치 버턴을 2,3,4,5,6,7,8,9번 핀에 연결하고 피에조부저는 12번에 연결하시오.



9번부터 2번까지 "도레미파솔라시도"의 건반핀으로 사용 할 예정입니다.

3. 코딩



피에조부저 제어편에 가시면 기본 멜로디 "도레미파솔라시도" 출력 예제가 있습니다. 그 소스를 기반으로 실험합니다.

  • 기본음계
#define NOTE_C5  523   //도 (5옥타브 음계 데이터)
#define NOTE_D5  587   //레
#define NOTE_E5  659   //미
#define NOTE_F5  698   //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047  //도
  • 스위치버턴 변수
const byte melodyPin[] = {9,8,7,6,5,4,3,2}; //스위치버턴
  • 멜로디 출력 변수
const byte tonepin = 12; //피에조부저
  • 음계 변수
const int melody[] = { NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6 }; //도레미파솔라시도
int noteDurations = 50; //톤길이
  • 스위치버턴 누름 동작
for(int i=0;i<8;i++){ //8개의 건반을 빠르게 체크
   if(digitalRead(melodyPin[i])==LOW){ //내부풀업스위치버턴 누르면
     tone(tonepin, melody[i],noteDurations); //해당 스위치 버턴 음 출력
     delay(noteDurations);  //음길이 최소
     noTone(tonepin); //음 중단
   }
 }

총 0~7까지의 배열위치의 스위치버턴 상태를 체크합니다. 8개의 스위치 버턴 상태를 체크하기 때문에 위에서 for문을 이용하여 순차적으로 체크를 하게 됩니다. 해당 스위치 버턴이 눌르게 되면 최소 누른 음과 길이를 50만큼 음이 울리게 지정했습니다. 이렇게 한 이유는 2개 이상의 스위치 버턴을 누를 때 2개이상의 음이 동시에 울리는 착시음 효과를 나타내기 위해 최소음길이로 지정했네요. 이 원리는 예전에 여러개의 LED를 하나의 제어선을 이용하여 시간차로 동시에 서로 다른 LED에 불이 들어오게 하는 원리와 비슷합니다.

종합해보면,

#define NOTE_C5  523   //도 (5옥타브 음계 데이터)
#define NOTE_D5  587   //레
#define NOTE_E5  659   //미
#define NOTE_F5  698   //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047  //도

const byte melodyPin[] = {9,8,7,6,5,4,3,2}; //스위치버턴
const byte tonepin = 12; //피에조부저
const int melody[] = { NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6 }; //도레미파솔라시도
int noteDurations = 50; //톤길이

void setup()
{
  for (int i = 0; i < 8; i++) {
    pinMode(melodyPin[i], INPUT_PULLUP); //내부풀업스위치 지정
  }    
}

void loop()
{
  for(int i=0;i<8;i++){ //8개의 건반을 빠르게 체크
    if(digitalRead(melodyPin[i])==LOW){ //내부풀업스위치버턴 누르면
      tone(tonepin, melody[i],noteDurations); //해당 스위치 버턴 음 출력
      delay(noteDurations); //음길이 최소
      noTone(tonepin); //음 중단
    }
  }
}

4. 결과


간단히 "학교종"을 연주해 보았습니다.


마무리


오늘은 예전에 피에조부저에 대해서 post하면서 빼먹은 피에조부저 응용 예제로 아두이노 피아노를 간단히 실험을 하였습니다.


댓글()

[아두이노] 곰세마리멜로디(피에조부저)

IOT/아두이노|2019. 2. 5. 14:32

[아두이노] 곰세마리멜로디(피에조부저)



1. 악보


2. 멜로디 배열변수 만들기

음계+음길이

위의 악보에서 음계랑 음길이를 메모장에서 간단히 표시해 보자.

도도도도도
4 8 8 4 4
미솔솔미도
4 8 8 4 4
솔솔미솔솔미
8 8 4 8 8 4
도도도
4 4 2

솔솔미도
4 4 4 4 
솔솔솔
4 4 2
솔솔미도
4 4 4 4 
솔솔솔
4 4 2

솔솔미도
4 4 4 4
솔솔솔라솔
8 8 8 8 2
도솔도솔
4 4 4 4
미레도
4 4 2

5옥타브음계

5옥타브 음계 도레미파솔라시도 이것만 이용 합니다.

5옥타브 음계에서 벗어난 음계를 사용할 경우 위 사이트 가셔서 다른 옥타브 음계를 참조하셔서 사용하시면 됩니다. 귀찮은 분들은 전음계를 다 복사해와서 만드셔도 됩니다.

#define NOTE_C5  523   //도
#define NOTE_D5  587   //레
#define NOTE_E5  659   //미
#define NOTE_F5  698   //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047  //도

멜로디배열변수

계이름을 매크로변수(#define) 이름으로 배열변수에 저장하시면 됩니다. 그냥 도를 523으로 저장해도 되지만 이름으로 표현하시면 음계를 쉽게 구별할 수 있기 때문에 숫자보다는 변수 이름으로 만들어서 코딩하는걸 추천 드려요.

int melody[] = {
NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,               //도도도도도
NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,               //미솔솔미도
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,       //솔솔미솔솔미
NOTE_C5,NOTE_C5,NOTE_C5,                               //도도도
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,                       //솔솔미도
NOTE_G5,NOTE_G5,NOTE_G5,                               //솔솔솔
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,                       //솔솔미도
NOTE_G5,NOTE_G5,NOTE_G5,                               //솔솔솔
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,                       //솔솔미도
NOTE_G5,NOTE_G5,NOTE_G5,NOTE_A5,NOTE_G5,               //솔솔솔라솔
NOTE_C6,NOTE_G5,NOTE_C6,NOTE_G5,                       //도솔도솔
NOTE_E5,NOTE_D5,NOTE_C5                                //미레도
};

멜로디음길이배열변수

각 음계의 음길이가 각기 다르기 때문에 4분음표, 8분음표, 2분음표 등의 박자길이를 숫자로 해서 멜로디음길이배열변수를 만들어서 순차적으로 음계랑 음길이를 나중에 코딩에서 합쳐서 음을 만들어 내기 위해서 배열변수로 해서 음계과 음길이를 따로 만듭니다.

int noteDurations[]={
4,8,8,4,4,
4,8,8,4,4,
8,8,4,8,8,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
8,8,8,8,2,
4,4,4,4,
4,4,2
};

3. 회로도 구성


준비물 : 피에조부저 1개, 아두이노우노


지난 시간의 회로도와 동일합니다.


4. 코딩


지난 시간의 코딩에서 2번의 멜로디를 만든 배열변수를 추가를 하여 약간 코딩을 변경하시면 됩니다.

#define NOTE_C5  523    //도
#define NOTE_D5  587    //레
#define NOTE_E5  659    //미
#define NOTE_F5  698    //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047 //도


int tonepin = 12;


int melody[] = {
NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,
NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,
NOTE_C5,NOTE_C5,NOTE_C5,

NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,

NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,NOTE_A5,NOTE_G5,
NOTE_C6,NOTE_G5,NOTE_C6,NOTE_G5,
NOTE_E5,NOTE_D5,NOTE_C5
};

int noteDurations[]={
4,8,8,4,4,
4,8,8,4,4,
8,8,4,8,8,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
8,8,8,8,2,
4,4,4,4,
4,4,2
};


void setup() { 
} 
void loop() {
  for (int i = 0; i < 49; i++) {
    
    int Durations = 1000/noteDurations[i];    // 음계의 음길이 계산
    tone(tonepin, melody[i], Durations);    
    int pauseBetweenNotes = Durations *1.3 ;
    delay(pauseBetweenNotes);
    noTone(tonepin);
  }

}

이번에는 loop()에 반복수행하도록 한번 표현해봤습니다.

int Durations = 1000/noteDurations[i];    // 음계의 음길이 계산

음의 출력이 melody[i], noteDurations[i]로 매칭되어 음이 나오게 됩니다.

음길이 계산식이 원래 고정 1000/4여서 외부에 변수로 표현했지만 실제 곰세마리악보에서 보듯이 음길이가 다르기 때문에 melody[i], noteDurations[i]를 맞추기 위해서 for문 안에다 표현을 하였습니다.

그리고 보기 좋게 하기 위해서 따로 Durations로 변수를 만들어서 i번째 음길이를 가져와서 음길이를 계산한 표현을 하였습니다. 물론 tone(tonepin, melody[i], 1000/noteDurations[i])으로 표현해도 되겠지만 코딩은 각각 구분짓고 경계를 나누어 코딩하는게 시각적 가독성이 좋습니다. 그래서 일부로 이건 음길이 계산이다라고 별도로 한줄로 표현한 것이죠.

나머지 코딩부분은 이전 시간의 코딩과 동일합니다. 다른점은 멜로디 음계와 음길이를 별도로 배열변수에 저장했다는 것이죠.

5. 결과

실제로 회로도 구성이 쉬워서

라즈베리파에 설치한 아두이노 IDE 을 통해 바로 아두이노에 프로그램을 이식했습니다.


문제는 동영상 촬영했는데 소리는 원래 크게 들렸는데 녹화시 너무 작게 소리가 들어가서 실제 실험동영상은 올리는 것을 포기 했고 사진만 올립니다.

마무리


사실 tone(), noTone(), delay() 세함수를 제어하는 것일 뿐 별다른게 없습니다. 물론 음길이 공식이 1000/noteDurations와 Durations*1.3 으로 딜레이 시간을 준 공식을 추가된 것은 있지만 함수는 3개뿐이 사용 안했고 응용도 저 세개 함수만 사용한 실험이였습니다.

단지 멜로디를 만들기 위해서 배열변수로 코딩량만 늘어났을 뿐이죠. 코딩량은 늘어났지만 사실 멜로리 코딩은 위 악보에서 음계와 음길이를 추출하는 것은 쉽기 때문에 거기서 실제 배열변수로 만드는것은 어렵지 않게 2번 내용을 보시면 하실 수 있을거라고 생각됩니다.

예전에 자전거 멜로디를 만들어서 실험했지만 최근 음악 관련 포스팅을 할때 곰세마리악보를 이용해서 이번 아두이노에서도 곰세마리악보를 이용해 멜로디를 만들어 봤네요.

한번 다른 악보를 보고 멜로디를 만들어보세요.


댓글()

[아두이노] 피에조부저 제어

IOT/아두이노|2019. 2. 4. 14:54

[아두이노] 피에조부저 제어



1. 회로도 구성


  • 준비물 : 피에조부조 1개, 아두이노우노
  • 내용 : 12번 핀을 주파수 출력핀으로 사용하여 멜로디를 만들어 보자.

회로 구성은 보는것과 같이 간단하다.



2. 코딩


  • 출처 : https://www.arduino.cc/en/Tutorial/toneMelody
  • 함수 : tone(핀번호, 주파수, 출력시간), noTone(핀번호), delay(시간값)
  • 내용 : 주파수 값은 해당 사이트에서 제공되는 값으로 도레미파솔라시도 출력해보자.

#define NOTE_C5  523   //도 (5옥타브 음계 데이터)
#define NOTE_D5  587   //레
#define NOTE_E5  659   //미
#define NOTE_F5  698   //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047 //도

int tonepin = 12;
int melody[] = { NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6 }; //도레미파솔라시도
int noteDurations = 1000 / 4; //톤길이(4분음표)

void setup() {
  for (int i = 0; i < 8; i++) {
    tone(tonepin, melody[i], noteDurations);
    int pauseBetweenNotes = noteDurations * 1.30;
    delay(pauseBetweenNotes);
    noTone(tonepin);
  }
}
void loop() {
}

첫번째 :
#define은 매크로 변수로 NOTE_C5란 변수명은 523이란 숫자를 지칭한다. 매크로상수라고만 이해하시면 된다. 523의 별명으로 NOTE_C5라고 불리운다라고 생각하면 될듯요. 각 주파수 값을 숫자로 이게 도인지 솔일지 모르잖아요. 그걸 별명으로 이름을 지어주는 거라고 생각하시면 됩니다.

두번째 :
setup()함수에 로직을 짠 이유는 loop()함수에다가 코딩해도 됩니다. 그런데 멜로디를 무한 반복해서 듣는건 좀 그래서 처음 한번만 수행하는 setup()함수에 1번만 멜로디를 듣도로 실험하기 위해서 setup()에 코딩했습니다. setup()에 코딩해야하는건 아니고 그냥 1회만 멜로디 출력시키기 위해서 이쪽에다 코딩했을뿐 의미가 있는건 아닙니다.

세번째 :
tone()과 notone()은 확실히 음과 음사이를 끊어주기 위한 것으로 하나의 음계의 소리가 귀로 들을때 들리는 음의 구별하기 위해서 처음과 끝을 나타내는 위치로 생각하시면 됩니다.
그리고 그 사이 delay() 함수의 길이가 1.3배함으로 음과 음의소리가 음과 음의길이사이의 딜레이가 발생시키게 합니다. 이걸 구별해서 이해하실려면 1.3대신에 더 큰 숫자를 넣고 돌려보세요. 음을 실제로 들어보면 왜 delay()함수를 넣은지 이해가 되실꺼에요.

melody[] 변수는 배열변수로 하나의 이름으로 여러개의 저장소를 갖는데 int melody[8]로 선언되면 8개의 그릇을 갖게 됩니다. 각 그릇은 다른 값을 저장이 가능합니다. 배열변수는 안에 숫자로 위치를 나타냅니다.
가령 melody[0]=523 , melody[1]=587, .... 이렇게 각 그릇해 주파수 값을 저장되어 있는 것이죠.

for문으로 0~7까지 루프를 돌잖아요.

tone(tonepin, melody[0], noteDurations);

이것은

tone(12, 523, 1000/4);

되는 것이죠.

하나의 음계의 동작은 도라는 음계를 4분음표이고 그걸 피에조부조로 들리게 한다면 아래와 같이 수행되게 됩니다.

tone(핀번호, 523, 1000/4); //도 4분음표
int pauseBetweenNotes = (1000/4) * 1.30; //4분음표 딜레이시간
delay(pauseBetweenNotes);
noTone(핀번호); 

여기서 도레미파솔라시도 총 8번 반복 수행해서 각 melody[i]값이 음계가 출력되는 것이죠.

3. 결과


마무리


오늘은 tone(), noTone()의 함수에 대해 배웠습니다. 이걸 통해서 피에조부저를 통해 멜로디를 만들었습니다.
그러면 한번 곰세마리를 악보를 보고 멜로디를 만들어 보세요.

참고할것은 여기서, 도레미파솔라시도는 4분음표로 고정해서 멜로디를 만들었습니다.
하지만 모든 음악이 4분음표만 있는게 아니죠.

힌트는,
1000/4은 4분음표면 1000/8은 8분음표겠죠. 1000/2은 2분음표이고요.
도레미파솔라시도의 4분음표일때
int noteDurations[]={4,4,4,4,4,4,4,4}; 이렇게 하면 음표 배열을 만들어지겠죠.
Durations=1000/noteDurations[i]으로 하면 해당 음계에 음표를 만들 수 있겠죠.

한번 곰세마리를 만들어 보세요.
내일 곰세마리 멜로디를 올리도 할께요.


댓글()