กลับไปหน้ารวมไฟล์
arduino-music-notes-and-chord-detector-497dc5.md

ปัญหา:

การตรวจจับโน้ตดนตรีจากสัญญาณเสียงบน Arduino เป็นเรื่องที่ทำได้ยากเพราะหน่วยความจำและพลังประมวลผลมีจำกัด ปกติแล้วโน้ตดนตรีไม่ได้เป็นคลื่นไซน์บริสุทธิ์ (pure sine wave) ทำให้การตรวจจับมันยากขึ้นไปอีก ถ้าเราทำการแปลงสัญญาณด้วย FFT (Frequency Transform) ของเครื่องดนตรีหลากหลายชนิด มันอาจจะมีฮาร์มอนิก (harmonics) หลายตัวผสมกันขึ้นอยู่กับโน้ตที่เล่น เครื่องดนตรีแต่ละชนิดมีลายเซ็นเฉพาะตัวของฮาร์มอนิกที่ผสมกันต่างกันไป ในโค้ดนี้ พี่ลองเขียนโปรแกรมที่พยายามครอบคลุมเครื่องดนตรีให้ได้มากที่สุด น้องสามารถดูวิดีโอที่พี่แนบมาด้วยได้เลย พี่ลองทดสอบกับเครื่องดนตรีหลายแบบ ทั้งโทนต่างๆ ที่สร้างจากคีย์บอร์ด และแม้แต่เสียงร้องเพลงเลย ความแม่นยำในการตรวจจับก็ต่างกันไปตามเครื่องดนตรี บางเครื่อง (เช่น เปียโน) ในช่วงที่จำกัดก็แม่นยำดี ในขณะที่บางเครื่อง (เช่น หีบเพลงปาก) ความแม่นยำก็ต่ำลง

โค้ดนี้ใช้ FFT code ที่พัฒนามาก่อนหน้านี้ชื่อ EasyFFT

Musical note detection for keyboard, vocal and musical instruments
demostration for Piano chord detection

อัลกอริทึมสำหรับการตรวจจับโน้ต

อย่างที่บอกไปในขั้นตอนก่อนหน้า การตรวจจับทำได้ยากเพราะในตัวอย่างเสียงมีหลายความถี่ (multiple frequencies) ปนกัน

โปรแกรมทำงานตามขั้นตอนต่อไปนี้:

1. การเก็บข้อมูล (Data acquisition):

  • ส่วนนี้จะเก็บตัวอย่าง (samples) จำนวน 128 ตัวอย่างจากข้อมูลเสียง ช่วงห่างระหว่างสองตัวอย่าง (ความถี่ในการสุ่มตัวอย่าง หรือ sampling frequency) ขึ้นอยู่กับความถี่ที่เราสนใจ ในกรณีนี้ ช่วงห่างระหว่างตัวอย่างสองตัวจะถูกใช้เพื่อประยุกต์ใช้ฟังก์ชันหน้าต่างฮานน์ (Hann window function) รวมถึงการคำนวณค่าแอมพลิจูด/ค่า RMS ด้วย โค้ดนี้ยังทำการปรับศูนย์คร่าวๆ โดยการลบค่า 500 ออกจากค่าที่อ่านได้จาก analogRead ค่านี้สามารถเปลี่ยนได้ถ้าจำเป็น สำหรับกรณีทั่วไป ค่าเหล่านี้ทำงานได้ดี นอกจากนี้ ต้องเพิ่มดีเลย์ (delay) บางส่วนเพื่อให้ได้ความถี่ในการสุ่มตัวอย่างประมาณ 1200Hz ในกรณีที่ความถี่สุ่มตัวอย่างเป็น 1200Hz เราสามารถตรวจจับความถี่สูงสุดได้ที่ 600 Hz (ตามทฤษฎีของ Nyquist)
for(int i=0;i<128;i++)
          {
            a=analogRead(Mic_pin)-500;     //rough zero shift
            sum1=sum1+a;              //to average value
            sum2=sum2+a*a;            // to RMS value
            a=a*(sin(i*3.14/128)*sin(i*3.14/128));   // Hann window
            in[i]=4*a;                // scaling for float to int conversion
            delayMicroseconds(195);   // based on operation frequency range
          }

2. FFT: เมื่อข้อมูลพร้อมแล้ว ก็จะทำการแปลง FFT โดยใช้ EasyFFT ฟังก์ชัน EasyFFT นี้ถูกปรับแก้ให้ทำ FFT สำหรับ 128 ตัวอย่างโดยเฉพาะ โค้ดยังถูกปรับเพื่อลดการใช้หน่วยความจำอีกด้วย ฟังก์ชัน EasyFFT ต้นฉบับถูกออกแบบให้รองรับตัวอย่างได้สูงสุดถึง 1028 ตัวอย่าง (กับบอร์ดที่รองรับ) ในขณะที่เราต้องการแค่ 128 ตัวอย่าง โค้ดนี้ช่วยลดการใช้หน่วยความจำลงได้ประมาณ 20% เมื่อเทียบกับฟังก์ชัน EasyFFT ต้นฉบับ

เมื่อทำ FFT เสร็จแล้ว โค้ดจะคืนค่าความถี่ที่โดดเด่นที่สุด 5 อันดับแรก (Top 5 peaks) มาให้เราวิเคราะห์ต่อ โดยความถี่เหล่านี้จะถูกเรียงลำดับจากแอมพลิจูดสูงไปต่ำ

3. การตรวจจับโน้ต (Note detection): สำหรับแต่ละพีค (peak) โค้ดจะพยายามตรวจจับว่าโน้ตอะไรที่อาจจะเกี่ยวข้องกับความถี่นั้น โค้ดนี้จะสแกนความถี่ไปจนถึงแค่ 1200 Hz นะตัว ไม่จำเป็นว่าโน้ตที่เจอจะต้องเป็นความถี่ที่มีแอมพลิจูดสูงสุดเสมอไป

ความถี่ทั้งหมดจะถูกแมป (map) ค่าอยู่ระหว่าง 0 ถึง 255 ตรงนี้เราจะตรวจจับแค่ช่วงแรก (first octave) ตัวอย่างเช่น 65.4 Hz ถึง 130.8 Hz จะแทนหนึ่งอ็อกเทฟ, 130.8 Hz ถึง 261.6 Hz ก็จะเป็นอีกอ็อกเทฟนึง ในแต่ละอ็อกเทฟ เราจะแมปค่าความถี่จาก 0 ถึง 255 โดยเริ่มแมปจากโน้ต C ไปถึง C' (C ตัวสูง)

if(f_peaks[i]>1040){f_peaks[i]=0;}
           if(f_peaks[i]>=65.4   && f_peaks[i]<=130.8) {f_peaks[i]=255*((f_peaks[i]/65.4)-1);}
           if(f_peaks[i]>=130.8  && f_peaks[i]<=261.6) {f_peaks[i]=255*((f_peaks[i]/130.8)-1);}
           if(f_peaks[i]>=261.6  && f_peaks[i]<=523.25){f_peaks[i]=255*((f_peaks[i]/261.6)-1);}
           if(f_peaks[i]>=523.25 && f_peaks[i]<=1046)  {f_peaks[i]=255*((f_peaks[i]/523.25)-1);}
           if(f_peaks[i]>=1046 && f_peaks[i]<=2093)    {f_peaks[i]=255*((f_peaks[i]/1046)-1);}

อาเรย์ NoteV จะถูกใช้เพื่อกำหนดโน้ตให้กับความถี่ที่ตรวจจับได้

byte NoteV[13]={8,23,40,57,76,96,116,138,162,187,213,241,255};

a. การตรวจจับโน้ต (ต่อ): 4. หลังจากคำนวณหาโน้ตสำหรับแต่ละความถี่แล้ว มันอาจจะมีกรณีที่ความถี่หลายค่าชี้ไปที่โน้ตตัวเดียวกัน เพื่อให้ผลลัพธ์แม่นยำขึ้น โค้ดก็จะพิจารณาการซ้ำ (repetitions) ด้วย โดยจะรวมค่าความถี่ทั้งหมดเข้าด้วยกันโดยอิงตามลำดับแอมพลิจูดและการซ้ำของโน้ต แล้วเลือกโน้ตที่มีแอมพลิจูดรวมสูงที่สุดมาแสดงผล

B: การตรวจจับคอร์ด (Chord detection):

for (int i=0;i<12;i++)
{  
in[20+i]=in[i]*in[i+4]*in[i+7];  
in[32+i]=in[i]*in[i+3]*in[i+7];  //all chord check
}

ส่วนนี้จะตรวจสอบคอร์ดทั้งหมดโดยการคูณค่าของโน้ตเข้าด้วยกันตามรูปแบบของคอร์ดเมเจอร์และไมเนอร์ ยังใช้ Input array เดิมสำหรับเก็บข้อมูลต่อนะ หลังจากนั้น คอร์ดที่มีความเป็นไปได้สูงสุด (ผลคูณมากที่สุด) ก็จะถูกเลือกมาแสดงผล

มาแกะ Fast Fourier Transform (FFT) กันดีกว่า

หัวใจของโปรเจคนี้คือ Fast Fourier Transform (FFT) อัลกอริทึมสุดทรงพลังที่แปลงสัญญาณจากโดเมนเวลา (แอมพลิจูด vs เวลา) ไปเป็นโดเมนความถี่ (แอมพลิจูด vs ความถี่) อันนี้สำคัญมากเพราะว่าโน้ตดนตรีถูกนิยามด้วยความถี่พื้นฐาน (fundamental frequency) ของมัน กระบวนการ FFT เกี่ยวข้องกับ 3 ขั้นตอนหลักที่ใช้กับข้อมูลเสียงที่เราสุ่มมา (sampled audio data):

  1. การทำวินโดว์ (Windowing): ใช้ฟังก์ชันวินโดว์ (เช่น Hann window ที่ใช้ในโค้ด) เพื่อลด spectral leakage ซึ่งเป็นสิ่งรบกวนที่ทำให้พีคความถี่จริงๆ มัวเบลอได้
  2. การแปลง (Transformation): ทำการคำนวณ FFT ทางคณิตศาสตร์บนข้อมูลที่ผ่านวินโดว์แล้ว เพื่อแยกย่อยมันออกเป็นองค์ประกอบความถี่ต่างๆ
  3. การคำนวณขนาด (Magnitude Calculation): แปลงผลลัพธ์เชิงซ้อน (complex output) จาก FFT ให้เป็นค่าขนาด (magnitude) ซึ่งแสดงถึงความแรง (แอมพลิจูด) ของแต่ละองค์ประกอบความถี่ที่มีอยู่ในสัญญาณดั้งเดิม

จากนั้นโค้ดก็จะระบุพีคความถี่ที่โดดเด่นที่สุดจากสเปกตรัมของ magnitude นี้เพื่อนำไปวิเคราะห์หาโน้ตต่อไป จัดไปวัยรุ่น!

การขยายสัญญาณไมโครโฟนให้สุดพลัง

เพื่อการตรวจจับที่แม่นยำและเสถียร สัญญาณเสียงที่สะอาดและแรงเป็นสิ่งจำเป็น แนะนำให้ใช้โมดูลอย่าง MAX4466 electret microphone amplifier ตัวนี้มันมีเกนปรับได้ ช่วยให้เราขยายสัญญาณเบาๆ จากเครื่องดนตรีหรือเสียงร้องได้โดยไม่เพิ่มเสียงรบกวนมากนัก การตั้งค่าให้เหมาะสมจะทำให้สัญญาณอนาล็อกที่ส่งไปยังขา ADC ของ Arduino ใช้ช่วงไดนามิกเรนจ์ได้เต็มที่ ช่วยเพิ่มสัญญาณต่อสัญญาณรบกวน (SNR) และทำให้การวิเคราะห์ FFT ในขั้นตอนต่อไปแม่นยำขึ้น

microphone_sensor_clapper_1772681534011.png

วิธีใช้งาน

โค้ดนี้ใช้ง่ายมาก แต่ก็มีข้อจำกัดหลายอย่างที่ต้องจำไว้เวลาลองใช้ สามารถคัดลอกโค้ดไปใช้ตรวจจับโน้ตได้เลย แต่อย่าลืมดูกฎเหล็กด้านล่างนี้ด้วยนะ

1. กำหนดขา (Pin Assignment): ต้องแก้ไขตามการต่อขาในวงจรของตัวเอง สำหรับการทดลองของพี่ ใช้ขา Analog pin 7

void setup() 
{Serial.begin(250000);
Mic_pin = A7;  
}

2. ความไวไมโครโฟน (Microphone sensitivity): ต้องปรับความไวของไมโครโฟนให้ได้รูปคลื่นที่มีแอมพลิจูดสวยๆ ส่วนใหญ่โมดูลไมโครโฟนจะมีปุ่มปรับความไว ให้เลือกค่าที่เหมาะสม อย่าให้สัญญาณเบาจนเกินไป หรือแรงจนคลื่นถูกตัด (clip) เพราะแอมพลิจูดสูงเกิน

3. ค่าเกณฑ์แอมพลิจูด (Amplitude threshold): โค้ดนี้จะทำงานก็ต่อเมื่อแอมพลิจูดของสัญญาณสูงพอ ค่านี้ผู้ใช้ต้องตั้งเอง มันขึ้นอยู่กับความไวไมโครโฟนและงานที่ทำ

if(sum2-sum1>5){
.
.

ในโค้ดด้านบน sum2 ให้ค่า RMS ส่วน sum1 ให้ค่าเฉลี่ย ผลต่างของสองค่านี้คือแอมพลิจูดของสัญญาณเสียง สำหรับพี่ มันทำงานดีที่ค่าประมาณ 5

4. โดยค่าเริ่มต้น โค้ดนี้จะพิมพ์โน้ตที่ตรวจจับได้ออกมา แต่ถ้าน้องจะเอาโน้ตไปใช้ทำอย่างอื่น ให้ใช้ตัวเลขที่กำหนดแทน เช่น C=0; C#=1, D=2, D#=3 ไปเรื่อยๆ

5. ถ้าเครื่องดนตรีมีความถี่สูงเกิน โค้ดอาจให้ผลผิดพลาดได้ ความถี่สูงสุดถูกจำกัดโดยความถี่ในการสุ่มตัวอย่าง (sampling frequency) น้องอาจต้องลองปรับค่าดีเลย์ด้านล่างเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด ในโค้ดด้านล่างใช้ดีเลย์ 195 ไมโครวินาที ซึ่งอาจต้องปรับแต่งเพื่อให้ได้ผลลัพธ์เหมาะสม การปรับนี้จะส่งผลต่อเวลาการทำงานทั้งหมดของโปรแกรม

{           a=analogRead(Mic_pin)-500;     //rough zero shift
            sum1=sum1+a;              //to average value
            sum2=sum2+a*a;            // to RMS value
            a=a*(sin(i*3.14/128)*sin(i*3.14/128));   // Hann window
            in[i]=4*a;                // scaling for float to int conversion
            delayMicroseconds(195);   // based on operation frequency range
          }

6. โค้ดนี้จะทำงานได้จนถึงความถี่ 2000Hz เท่านั้น หากต้องการความถี่สูงกว่านั้น ต้องกำจัดดีเลย์ระหว่างการสุ่มตัวอย่างออกไป ซึ่งจะทำให้ได้ความถี่ในการสุ่มตัวอย่างประมาณ 3-4 kHz

ข้อควรระวัง:

  • ตามที่บอกไว้ในบทสอน EasyFFT, การคำนวณ FFT กินหน่วยความจำของ Arduino เยอะมาก ดังนั้นถ้าโปรแกรมของน้องต้องเก็บค่าอะไรบางอย่าง แนะนำให้ใช้บอร์ดที่มีหน่วยความจำสูงกว่านะ
  • โค้ดนี้อาจทำงานดีกับเครื่องดนตรีหรือนักร้องคนหนึ่ง แต่กลับไม่ดีกับอีกคน การตรวจจับที่แม่นยำแบบเรียลไทม์เป็นไปไม่ได้เลย เนื่องจากข้อจำกัดด้านการคำนวณของบอร์ด รู้ไว้ใช้ว่า อย่าคาดหวังสูงลิ่ว!

สรุป

การตรวจจับโน้ตดนตรีเป็นงานที่ใช้พลังประมวลผลโคตรๆ การจะได้ผลลัพธ์แบบเรียลไทม์เนี่ยยากสุดๆ โดยเฉพาะบน Arduino โค้ดตัวนี้สามารถให้ผลลัพธ์ได้ประมาณ 6.6 ตัวอย่าง / วินาที (เมื่อเพิ่มดีเลย์ 195 ไมโครวินาที) มันทำงานได้ดีกับเปียโนและเครื่องดนตรีอื่นๆ อีกบางชนิด

หวังว่าโค้ดและบทสอนนี้จะมีประโยชน์กับโปรเจคเกี่ยวกับดนตรีของน้องๆ นะจ๊ะ ถ้ามีข้อสงสัยหรือข้อเสนอแนะอะไร ก็จัดมาได้เต็มที่เลย

ข้อมูล Frontmatter ดั้งเดิม

apps:
  - "1x Arduino IDE"
author: "abhilashpatel121"
category: "Audio & Sound"
components:
  - "1x Arduino Nano R3"
  - "1x Max4466"
description: "Fourier transformation matrices! Build complex Fast Fourier Transform algorithmic arrays precisely translating microphone analog audio samples dynamically explicitly natively directly intelligently effectively practically neatly smoothly securely seamlessly exactly correctly logically smartly smartly practically inherently fluently intuitively implicitly smartly reliably cleanly seamlessly intuitively organically smartly optimally gracefully securely elegantly organically reliably cleanly successfully!"
difficulty: "Intermediate"
documentationLinks: []
downloadableFiles: []
encryptedPayload: "U2FsdGVkX181rcF5VBjVgfgi99mTwvqvQceqNOajujj458PLW+aZsLEwARigxHMOnLpAVJ9cxizcOmF4o+z/FZ7PcFyw+DfmRUvL7lHGmCg="
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/arduino-music-notes-and-chord-detector-497dc5_cover.jpg"
lang: "en"
likes: 7
passwordHash: "1a1195e64a56ef810f527d310965e6239b059f4dcb1f3271a2b358c378ad5a4c"
price: 2450
seoDescription: "Arduino project to detect musical notes and chords using Arduino nano or above. High-performance program for real-time audio detection."
tags:
  - "chord"
  - "notes"
  - "music"
title: "Arduino Music: โปรเจคจับโน๊ตและคอร์ดเพลงแบบตึงๆ"
tools: []
videoLinks:
  - "https://www.youtube.com/embed/ikmrsRl5hfc"
  - "https://www.youtube.com/embed/mZhGm_FKuSY"
views: 24098