กลับไปหน้ารวมไฟล์
creating-a-midi-pass-through-recorder-53d48c.md

สร้างเครื่องบันทึก MIDI แบบพาสทรู

ถ้าน้องเคยใช้ซอฟต์แวร์ทำเพลงในคอม น่าจะรู้จัก MIDI อยู่แล้ว มันคือโปรโตคอลที่ให้พวกคอนโทรลเลอร์สั่งงานเครื่องดนตรีเสมือนอย่างซินธ์ได้ แล้วก็เป็นภาษาที่ฮาร์ดแวร์เสียงจริงๆ ใช้คุยกันด้วย แทนที่จะส่งสัญญาณแรงดันที่ขึ้นๆ ลงๆ หรือค่าตัวอย่างดิจิทัลเป็นชุดๆ อุปกรณ์จะคุยกันว่าเกิดอะไรขึ้น ("กดปุ่ม A4 แล้วนะ", "ปล่อย F4 แล้ว", "ล้อ mod ถูกหมุนลง" อะไรแบบนี้)

เพราะงั้น เวลาจะบันทึกเสียงเครื่องดนตรีดิจิทัล (ของจริงหรือของเสมือน) เราทำได้สองทาง: บันทึกเสียงที่มันเปล่งออกมาเลย หรือบันทึกเหตุการณ์ MIDI ที่ทำให้มันเปล่งเสียงนั่นแหละ และนี่คือจุดที่มันเริ่มสนุกแล้ว

มีวิธีบันทึกเสียงอยู่ เพียบ ตั้งแต่ใช้ไมค์ ใช้ไลน์มอนิเตอร์ ใช้อินเทอร์เฟซเสียง บนฮาร์ดแวร์เฉพาะ คอม มือถือ ฯลฯ แต่สำหรับการบันทึกเหตุการณ์ MIDI นี่... ไม่ค่อยมีวิธีเท่าไหร่ จริงๆ แล้วคือ ถ้าน้องไม่ได้รันซอฟต์แวร์ที่คอยจับตาเหตุการณ์ MIDI อยู่ ก็แทบไม่มีทางบันทึกมันได้เลย ผมเลยตั้งใจจะเปลี่ยนเรื่องนี้: เหมือนกับที่เราสามารถต่อเครื่องบันทึกเสียงภาคสนาม (อย่าง Tascam DR-05) ไว้ระหว่างเอาต์พุตเสียงของอุปกรณ์สร้างเสียง กับอินพุตเสียงของอุปกรณ์รับฟังเสียง แล้วบันทึกลงการ์ด SD เป็นไฟล์ .wav หรือ .mp3 ผมก็สร้างเครื่องบันทึก MIDI "ภาคสนาม" ขึ้นมา โดยให้มันต่ออยู่ระหว่าง MIDI-out ของน้อง กับ MIDI-in ของอุปกรณ์ปลายทาง มันจะบันทึกทุกเหตุการณ์ MIDI ที่วิ่งผ่านสายลงการ์ด SD เป็นไฟล์ .mid แบบไม่เลือกที่รักมักที่ชัง

น้องอาจคิดว่า ของแบบนี้ต้องมีขายเป็นผลิตภัณฑ์อยู่แล้วแน่ๆ (ถึงแม้อาจจะถูกตีราคาแพงเพราะทำจาก "อลูมิเนียมอัดขึ้นรูปเคลือบผง" และใช้ชิ้นส่วน "คุณภาพระดับ Audiophile" ก็เถอะ) แต่ที่น่าประหลาดใจคือ... มันไม่มีเลยว่ะ ไม่มีอะไรสักอย่าง

สรุป: ถ้าน้องอยากได้เครื่องบันทึก MIDI น้องต้องสร้างมันขึ้นมาเอง... และถ้าน้องอยากสร้างมัน โพสต์นี้อาจมีประโยชน์กับน้องนะ!

วงจรไฟฟ้า

การสร้างเจ้านี่ เราก็แค่สร้างวงจรมาตรฐาน "MIDI-In + MIDI-Thru" โดยใช้ Arduino แล้วต่อโมดูลการ์ด SD เข้าไปเพื่อบันทึกข้อมูลที่วิ่งผ่านมา อุปกรณ์ที่ต้องใช้มีดังนี้:

  • โมดูลการ์ด SD สำหรับ Arduino (~10 ดอลลาร์ ได้ 5 ชิ้น)
  • ขั้วต่อ DIN 5 พิน แบบเมีย 2 ตัว (~5 ดอลลาร์ ได้ 10 ชิ้น)
  • ออปโตคัปเปลอร์ 6N138 (~10 ดอลลาร์ ได้ 10 ชิ้น)
  • ตัวเลือก: โมดูลนาฬิกาจริง (RTC) แบบ DS3231

และแน่นอน อุปกรณ์พื้นฐานที่มากับชุดเริ่มต้น Arduino ทั่วไป:

  • บอร์ด Arduino UNO R3 หรือเทียบเท่า
  • ตัวต้านทาน (Resistor) 220 โอห์ม 3 ตัว
  • ตัวต้านทาน 4.7k โอห์ม 1 ตัว
  • ตัวต้านทาน 10k โอห์ม 2 ตัว
  • ไดโอด 1 ตัว
  • ออดแบบเพียโซ (piezo buzzer) 1 ตัว
  • ปุ่มกดแบบคลิกๆ 2 ปุ่ม

ส่วน MIDI ของเครื่องบันทึกเรา

เราตั้งค่า MIDI-In บนพิน RX<-0 ของ Arduino โดยให้ MIDI-Thru แตะตรงๆ เข้ากับสัญญาณเดียวกันที่กำลังส่งไปยัง RX<-0 ด้วย สิ่งที่ต้องระวังหน่อยคือ สัญญาณ MIDI จะถูกแยกออกจากวงจรส่วนอื่นด้วยออปโตคัปเปลอร์ (ซึ่งแก้ปัญหา ground loop ได้โดยการส่งสัญญาณผ่าน LED ที่แปลงสัญญาณไฟฟ้าเป็นแสง แล้วให้โฟโตทรานซิสเตอร์อีกข้างแปลงแสงกลับเป็นสัญญาณไฟฟ้า) เวลาใส่และต่อออปโตคัปเปลอร์ ต้องแน่ใจว่ารู้ว่าพินไหนคือพินที่ 1: มักจะมีเครื่องหมายเล็กๆ ไว้ (เช่น จุดบนตัวชิป) บอกว่าด้านนั้นมีพิน 1 ถึง 4 เรียงจากบนลงล่าง และพิน 5 ถึง 8 อยู่อีกด้านหนึ่ง เรียงจากล่างขึ้นบน แล้วก็จำไว้ว่าเราไม่ได้ใช้พิน 1 และ 4 ในวงจรนี้: มีแค่พิน 2 และ 3 ที่ต่อกับขั้วต่อ MIDI-In ส่วนพิน 5 ถึง 8 ต่อกับพินต่างๆ ของ Arduino

(พี่รู้นะว่า "Thru" มันไม่ใช่คำภาษาอังกฤษ แต่ใน สเปค MIDI เค้าเรียกแบบนี้แหละน้อง ภาษาอังกฤษต้องยอมแพ้ไปเลย...)

ส่วน SD ของเครื่องบันทึกเรา

วงจร SD การ์ดเนี่ย จริงๆ ก็แค่ "ต่อขาตรงๆ" นี่แหละ แต่มันมีจุดแปลกอยู่นิดเดียวคือขามัน ไม่ค่อย ตรงกันพอที่จะปักโมดูล SD การ์ดลงบน Arduino ตรงๆ ได้เป๊ะๆ

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

เพิ่มปุ่มทำเครื่องหมาย MIDI

เพื่อให้หาส่วนที่ "น่าดูอีกครั้ง" ในสิ่งที่บันทึกไว้ได้ง่ายขึ้น เราจะเพิ่มปุ่มที่ต่อกับขา 4 เอาไว้เขียนเครื่องหมาย (marker) ลงในไฟล์ของเรา วงจรนี้แทบไม่มีอะไรเลย:

เพิ่มเสียงบี๊บ สำหรับดีบั๊ก

และสุดท้าย เราจะเพิ่มลำโพงเพียโซตัวจิ๋วกับปุ่มอีกอัน เอาไว้กดเปิด/ปิดเสียงโน้ตที่ตรงกับ MIDI note ที่กำลังเล่นอยู่ เป็นเหมือนการดีบั๊กด้วยเสียงแทนภาพ งานก็ไม่มีอะไรมาก: ต่อ "ลำโพง" ระหว่างขา 8 กับกราวด์ แล้วต่อปุ่มเข้ากับขา 2 แค่นี้เอง! บี๊บ บี๊บ!

ตัวเลือก: เพิ่มนาฬิกาจริง (Real Time Clock)

วงจรสุดท้ายนี่ไม่จำเป็นต้องมีก็ได้ แต่ถ้ามันจะช่วยให้ใช้ง่ายขึ้นอีกนิด: นาฬิกาจริง (RTC) ที่ใช้ชิป DS3231 ซึ่งเป็น RTC แบบ "หรู" มีสมองในตัวทำให้มันแม่นยำแม้อุณหภูมิจะเปลี่ยน การต่อก็ตรงไปตรงมา ใช้ขาที่ด้านข้างของ Arduino ที่เรายังไม่ได้ใช้ โดยต่อขา SDA (หรือ "D") เข้ากับอินพุต A4 และขา SCL (หรือ "C") เข้ากับอินพุต A5 แล้วมันได้อะไร? อย่างแรกคือไลบรารี SD การ์ดจะใช้มันเพื่อให้ไฟล์มีวันที่จริงๆ และอย่างที่สอง มันจะทำให้เราเขียนเครื่องหมาย MIDI ที่เชื่อมโยงกับวันที่และเวลาได้ แทนที่จะเป็นแค่เลขลำดับธรรมดา สองอย่างนี้จะช่วยให้น้องหางานเก่าได้ง่ายขึ้นเยอะเลย

ส่วนซอฟต์แวร์

จัดวางวงจรเสร็จแล้ว มาลุยเขียนโปรแกรมกันดีกว่า โดยจะแยกจัดการแต่ละวงจรเป็นส่วนๆ ไป

  • พื้นฐานโปรแกรม
  • จัดการสัญญาณพื้นฐาน (ไลบรารี MIDI)
  • เขียนไฟล์พื้นฐาน (ไลบรารี SD)
  • บันทึกเครื่องหมาย MIDI
  • ดีบั๊กด้วยเสียง (บี๊ป บี๊ป)
  • โบนัสความสะดวก: รีสตาร์ท "สะอาด" ตอนว่าง
  • โบนัสความสะดวก 2: สคริปต์ "ซ่อมความยาวแทร็ก"

พื้นฐานโปรแกรม

โปรแกรมพื้นฐานของเราต้อง import ไลบรารีมาตรฐาน SD และไลบรารี MIDI (ซึ่งน้องอาจต้องติดตั้งก่อน)

ถ้าน้องไม่อยากตามทีละขั้นตอนและอยากได้โค้ดไปเลยล่ะก็ สามารถก็อปปี้โค้ดจาก ไฟล์ midi-recorder.ino ไปวางใน Arduino IDE ได้เลยจ้า

#include <SD.h>
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
void setup() {
  // we'll put some more code here in the next sections
}
void loop() {
  // we'll put some more code here in the next sections
}

จัดไปวัยรุ่น! เรียบร้อยละ

แน่นอนว่าตอนนี้โค้ดยังไม่ ทำ อะไรเลย งั้นมาเติมโค้ดส่วนที่เหลือกันดีกว่า สู้งานนะน้อง

จัดการ MIDI

สำหรับการจัดการ MIDI ของเรา เราต้องตั้งค่า listener สำหรับเหตุการณ์ MIDI และให้แน่ใจว่าเราไปอ่านข้อมูลนั้นระหว่างที่โปรแกรมวนลูปอยู่:

void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandlePitchBend(handlePitchBend);
  MIDI.setHandleControlChange(handleControlChange);
}
void loop() {
  checkForMarker();
  setPlayState();
  updateFile();
  MIDI.read();
}

โค้ดนี้ตั้งค่าให้ฟัง MIDI บนทุกช่องสัญญาณ (มีทั้งหมด 16 ช่องนะจ๊ะ เราจะไม่ทายมั่วๆ ว่าช่องไหนกำลังใช้งาน) และอ่านข้อมูล MIDI จาก RX<-0 — น้องอาจสังเกตว่าเราไม่ได้ตั้งค่า baud rate เอง: สเปค MIDI อนุญาตแค่ 31,250 บิตต่อวินาทีเท่านั้น ดังนั้นไลบรารี MIDI จะจัดการตั้งค่าอัตราการอ่านที่ถูกต้องให้เราเองอัตโนมัติ

น้องจะเห็นว่า loop() ทำ 4 อย่าง: ในส่วนนี้เราจะดูแค่การอ่าน MIDI ก่อน ส่วนอีก 3 ขั้นตอนที่เหลือ เดี๋ยวเราค่อยว่ากันในหัวข้อถัดไป

ตอนนี้ สมมติว่าการเรียกฟังก์ชัน 3 ตัวแรกทำงานได้ (เพราะเดี๋ยวมันก็จะทำงานอยู่แล้ว =) เหลือแค่การเขียนโค้ดจัดการเหตุการณ์ MIDI ของเรา:

#define NOTE_OFF_EVENT 0x80
#define NOTE_ON_EVENT 0x90
#define CONTROL_CHANGE_EVENT 0xB0
#define PITCH_BEND_EVENT 0xE0
void handleNoteOff(byte channel, byte pitch, byte velocity) {
  writeToFile(NOTE_OFF_EVENT, pitch, velocity);
}
void handleNoteOn(byte channel, byte pitch, byte velocity) {
  writeToFile(NOTE_ON_EVENT, pitch, velocity);
}
void handleControlChange(byte channel, byte controller, byte value) {
  writeToFile(CONTROL_CHANGE_EVENT, controller, value);
}
void handlePitchBend(byte channel, int bend_value) {
  // อย่างแรก เราต้อง "ปรับศูนย์กลาง" ค่า bend ใหม่
  // เพราะใน MIDI ค่า bend เป็นค่าบวก
  // ในช่วง 0x0000-0x3FFF โดย 0x2000 ถือเป็น
  // จุดกลาง "กลาง" ในขณะที่ไลบรารี MIDI
  // ให้ค่ามาเป็นจำนวนเต็มมีเครื่องหมาย โดยใช้ 0 เป็น
  // จุดกลาง และใช้ค่าลบเพื่อหมายถึง "ลง"
  bend_value += 0x2000;
  // จากนั้น ตามสเปค MIDI เราต้องเข้ารหัสค่า bend 14 บิต
  // เป็นไบต์ 7 บิตสองตัว โดยที่ไบต์แรก
  // บิตต่ำสุด 7 บิตของค่า bend ของเรา และไบต์ที่สอง
  // บิตสูงสุด 7 บิตของค่า bend ของเรา:
  byte lowBits = (byte) (bend_value & 0x7F);
  byte highBits = (byte) ((bend_value >> 7) & 0x7F);
  writeToFile(PITCH_BEND_EVENT, lowBits, highBits);
}

(สังเกตว่าเราไม่สนใจไบต์ channel นะ: เราจะสร้างไฟล์ MIDI แบบ "ง่าย" แบบ format 0 และเพื่อให้ใช้งานได้ง่ายสุดตอนนำข้อมูลเข้า DAW เราจะใส่เหตุการณ์ทั้งหมดลงในช่องที่ 1 เราจะเห็นได้จากค่าคงที่เหตุการณ์ของเรา: เหตุการณ์ใช้ "นิบเบิล" 4 บิตสองตัว โดยนิบเบิลแรกคือตัวระบุเหตุการณ์ และนิบเบิลที่สองคือช่องที่เหตุการณ์เกิดขึ้น ตัวอย่างเช่น: NOTE_OFF_EVENT ใช้ 0x80 สำหรับ "note off บนช่อง 1" แต่ 0x8F สำหรับ "note off บนช่อง 16")

เริ่มต้นได้ดีเลยน้อง! แต่ต้องเข้าใจว่า MIDI events มันคือ "เหตุการณ์" จริงๆ นะ เหตุการณ์มันต้องเกิด "ณ เวลาใดเวลาหนึ่ง" ซึ่งเรายังต้องเก็บข้อมูลตรงนี้อยู่ดี สิ่งที่ดีคือ MIDI events มาตรฐานไม่ได้ใช้เวลาจริงจากนาฬิกาโลก (RTC) ซึ่งดีมากเพราะ Arduino ของเราไม่มี RTC ในตัว! แทนที่จะใช้เวลาจริง มันใช้การนับ "time delta" คือมันจะบันทึกว่าเหตุการณ์แต่ละอันเกิดห่างจากเหตุการณ์ก่อนหน้าเท่าไหร่ โดยวัดเป็น "MIDI clock ticks" โดยเหตุการณ์แรกสุดในสตรีมจะมีค่า delta เป็นศูนย์ชัดๆ

งั้นมาเขียนฟังก์ชัน getDelta() กันดีกว่า ไว้ใช้วัดว่าเกิดกี่ MIDI tick แล้วตั้งแต่เหตุการณ์ล่าสุด (ก็คือตั้งแต่ครั้งล่าสุดที่เรียก getDelta() นั่นแหละ) พอได้ข้อมูลครบเราก็จะเริ่มเขียน MIDI ลงไฟล์ได้แล้ว:

unsigned long startTime = 0;
unsigned long lastTime = 0;
int getDelta() {
if (startTime == 0) {
startTime = millis();
lastTime = startTime;
return 0;
}
unsigned long now = millis();
unsigned int delta = (now - lastTime);
lastTime = now;
return delta;
}

ฟังก์ชันนี้ดูใหญ่เกินจำเป็นใช่มั้ย? จริงๆ เราสามารถเริ่มจับเวลาเมื่อสเก็ตช์เริ่มทำงานได้เลย โดยเซ็ต lastTime=millis() ใน setup() แล้วใน getDelta ก็แค่คำนวณ timeDelta กับอัพเดท lastTime เท่านั้น แต่วิธีนั้นจะทำให้เรา "เข้ารหัสความว่างเปล่า" จำนวนมากตอนต้นไฟล์ MIDI ของเรา คือเราจะนับ tick ตั้งแต่ตอนที่ยังไม่มีเหตุการณ์อะไรเกิดขึ้นเลย

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

apps:
  - "1x Arduino IDE"
author: "pomax"
category: "Audio & Sound"
components:
  - "1x 4.7k ohm resistor"
  - "1x Arduino UNO"
  - "2x Resistor 10k ohm"
  - "1x MicroSD card module"
  - "1x Opto-Isolator"
  - "2x Resistor 10k ohm"
  - "1x Buzzer"
  - "3x Resistor 220 ohm"
  - "2x 5-pid DIN MIDI connector"
  - "2x 4-pin button"
  - "1x 3.6V 0.5W Zener Diode"
description: "Digital audio workstation synthesis! Plumb the massive hexadecimal depths of the 31250 baud Native MIDI protocol entirely, dynamically intercepting and rewriting live keyboard sequences seamlessly into a multi-track hardware loop-pedal."
difficulty: "Advanced"
documentationLinks: []
downloadableFiles:
  - "https://github.com/Pomax/arduino-midi-recorder"
  - "https://projects.arduinocontent.cc/72efb633-587d-4bf8-aeda-313c89282b83.ino"
  - "https://github.com/Pomax/arduino-midi-recorder"
encryptedPayload: "U2FsdGVkX18Ayv1vX5Id27tVRrOCFaP6qi06Y2zphgj0VwKQ7LT2Aktr3iLR/bnLJWy4OxcrBm4lDZqC2pHXBj22OmBCxHAbGRg1oNC5V4M="
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/creating-a-midi-pass-through-recorder-53d48c_cover.jpg"
lang: "en"
likes: 7
passwordHash: "c20c35dd3c8a5dbaa23ccb8de15babc24d347555a4b4edb45f80cb3546e677f5"
price: 2990
seoDescription: "Build a MIDI Pass-Through Recorder with Arduino and an SD card module. Record every note and never lose your musical ideas again."
tags:
  - "midi"
  - "sd"
  - "recording"
title: "สร้างเครื่องบันทึก MIDI แบบ Pass-Through ไว้ดักเก็บทุกโน้ต!"
tools: []
videoLinks:
  - "https://www.youtube.com/embed/Se1Anm1-yaw"
views: 14042