스파크펀 일렉트렛 마이크 브레이크아웃 (SparkFun Electret Microphone Breakout)
1
스파크펀 모노 오디오 앰프 브레이크아웃 (SparkFun Mono Audio Amp Breakout)
1
스피커: 0.25W, 8옴
1
브레드보드 (일반용)
1
5mm 빨간색 LED
3
330옴 저항
3
점퍼 와이어 (일반용)
1
필요 도구 및 장비
납땜 인두 (일반용)
앱 및 온라인 서비스
BitVoicer Server 1.0
프로젝트 소개
이전 프로젝트에서 아두이노 보드와 BitVoicer Server로 몇 개의 LED를 제어하는 방법을 보여드렸습니다. 이번 프로젝트에서는 좀 더 복잡한 작업을 다룹니다. 아두이노 Due의 디지털-아날로그 변환기(DAC)를 사용해 음성 합성도 구현합니다. 아두이노 Due가 없다면 다른 아두이노 보드도 사용 가능하지만, 외부 DAC와 추가 코드가 필요합니다(BVSSpeaker 라이브러리는 외부 DAC를 지원하지 않습니다).
아래 영상에서 아두이노가 짧은 곡을 연주하고 LED가 피아노 건반처럼 깜빡이는 것을 볼 수 있습니다. 피아노 실력이 부족해 죄송합니다만 최선을 다했습니다 :) LED는 실제 C, D, E 건반과 같은 순서와 타이밍으로 깜빡이므로 피아노가 있다면 LED를 따라 같은 곡을 연주할 수 있습니다. 없어진 오래된 소매상(Mappin)의 징글입니다.
음성 명령을 LED 동작과 합성 음성으로 변환하는 절차:
스파크펀 일렉트렛 브레이크아웃 보드로 오디오 파형 포착 및 증폭
증폭된 신호가 아두이노 ADC로 디지털화 및 버퍼링
오디오 샘플이 아두이노 시리얼 포트로 BitVoicer Server에 스트리밍
BitVoicer Server가 오디오 스트림 처리 및 음성 인식
인식된 음성이 미리 정의된 명령에 매핑되어 아두이노로 전송. 음성 합성 명령 포함 시 BitVoicer Server가 오디오 스트림 준비 후 아두이노로 전송
아두이노가 명령 식별 후 적절한 액션 수행. 오디오 스트림 수신 시 BVSSpeaker 클래스에 큐잉되어 Due DAC와 DMA로 재생
스파크펀 모노 오디오 앰프가 DAC 신호 증폭해 8옴 스피커 구동
재료 목록
아두이노 Due: ~$50.00
스파크펀 일렉트렛 마이크 브레이크아웃: $7.95
스파크펀 모노 오디오 앰프 브레이크아웃: $7.95
BitVoicer Server 1.0: $9.90
8옴 스피커: ~$2.00
브레드보드: ~$10.00
LED 3개: ~$1.00
330옴 저항 3개: ~$0.75
점퍼 와이어: ~$0.50
1단계: 배선
아래 사진과 같이 아두이노와 브레드보드에 부품을 배선합니다. 스피커가 진동이 심해 작은 고무 패드를 깔지 않으면 오디오 품질이 크게 저하됩니다.
이전 프로젝트와 작지만 중요한 차이점이 있습니다. 대부분의 아두이노 보드는 5V로 동작하지만 Due는 3.3V로 동작합니다. 스파크펀 일렉트렛 브레이크아웃을 3.3V로 구동할 때 결과가 더 좋아 5V 아두이노 보드 사용 시 3.3V 핀과 AREF 핀 사이에 점퍼 연결을 권장합니다. Due는 이미 3.3V 아날로그 레퍼런스를 사용하므로 AREF 핀 점퍼 불필요. 사실 Due의 AREF 핀은 저항 브리지를 통해 마이크로컨트롤러와 연결되어 있어 AREF 핀 사용 시 PCB에서 BR1 저항을 제거해야 합니다.
2단계: 아두이노에 코드 업로드
아래 코드를 아두이노에 업로드하세요. 편의를 위해 아두이노 스케치는 이 게시물 하단 첨부 파일 섹션에도 있습니다. 업로드 전 BitVoicer Server 라이브러리를 아두이노 IDE에 제대로 설치해야 합니다(.zip 라이브러리 가져오기).
아두이노 스케치: BVS_Demo2.ino
이 스케치는 7가지 주요 부분으로 구성됩니다:
라이브러리 참조 및 변수 선언: 처음 4줄은 BVSP, BVSMic, BVSSpeaker, DAC 라이브러리 참조. 이 라이브러리들은 BitSophia에서 제공하며 BitVoicer Server 설치 폴더에서 찾을 수 있습니다. DAC 라이브러리는 BVSSpeaker 라이브러리 참조 시 자동 포함. 나머지 줄은 스케치 전반에서 사용되는 상수와 변수 선언. BVSP 클래스는 BitVoicer Server와 통신, BVSMic 클래스는 오디오 샘플 캡처 및 저장, BVSSpeaker 클래스는 Due DAC로 오디오 재생 담당.
Setup 함수: 핀 모드 및 초기 상태 설정, 시리얼 통신 초기화, BVSP/BVSMic/BVSSpeaker 클래스 초기화. BVSP 클래스의 frameReceived, modeChanged, streamReceived 이벤트 핸들러(함수 포인터) 설정.
Loop 함수: 5가지 중요 동작 수행: 서버에 상태 정보 요청(keepAlive()), 서버 전송 데이터 확인 및 처리(receive()), 오디오 스트림 녹음 및 전송 제어(isSREAvailable(), startRecording(), stopRecording(), sendStream()), BVSSpeaker 클래스에 큐잉된 오디오 샘플 재생(play()), playNextLEDNote() 호출로 playLEDNotes 명령 수신 후 LED 깜빡임 제어.
BVSP_frameReceived 함수: receive()가 완전한 프레임 수신 식별 시마다 호출. BitVoicer Server에서 전송된 명령 실행. LED 제어 명령은 2바이트: 첫 바이트=핀 번호, 두 번째 바이트=핀 값. analogWrite()로 핀 값 설정. playLEDNotes 명령(Byte 타입) 수신 시 playLEDNotes를 true로 설정하고 현재 시간 기록. 이 시간은 playNextLEDNote 함수에서 LED와 노래 동기화에 사용.
BVSP_modeChanged 함수: receive()가 아웃바운드 방향(서버→아두이노) 모드 변경 식별 시 호출. BitVoicer Server는 프레임 데이터나 오디오 스트림을 아두이노로 전송. 통신 모드 전환 전 신호 전송. BVSP 클래스가 이 신호 식별해 modeChanged 이벤트 발생. BVSP_modeChanged 함수에서 통신이 스트림 모드에서 프레임 모드로 전환 감지 시 오디오 종료로 판단, BVSSpeaker 클래스에 오디오 샘플 재생 중지 지시.
BVSP_streamReceived 함수: receive()가 오디오 샘플 수신 식별 시마다 호출. 샘플 가져와 BVSSpeaker 클래스에 큐잉해 play() 함수로 재생.
playNextLEDNote 함수: BVSP_frameReceived가 playLEDNotes 명령 식별 시에만 실행. BitVoicer Server에서 전송된 오디오와 LED 동기화 제어. 올바른 타이밍을 위해 Sonic Visualizer 사용. 이 무료 소프트웨어로 오디오 파형 확인해 피아노 건반 눌린 시점 파악. 타임라인도 보여줘 밀리초 타이밍 계산 가능. 우회적인 트릭이지만 효과적. 오디오 스트림 분석해 해당 LED 점등하는 방식도 가능하겠지만 제 능력 밖입니다.
3단계: BitVoicer Server 솔루션 객체 가져오기
이제 BitVoicer Server를 아두이노와 연동하도록 설정합니다. BitVoicer Server에는 4가지 주요 솔루션 객체가 있습니다: Locations, Devices, BinaryData, Voice Schemas.
Locations: 디바이스 설치 물리적 위치. 제 경우 Home 위치 생성.
Devices: BitVoicer Server 클라이언트. Mixed 디바이스 생성, 이름 ArduinoDUE, 통신 설정 입력. 중요: 아두이노 Due에도 BitVoicer Server가 스트리밍할 오디오 샘플 저장용 메모리가 적음. 대역폭 제한하지 않으면 훨씬 큰 버퍼 필요. 버퍼 오버플로 발생해 통신 설정에서 Data Rate를 8000 samples/초로 제한.
BinaryData: BitVoicer Server가 클라이언트 디바이스로 보낼 수 있는 명령 타입. 실제로는 명령에 연결할 수 있는 바이트 배열. 음성 인식 시 타겟 디바이스로 바이트 배열 전송. 핀 값마다 BinaryData 객체 생성: ArduinoDUEGreenLedOn, ArduinoDUEGreenLedOff 등. 총 18개 BinaryData 객체 생성. 아래 VoiceSchema.sof 파일에서 객체 다운로드 및 가져오기 권장.
Voice Schemas: 모든 것이 통합되는 곳. 인식할 문장과 실행할 명령 정의. 문장당 여러 명령 정의 가능, 실행 순서와 명령 간 지연 시간도 정의. 영상에서 본 액션 시퀀스를 이렇게 구현.
Voice Schema의 문장 중 하나는 "play a little song". 이 문장엔 두 명령 포함: 첫 번째 명령은 다음 명령이 오디오 스트림임을 나타내는 바이트 전송. 아두이노는 오디오 전송 중 "LED 연주" 시작. 오디오는 직접 녹음한 피아노 징글, 두 번째 명령의 오디오 소스로 설정. BitVoicer Server는 8비트 모노 PCM 오디오(초당 8000 샘플)만 지원하므로 오디오 파일 변환 시 온라인 변환 도구 추천.
아래 파일에서 이 프로젝트에 사용한 모든 솔루션 객체 가져오기 가능(솔루션 객체 가져오기). 하나는 Due 디바이스, 다른 하나는 Voice Schema와 명령 포함.
솔루션 객체 파일:
Device.sof
VoiceSchema.sof
4단계: 결론
자, 다 됐습니다! 모든 전원 켜고 영상에서 본 것과 같은 작업 수행 가능.
이전 프로젝트처럼 BitVoicer Server Manager에서 아두이노 디바이스를 활성화해 음성 인식 시작. 활성화되자마자 아두이노가 사용 가능한 음성 인식 엔진 식별하고 BitVoicer Server로 오디오 스트리밍 시작. 다만 이제 BitVoicer Server에서 아두이노로 오디오 스트리밍될 때 아두이노 RX LED 활동이 훨씬 많이 보입니다.
다음 프로젝트에선 좀 더 야심차게 진행하려 합니다. 아두이노 하나에 와이파이 통신 추가하고 다른 아두이노 두 대를 음성으로 통합 제어. 일종의 게임 구상 중. 제안 환영합니다!
코드
아두이노 스케치 (아두이노)
#include <BVSP.h>
#include <BVSMic.h>
#include <BVSSpeaker.h>
#include <DAC.h>
// 오디오 캡처용 아두이노 핀 정의
#define BVSM_AUDIO_INPUT 7
// LED 핀 정의
#define RED_LED_PIN 6
#define YELLOW_LED_PIN 9
#define GREEN_LED_PIN 10
// BVSP.begin 함수에 전달할 상수 정의
const unsigned long STATUS_REQUEST_TIMEOUT = 3000;
const unsigned long STATUS_REQUEST_INTERVAL = 4000;
// 마이크 오디오 버퍼 크기 정의
const int MIC_BUFFER_SIZE = 64;
// 스피커 오디오 버퍼 크기 정의
const int SPEAKER_BUFFER_SIZE = 128;
// 수신 버퍼 크기 정의
const int RECEIVE_BUFFER_SIZE = 2;
// BVSP 클래스 전역 인스턴스 초기화
BVSP bvsp = BVSP();
// BVSMic 클래스 전역 인스턴스 초기화
BVSMic bvsm = BVSMic();
// BVSSpeaker 클래스 전역 인스턴스 초기화
BVSSpeaker bvss = BVSSpeaker();
// BVSMic 클래스에서 녹음된 샘플 읽기용 버퍼
byte micBuffer[MIC_BUFFER_SIZE];
// BVSSpeaker 클래스에 오디오 샘플 쓰기용 버퍼
byte speakerBuffer[SPEAKER_BUFFER_SIZE];
// BitVoicer Server에서 전송된 명령 읽기용 버퍼
// 바이트 0 = 핀 번호
// 바이트 1 = 핀 값
byte receiveBuffer[RECEIVE_BUFFER_SIZE];
// "LED 노트" 재생 제어용 변수
// 이 노트들은 BitVoicer Server에서 스트리밍되는 노래와 함께 재생됨
bool playLEDNotes = false;
unsigned int playStartTime = 0;
void setup() {
// 핀 모드 설정
pinMode(RED_LED_PIN, OUTPUT);
pinMode(YELLOW_LED_PIN, OUTPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
// 모든 LED 초기 상태 설정
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(YELLOW_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
// 115200 bps로 시리얼 통신 시작
Serial.begin(115200);
// 통신용 아두이노 시리얼 포트, 상태 요청 타임아웃, 상태 요청 간격 설정
bvsp.begin(Serial, STATUS_REQUEST_TIMEOUT, STATUS_REQUEST_INTERVAL);
// frameReceived 이벤트 핸들러 함수 정의
bvsp.frameReceived = BVSP_frameReceived;
// modeChanged 이벤트 핸들러 함수 정의
bvsp.modeChanged = BVSP_modeChanged;
// streamReceived 이벤트 핸들러 함수 정의
bvsp.streamReceived = BVSP_streamReceived;
// BVSMic 클래스 타이머 준비
bvsm.begin();
// BVSSpeaker 클래스가 사용할 DAC 설정
bvss.begin(DAC);
}
void loop() {
// 상태 요청 간격 경과 시 BitVoicer Server에 상태 요청 전송
bvsp.keepAlive();
// 시리얼 포트 버퍼에 데이터 있으면 내용 처리
bvsp.receive();
// 사용 가능한 SRE(음성 인식 엔진) 있는지 확인. 있으면 녹음 시작
if (bvsp.isSREAvailable()) {
// BVSMic 클래스가 녹음 중이 아니면 오디오 입력 설정 후 녹음 시작
if (!bvsm.isRecording) {
bvsm.setAudioInput(BVSM_AUDIO_INPUT, DEFAULT);
bvsm.startRecording();
}
// BVSMic 클래스에 사용 가능한 샘플 있으면
if (bvsm.available) {
// 인바운드 모드가 STREAM_MODE인지 확인 후 스트림 전송
if (bvsp.inboundMode == FRAMED_MODE)
bvsp.setInboundMode(STREAM_MODE);
// BVSMic 클래스에서 오디오 샘플 읽기
int bytesRead = bvsm.read(micBuffer, MIC_BUFFER_SIZE);
// BitVoicer Server로 오디오 스트림 전송
bvsp.sendStream(micBuffer, bytesRead);
}
} else {
// 사용 가능한 SRE 없음. BVSMic 클래스가 녹음 중이면 중지
if (bvsm.isRecording)
bvsm.stopRecording();
}
// BVSSpeaker 클래스 내부 버퍼에 있는 모든 오디오 샘플 재생
// 이 샘플들은 BVSP_streamReceived 이벤트 핸들러에서 기록됨
// 내부 버퍼에 샘플 없으면 아무것도 재생 안 함
bvss.play();
// playLEDNotes가 true로 설정된 경우
// 음악과 함께 "LED 노트" 재생
if (playLEDNotes)
playNextLEDNote();
}
// frameReceived 이벤트 처리
void BVSP_frameReceived(byte dataType, int payloadSize) {
// 수신 프레임이 바이너리 데이터 포함 확인
// 0x07 = 바이너리 데이터 (바이트 배열)
if (dataType == DATA_TYPE_BINARY) {
// 2바이트 수신 시 명령 처리
if (bvsp.getReceivedBytes(receiveBuffer, RECEIVE_BUFFER_SIZE) == RECEIVE_BUFFER_SIZE) {
analogWrite(receiveBuffer[0], receiveBuffer[1]);
}
}
// 수신 프레임이 바이트 데이터 타입 확인
// 0x01 = 바이트 데이터 타입
else if (dataType == DATA_TYPE_BYTE) {
// 수신 바이트 값이 255면 playLEDNotes 설정 및 현재 시간 기록
if (bvsp.getReceivedByte() == 255) {
playLEDNotes = true;
playStartTime = millis();
}
}
}
// modeChanged 이벤트 처리
void BVSP_modeChanged() {
// 아웃바운드 모드(서버→디바이스)가 FRAMED_MODE로 전환되면
// 더 이상 오디오 스트림 수신 안 됨.
// BVSSpeaker 클래스에 내부 버퍼 비워질 때 재생 중지 지시
if (bvsp.outboundMode == FRAMED_MODE)
bvss.finishPlaying();
}
// streamReceived 이벤트 처리
void BVSP_streamReceived(int size) {
// BVSP 클래스에서 수신 스트림 가져오기
int bytesRead = bvsp.getReceivedStream(speakerBuffer, SPEAKER_BUFFER_SIZE);
// 수신 스트림을 재생 큐에 추가
bvss.enqueue(speakerBuffer, bytesRead);
}
// playStartTime부터 현재까지 경과 시간에 따라 적절한 LED 점등
// 타이밍은 음악과 동기화됨
void playNextLEDNote() {
// playStartTime과 현재 시간 간 경과 시간 계산
unsigned long elapsed = millis() - playStartTime;
// 모든 LED 소등
allLEDsOff();
// 마지막 노트 재생 완료
// 마지막 LED 소등 후 LED 노트 재생 중지
if (elapsed >= 11500) {
analogWrite(RED_LED_PIN, 0);
playLEDNotes = false;
} else if (elapsed >= 9900) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 9370) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 8900) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 8610) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 8230) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 7970) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 7470) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 6760) analogWrite(GREEN_LED_PIN, 255); // E 음
else if (elapsed >= 6350) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 5880) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 5560) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 5180) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 4890) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 4420) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 3810) analogWrite(GREEN_LED_PIN, 255); // E 음
else if (elapsed >= 3420) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 2930) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 2560) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 2200) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 1930) analogWrite(YELLOW_LED_PIN, 255); // D 음
else if (elapsed >= 1470) analogWrite(RED_LED_PIN, 255); // C 음
else if (elapsed >= 1000) analogWrite(GREEN_LED_PIN, 255); // E 음
}
// 모든 LED 소등
void allLEDsOff() {
analogWrite(RED_LED_PIN, 0);
analogWrite(YELLOW_LED_PIN, 0);
analogWrite(GREEN_LED_PIN, 0);
}