[아두이노] 사운드센서로 그래픽 이퀄라이저 표현

IOT/아두이노|2019. 4. 7. 09:00

[아두이노] 사운드센서로 그래픽 이퀄라이저 표현



어제 마이크사운드 감지센서를 통해서 센서의 값의 초기값 조정하는 것과 읽는 법을 배웠습니다. 오늘은 마이크사운드 감시센서을 이용하여 소리를 NeoPixel로 출력하는 실험을 해보도록 하겠습니다. "아두이노 이퀄라이저" 키워드로 찾으시면 화려한 표현들을 많이 보실 수 있을거에요. 오늘 포스팅은 그런 화려한 표현이 아닌 간단히 어떻게 소리를 NeoPixel로 표현을 하는지에 대한 원리를 이해하는 시간으로 채우겠습니다. 화려한 그래픽 이퀄라이저를 상상했다면 아마 실망하실지 모르겠군요.

1. 마이크사운드 감지센서와 3색LED




3색 LED에 대한 설명은 아래 링크된 포스트로 가셔서 잠깐 살펴보세요.
[아두이노] 3색 LED 제어



마이크사운드 감지센서는 어제 포스트인 아래 링크로 다시 복습를 해주세요.
[아두이노] 마이크사운드 감지센서 제어(아두이노)

입력은 마이크르사운드 감지센서를 사용하고 출력은 NeoPixel이 없는 관계로 비슷한 3색 LED를 이용하여 실험이 이루어집니다.

2. 회로도 구성


  • 준비물 : 마이크사운드 감지센서 1개, 3색 LED 1개, 저항 220옴 3개, 아두이노우노
  • 내용 : 사운센서랑 3색 LED를 사용할 핀을 원하는 위치에 배치해 보자.

가상회로도 이미지에서 저항은 실수 1k옴으로 그려졌네요. 혼동하시면 안되고 정상적으로 220옴을 실제로 사용하셔야 합니다. 이미지의 저항색깔을 보시고 혼동하시면 안됩니다.


RGB 색상핀은 아날로그 출력을 해야하기 때문에 PWM핀들 중에 9,10,11번 핀을 선택했습니다. 그리고 마이크사운드 감지센서의 값을 읽는 아날로그 핀은 이전시간과 동일한 A0핀을 선택했습니다.

3. 코딩


  • 사용함수 : analogRead(), PinMode(), analogWrite(), map(), randomSeed(), random()
  • 내용 : 마이크사운드 감지센서의 소리의 값을 기준으로 RGB 색상을 만들어 그래픽 이퀄라이저 효과를 표현하자.

[ 기본 소스 ] : 마이크사운드 감지센서를 이용한 소리의 최소값과 최대값 출력

int m_min = 26;
int m_max = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  int SoundSensor =analogRead(A0);
    if(SoundSensor<m_min) m_min=SoundSensor;
    if(SoundSensor>m_max) m_max=SoundSensor;
    
    Serial.print(m_min);    
    Serial.print("  :  ");
  Serial.println(m_max);    
  delay(50); 
}

이전 시간에 실험한 소스입니다. 이 소스를 기반으로 접근해 보겠습니다.

우선 RGB핀을 사용하기 때문에 RGB핀 변수를 만들어야 겠죠.

const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

그 다음에는 RGB핀들은 출력모드입니다. 그러면 setup()함수에서 "출력모드로 사용할꺼야!" 하고 선언해야겠죠.

void setup()
{
  Serial.begin(9600);
 
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

이제 소리를 어떤 기준으로 해서 색을 만들면 좋을지 생각해보세요. 마이크사운드 감지센서의 아날로그 신호 0~1023 값을 아두이노가 읽는다고 했죠. 그러면 0~1023값을 쪼개서 색으로 나타내면 되겠다고 처음에 다들 여기서 부터 출발합니다. 여기서, RGB핀 출력은 PWM핀으로 아날로그 0~255 출력을 낼 수 있습니다. 0(0V)이고 255(5V)입니다. 그러면 마이크사운드 감지센서에서 읽은 0~1023 값을 아날로그 출력 0~255값에 매핑 시켜서 출력시키면 되겠다고 생각 하실꺼에요.

 int SoundSenser =analogRead(A0);
 int SoundColor = map(SoundSenser,0,1023,0,255);

map()함수는 다시 복습을 하면

  • map(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : 입력최소~최대범위에서 입력값이 출력 최소~최대범위에 어느정도 위치인지 그 출력위치값을 반환합니다.

예) map(입력값,0,100,0,10) 이면, 입력값이 10일때 출력값는 1이 됩니다. 입력값이 20일때 출력값은 2가 출력됩니다. 즉, 이말은 입력범위를 출력범위에 맞춰서 출력된다고 생각하시면 됩니다. 일정한 간격 비율에 맞춰서 출력값에 매칭 된다고 생각하면 될듯요. 0~10사이의 값은 1로 매칭되고 11~20사이의 값은 2로 매칭이 되고 이렇게 생각하시면 될 듯 하네요. 입력범위에 대한 입력값의 위치가 출력범위에서 어느정도의 위치가 출력위치인지 그 출력위치의 값을 반환해주는 함수라고 생각하시면 됩니다.

 Serial.println(SoundSenser);
 analogWrite(RedPin,random(SoundColor));
 analogWrite(GreenPin,random(SoundColor));
 analogWrite(BluePin,random(SoundColor));

우선 소리값을 그냥 color 값으로 출력한다면 소리값에 0~255사의 값이 RGB 색의 출력된 값이 되고 출력명령은 analogWrite()함수로 color를 출력하게 됩니다. 이렇게 하면 코딩이 끝났다고 생각하실꺼에요. 한번 돌려보세요 어떤 현상이 발생하는지요. 마이크사운드센서에 소리를 입력하기도 전에 3색 LED를 막 깜박일꺼에요.

그 이유가 뭘까요. 지난시간에 공부하셨으면 쉽게 찾을 수 있을꺼에요. 지난시간에 마이크사운드 감지센서는 초기값으로 일정한 전류가 발생한다고 했죠. 같은 종류라도 센서라도 초기의 센서에 흐르는 전류의 초기값은 제각각 입니다. 제가 사용하는 Sensor는 처음 46이 초기값이였고, 가변저항기를 돌려서 26정도로 맞췄습니다. 여기서 아무런 소리를 입력하기 전 초기 상태에서 이미 소리값이 일정 수치가 읽어지니깐 자연히 그 초기값의 색이 RGB LED에 출력이 되는 것이죠 제 경우 초기값이 26이라면 26에 대한 색이 출력되더군요.

처음 마이크 상태는 0에서 출발해야 하는데 그렇게 안되겠죠. 그럼 그렇게 0에서 출발하도록 만들어줘야 합니다. 바로 map()함수를 수정하시면 됩니다.

 int SoundColor = map(SoundSenser,26,1023,0,255);

입력 최소값을 26으로 잡아주면 간단히 해결됩니다. 그런데 두번째 문제가 발생합니다. 과연 뭘까요. 어제 포스트를 읽어봤다면 찾을 수 있을꺼에요. 소리가 입력되고 다시 입력이 안되면 전기신호를 감소합니다. 중요한 것은 소리가 입력될때는 전기신호가 커졌다가 입력이 안될때는 전기신호가 작아지는데 초기값보다 마이너스(-)로 빠져버리는 현상이 발생한다고 했죠. 그러면 어떻게 될까요. 그 이상한 값에 의해서 색이 출력되어버립니다. 최소값 문제가 발생합니다.

그걸 해결하기 위해서 어제 최소값 구하는 로직을 이용할꺼에요.

if(SoundSensor<26) SoundSensor=26;

만약에 26보다 작은 전기신호가 온다면 26에 수렴되게 만들면 됩니다. 그리고 또 어떤 문제가 발생할까요. 아두이노에서 사용되는 마이크사운드 감지센서를 아주 싸고 질이 떨어집니다. 결론은 소리감지에 좀 문제가 있습니다. 어느정도 큰소리라고 생각했지만 측정되는 수치는 그렇게 높지 않습니다. 거의 마이크를 두둘겨야 비로소 일정 수치까지 올라갑니다. 말하자면 대부분의 소리가 낮은 전기신호값을 가지고 있으면 일부 소리만 큰전기신호값으로 입력으로 들어옵니다. 결론은 거의 동작을 안하는 것처럼 보이고 색상도 아주 낮게 보입니다. 그래픽 이퀄라이저 효과를 뚜렷하게 표현하기 어렵다는 것이죠.

해결책으로는 낮은 값을 기준으로 색의 값을 재배열 하면 됩니다.

if(SoundSensor>300) SoundSensor=300;

이렇게 300이상의 소리는 그냥 300에 수렴하게 만들면 됩니다. 대충 아래 그래프처럼 입력값의 범위를 강제적으로 정하면 됩니다. 가로축는 마이크사운드 감지센서의 측정값이고 세로축은 소리입력허용 수치입니다. 26~300사이의 값으로 소리입력범위가 잡으시면 됩니다.


그러면, map()함수는 어떻게 될까요.

 int SoundColor = map(SoundSenser,26,300,0,255);

이렇게 해서 26~300사이의 값을 입력범위로 해서 0~255의 색상값으로 출력하겠다고 표현하시면 됩니다.
여기서, 어제 배웠던 최소값과 최대값 구한 로직을 기본소스로 우선 보여준 이유가 이 로직으로 이런 2가지의 문제를 해결하기 위해서 입니다.

이렇게하면 소리에 대한 밝기만 표현되기 때문에 그래픽 이퀄라이저라고 보기 어렵습니다. 그래서 밝기와 더불어 다양한 색을 만들어보는 시도를 해보겠습니다. 그 방법으로 random()함수를 사용하겠습니다.

void setup()
{
  randomSeed(analogRead(A1));
}
void loop(){
    random(SoundColor);
}

A0은 마이크사운드 감지센서로 사용되니깐 A1은 랜덤함수 초기값으로 세팅하겠습니다. 여기서 아날로그핀값을 읽어오기만 하는게 난수초기세팅인 이유는 뭘까요. 그 이유는 아두이노는 핀마다 미세한 전류가 자체적으로 흐르고 있습니다. 사용을 안하더라고 핀마다 작게 전류가 흘러서 그 전류는 미세하지만 랜덤한 전류의 양을 가지고 있어서 그 값을 기준으로 난수를 세팅하면 랜덤값을 추출할 수 있습니다.
그리고 random(255)함수면 255이 max값으로 임의의 난수를 만들어 냅니다. 이 값을 RGB 마다 random()함수로 색의 난수값을 만들어내면 소리에 대한 다양한 색을 만들 수 있겠죠.

종합해보면.

const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

void setup()
{
  Serial.begin(9600);
  randomSeed(analogRead(A1));
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  
  Serial.println(SoundColor);
  analogWrite(RedPin,random(SoundColor));
  analogWrite(GreenPin,random(SoundColor));
  analogWrite(BluePin,random(SoundColor));
    
  delay(50); 
}

4. 결과



음악으로 실험해야하는데 음악은 저작권 때문에 단지 손으로 마이크사운드 감지센서에 자극을 줘서 출력실험을 했네요. 보면은 터치센서인줄 착각할 수 있는 장면이네요.

참고로 위 소스대로 하시면 그렇게까지 그래픽 이퀄라이저 효과를 얻을 수 없습니다. 결함이 있는 소스입니다. 그 이유가 뭘까요. 바로 random()함수를 단순히 측정한 소리값을 max값으로 넣었기 때문입니다. 이렇게 표현하면은 소리에 따라 다양한 색을 만들어 내기는 합니다. 하지만 300의 소리가 입력되었어도 100이 나올수 있고 150의 소리가 입력되어도 100이 나올 수 있다는 것이죠. 이말은 색상을 일정한 패턴 범위에 맞추지 못했다는 결함적 소스입니다.

그런데 왜 수정을 안하고 여기서 멈추었냐면 이제부터서 여러분들이 수정해 나갈 차례입니다. 기본 베이스는 다 주어졌습니다. 소리의 초기상태를 읽을 수 있고 최소와 최대값을 정할 수 있게 되었고 map()으로 색의 값의 범위를 정할 수 있게 되었습니다. random()함수로 색을 랜덤하게 만들어 낼 수 있게 되었습니다. 여러분들은 이제 소리를 좀 더 디테일적으로 나누는 작업과 그 나누었을 때 색을 랜덤함수로 범위를 정할지 아니면 고정된 색값으로 하고 밝기만 변화를 줄지의 선택은 여러분의 몫입니다.

참고로, 이렇게 코딩을 해도 실제 돌려보면 색이 화려하지 않고 낮은 밝기와 색값으로 출력됩니다. 그것은 바로 마이크사운드 감지센서의 문제입니다. 좀 큰소리를 말해도 실질적으로 측정되는 수치는 그렇게 높지 않습니다. 그렇기 때문에 미세한 1의 변화에도 색의 밝기값을 좀 더 큰값 범위를 기준으로 해서 정해보시는 것을 추천드려요.

5. 가상시뮬레이터에서 느낌만 실험


그냥 실제로만 하면 뭔가 아쉬울 것 같아서 느낌만 살펴서 가상시뮬레이터로 실험해 해보세요. 위에 공개회로도에 링크 걸렸으니깐 돌려만 보세요.

1) 가변저항기와 3색 LED


const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

void setup()
{
  Serial.begin(9600);
  randomSeed(analogRead(A1));
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  
  Serial.println(SoundColor);
  analogWrite(RedPin,random(SoundColor));
  analogWrite(GreenPin,random(SoundColor));
  analogWrite(BluePin,random(SoundColor));
    
  delay(50); 
}

2) 가변저항기와 NeoPixel


#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, neopixelPin, NEO_GRB + NEO_KHZ800);
 
void setup()
{
  neopixel.begin();   
  randomSeed(analogRead(A1));
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  neopixel.setPixelColor(0, random(SoundColor), random(SoundColor), random(SoundColor));    
  neopixel.show();
  delay(50);    
  neopixel.clear(); 
}

3) 두가지 실험 결과

위 3색 LED와 NeoPixel을 가변저항기가 마이크사운드 감지센서라고 상상하고 실험한 영상입니다.


마무리


하다보니깐 길어졌네요. 원래는 추가적으로 NeoPixl 바 모양 향태로 음악을 들을때 사운드 그래픽 바 모양을 아실꺼에요. 그것도 추가 설명할려고 했는데 그건 못하게 되었네요. 내일 또 이여서 하면 너무 길게 연장 될 것 같아서 여러분들의 상상력으로 표현을 해보세요.

혹시, 사운드 그래픽 바를로 출력하고자 하고자 한다면 참고해주세요. NeoPixel을 예전 포스팅에 어떻게 표현을 했었죠. 배열 행태로 표현했었습니다. 그 배열을 사운드 그래픽 바의 위치라고 상상하고 소리의 크기에 따라서 배열로 몇번째 배열까지 불이 들어오게 할건지 그 간격만 정하시면 됩니다. 그리고 해당 NeoPixel의 색 값은 random()함수로 표현하시거나 고정값을 하셔도 됩니다. 쉽게 말해서, 소리가 1~10까지 있으면 3이라는 신호값이 발생하면은 NeoPixel를 3번째까지 불이 들어오게 하면 됩니다. 이렇게 소리의 크기 값에 neoPixel의 위치를 정하면 됩니다. 한번 연구를 해보세요.

오늘도 어떻게 표현하면 더 자연스럽고 화려할지를 상상의 나래를 펼쳐 보세요.


댓글()