วันเสาร์ที่ 23 เมษายน พ.ศ. 2554

ฆ้อง MIDI ด้วย Chuck

แนวคิดหนึ่งที่อยากทำมานานก็คือพัฒนาโปรแกรมที่ช่วยให้นักดนตรีไทยประพันธ์เพลงได้สะดวกขึ้น ทางหนึ่งที่ทำได้ก็คือเขียนโปรแกรมรับข้อมูล MIDI จากคีย์บอร์ด แล้วสังเคราะห์เสียงตามคำสั่ง

Chuck มีความสามารถในการสื่อสารกับเครื่องดนตรีอิเล็กทรอนิกส์ทาง MIDI โดยใน Chuck จะมีคลาส MIDI ในตัว การเรียกใช้ความสามารถทาง MIDI ใน Chuck ทำได้ดังตัวอย่าง (จาก Official Chuck Tutorial)

MidiIn min;
MidiMsg msg;

// open midi receiver, exit on fail
if ( !min.open(0) ) me.exit(); 

while( true )
{
    // wait on midi event
    min => now;

    // receive midimsg(s)
    while( min.recv( msg ) )
    {
        // print content
     <<< msg.data1, msg.data2, msg.data3 >>>;
    }
}

จากการทดลองพบว่า
  • msg.data1 คือข้อมูล Channel และคำสั่ง MIDI อื่น ๆ เช่น Note-On, Note-Off
  • msg.data2 คือขั้นเสียง
  • msg.data3 คือความดัง (Velociy)
เราสามารถใช้ msg.data# มาควบคุมการสังเคราะห์เสียงของ Chuck ที่เราทำไว้เมื่อคราวก่อนได้ดังนี้

MidiIn min;
MidiMsg msg;
min.open(0) => int AmIOpen;

if(!AmIOpen)
{
 <<< "Can not Open MIDI Input Port" >>>;
 me.exit();
}else
{
 while(true)
 {
  min => now;
  while(min.recv(msg))
  {
   <<< msg.data1,msg.data2,msg.data3,"MIDI Message">>>;
   if(msg.data3!=0)
   {
    if(msg.data2==59)
    {
     Machine.add("gong01.ck");
    }
    if(msg.data2==60)
    {
     Machine.add("gong02.ck");
    }
   }
  }
 }
}

เราใช้ msg.data# เป็นเงื่อนไขให้ Chuck เพิ่ง shred ของฆ้องแต่ละลูกเข้าไปใน Chuck ได้ด้วยคำสั่ง

Machine.add("gong01.ck");

เมื่อ gong01.ck gong02.ck ... เป็นสคริปต์สังเคราะห์เสียงฆ้องแต่ละลูก แต่นี่หมายความว่าที่เครื่องคอมพิวเตอร์จะต้องรัน Chuck อยู่แล้วด้วยคำสั่ง

# chuck --loop

ด้วยวิธีนี้ Chuck จะเป็นคนจัดการเรื่องการสังเคราะห์เสียงหลาย ๆ เสียงพร้อม ๆ กันได้เอง เราไม่ต้องเขียนโปรแกรมขึ้นมาใหม่เลย นั่นหมายความว่าหากเราสามารถสร้างสคริปต์สังเคราะห์เสียงเครื่องดนตรีไทยชนิดต่าง ๆ ได้ ชุดโปรแกรมชุดนี้ก็สามารถรับสัญญาณ MIDI มาตรฐานจากโปรแกรมทั่ว ๆ ไปและสร้างเสียงดนตรีไทยทั้งวงได้ทันที

วันพฤหัสบดีที่ 21 เมษายน พ.ศ. 2554

การสังเคราะห์เสียงด้วย Chuck

ก่อนหน้านี้ได้ลองใช้ Puredata (pd) สังเคราะห์สัญญาณ Decay Sinusoid ซึ่งมีลักษณะเสียงคล้ายเสียงฆ้อง ตั้งใจว่าจะใช้สังเคราะห์เสียงดนตรีไทย แต่ว่าการใช้งาน Python ร่วมกับ Puredata แม้ว่าจะทำได้ แต่ดูเหมือนว่าจะทำได้ยาก เนื่องจากมีเอกสารกำกับน้อย น่าจะเหมาะกับคนที่ใช้ Python จนคล่องแคล่วแล้วมากกว่า

ผมก็เลยกลับมาพิจารณา Chuck อย่างจริงจังอีกครั้งหนึ่งหลังจากที่ได้เอาคู่มือมาพลิกผ่าน ๆ เมื่อหลายเดือนก่อนพบว่าน่าจะสามารถใช้งานร่วมกับ Python ได้ง่ายกว่า

Chuck คืออะไร
Chuck คือ Real-Time Audio Programming Language สำหรับงานสังเคราะห์และเรียบเรียงเสียงสังเคราะห์ โปรแกรมที่เขียนด้วย Chuck จะรันใน Chuck Virtual Machine ที่มีความสามารถในการรันหลาย ๆ โปรแกรม (Shred) ได้พร้อม ๆ กัน และแต่ละ Shred ก็สามารถสื่อสารเพื่อประสานงานกันได้ ท่านที่สนใจสามารถอ่านรายละเอียดเพิ่มเติมได้ที่ http://chuck.cs.princeton.edu/ และ http://kijjaz.exteen.com/20070702/entry (ภาษาไทย)

ตัวอย่างสคริปต์ที่รันใน Chuck

SinOsc s => dac;
440 => s.freq;
0.5 => s.gain;
2::second => now;

สคริปต์ข้างต้นนี้จะกระทำ 2 สิ่งคือ ต่อไซนูซอยด์ออสซิลเลเตอร์ (s) เข้ากับซาวด์การ์ด (dac) ตั้งค่าความถี่และขนาดสัญญาณเป็น 440 เฮิรทซ์และ 0.5 ตามลำดับ จากนั้นให้เวลาในสคริปต์เดินหน้าไป 2 วินาทีส่งผลให้เกิดเสียงขึ้น 2 วินาที

เรื่องเวลานี่ทีแรกผมไม่เข้าใจ แต่ถ้าเราเปรียบเทียบกับการเดินหน้าเส้นเทปไป 2 วินาทีก็จะเข้าใจง่ายขึ้น สัญญาณเสียงนั้นอยู่ในเทปอยู่แล้ว แต่ถ้าเส้นเทปไม่เดินผ่านหัวเทปก็จะไม่เกิดเสียงขึ้น

หลังจากเขียนสคริปต์เสร็จแล้วก็บันทึกไว้ในแฟ้มชื่อ foo.ck ก็ได้ (ตามขั้นตอนใน Tutorial เป๊ะ ๆ) ถ้าจะให้ Chuck ทำงานก็เรียกคำสั่ง

# chuck foo.ck

ก็จะได้ยินเสียงไซนูซอยด์ดังออกมาจากลำโพง

นอกจากนี้โปรแกรม Chuck ยังมี Built-in ฟังก์ชันและออบเจกต์ที่ใช้ประมวลผลสัญญาณเสียงอีกมากมาย ที่ใช้บ่อย ๆ ก็คงจะเป็นพวกตัวกรองต่าง ๆ ในงานที่ผมใช้สังเคราะห์เสียง Decay Sinusoid ที่มีเสียงคล้ายเสียงฆ้องนั้น เมื่อพิจารณาแล้วสามารถใช้ตัวกรองแบบ Bi-Quad ได้ การใช้ตัวกรอง Bi-Quad ใน Chuck สามารถทำได้ดังนี้

Impulse i => BiQuad f => dac;
0.000 => f.b0;
0.04128430 => f.b1;
0.000 => f.b2;
1.0000 => f.a0; 
-1.99824581 => f.a1;
0.99995098 => f.a2;

0.3 => i.next;
5::second => now;

ในสคริปต์นี้
  • บรรทัดแรกจะเรียกใช้ออบเจกต์อิมพัลส์และเชื่อมต่ออิมพัลส์เข้ากับตัวกรอง Bi-Quad และต่อเอาต์พุตจาก Bi-Quad เข้ากับซาวด์การ์ดของเรา 
  • บรรทัดที่ 2 - 7 ระบุค่า Coefficient ของตัวกรองทั้ง 6 ตัว 
  • บรรทัดที่ 8 ระบุขนาดของอิมพัลส์ที่จะส่งเข้าตัวกรอง
  • บรรทัดที่ 9 ระบุช่วงเวลาที่จะให้ Chuck ส่งเสียงออกมา
นี่คือเสียงสังเคราะห์ของฆ้องวงเล็กลูกแรก ผมบันทึกแฟ้มเป็น gong01.ck ถ้าต้องการให้ Chuck สร้างเสียงฆ้องลูกนี้ก็เรียกใช้คำสั่ง

# chuck gong01.ck

ก็จะได้ยินเสียงคล้าย ๆ เสียงฆ้องดังออกมา

จากที่ทดลองพบว่าการใช้ Chuck นั้นง่ายพอ ๆ กับ puredata แต่ว่าถ้าคิดถึงการเชื่อมต่อกับโปรแกรมอื่น ๆ เพื่อให้ทำงานร่วมกันหลังฉากโดยไม่ต้องให้ผู้ใช้งงกับหน้าต่างมาก ๆ โปรแกรม Chuck ดูเหมือนจะเป็นทางเลือกที่ดีกว่า ต่อไปนี้ก็จะลองใช้ Chuck เพื่อสังเคราะห์เสียงอื่น ๆ ต่อไปดู

วันอังคารที่ 5 เมษายน พ.ศ. 2554

ติดตั้งเครื่องพิมพ์ Canon LBP3050 บน Debian (wheezy)

[ T_T สุดท้ายก็ไม่สามารถพิมพ์ภาพได้ครับ นอกจากนี้เมื่อตอนทดสอบยังไม่ได้ทดสอบกับภาษาไทย พอทดสอบกับภาษาไทยก็ยิ่งแย่ ตัวหนังสือตีกันไปหมด ตอนนี้ยกธงขาวแล้ว แฟนจะเหน็บก็ต้องยอมแล้วล่ะ]

ที่บ้านมีเครื่องพิมพ์อยู่เครื่องหนึ่งคือ Canon LBP3050 เป็นของแฟน พอจัดห้องทำงานที่บ้านใหม่แฟนก็ยกเครื่องพิมพ์เครื่องนี้ให้ แต่ว่าเวลาเอามาใช้กับเครื่องคอมพ์ของผมดันพิมพ์งานไม่ออก!! ทั้ง ๆ ที่ Linux ตรวจเจอเครื่องพิมพ์

งานเข้าล่ะครับ เพราะแฟนผมชอบเหน็บให้เจ็บใจเล่น ๆ เวลาที่ Linux ทำอะไรบางอย่างไม่ได้ในขณะที่ Windows ของเธอทำได้ (ผมก็เหน็บเธอเหมือนกันเวลาที่ Windows ทำอะไรบางอย่างไม่ได้ในขณะที่ Linux ทำได้ ก็เจ๊ากันไปนะ ฮิ ฮิ)

ผมพบว่าระบบตรวจจับเครื่องพิมพ์อัตโนมัติของ Linux ใช้ไม่ได้กับเครื่องพิมพ์บางจำพวก และเจ้า Canon LBP3050 เครื่องนี้ก็เป็นหนึ่งในนั้นครับ ในกรณีของ Canon LBP3050 นั้นผมคิดว่าเป็นเพราะ LBP3050 เป็นเครื่องพิมพ์ที่ใช้เทคโนโลยี CAPT - Canon Advanced Printing Technology ซึ่งทาง Canon โฆษณาว่าสามารถพิมพ์งานได้เร็ว และพิมพ์งานที่ขนาด (หน่วยความจำ) ใหญ่ได้โดยไม่ต้องเพิ่มหน่วยความจำ ... เอาเถอะ ... จะเป็นอะไรก็ช่าง เอาเป็นว่าถ้าจะใช้เครื่องพิมพ์ตระกูล CAPT จะต้องมี CAPT Driver ไม่ว่าจะเป็นบน Linux Mac หรือ Windows ก็ตาม ทีนี้ในระบบ Linux เราใช้ CUPS - Common UNIX Printing Solution ก็แปลว่าเจ้า CAPT Driver ที่ว่าจะต้องคุยกับ CUPS ให้รู้เรื่อง และการทำให้มันคุยกันรู้เรื่อง ก็ต้องใช้การติดตั้งที่ถูกต้องจึงจะสำเร็จ

การติดตั้งมีด้วยกัน 4 ขั้นตอนคือ
  1. จัดหาแพคเกจที่จำเป็น
  2. ติดตั้งแพคเกจดังกล่าว
  3. ติดตั้งเครื่องพิมพ์ลงบน CUPS
  4. ผูกเครื่องพิมพ์เข้ากับ Service ccpd
  5. ใช้ insserv สั่งให้เริ่มบริการ ccpd โดยอัตโนมัติทุกครั้งที่มีการเริ่มระบบใหม่
  6. เรียกใช้ ccpd เพื่อใช้งานเครื่องพิมพ์
รายละเอียดการติดตั้งมีอยู่ในแฟ้มเอกสารที่มาพร้อมกับแพคเกจที่จำเป็น แต่จะขอบันทึกไว้เผื่อว่าจะได้ใช้ในภายหลัง โดยจะเขียนอธิบายตามรายการข้างต้นไปทีละข้อ ๆ

จัดหาแพคเกจที่จำเป็น
ดาว์นโหลดได้ที่เว็บไซต์ Canon ออสเตรเลียเลยครับ (ค้น ๆ ดูมีที่สิงค์โปร์ด้วย ส่วนเมืองไทยไม่มีนะครับ) แฟ้มที่โหลดได้คือ CAPT_Printer_Driver_for_Linux_v220_uk_EN.tar.gz ขนาดประมาณ 33 MB
เมื่อได้มาแล้วก็แตกแฟ้มไว้ที่ไหนสักแห่ง ซึ่งต่อไปจะขอเรียกไดเรคทอรีนี้ว่า #Canon เพื่อความสะดวก

ติดตั้งแพคเกจดังกล่าว
เมื่อแตกแฟ้มแล้วจะพบแพคเกจ
  • cndrvcups_common_2.20-1_i386.deb  และ
  • cndrvcups_capt_2.20-1_i386.deb
อยู่ในไดเรคทอรี #Canon/CAPT_Printer_Driver_for_Linux_v220_uk_EN/32-bit_Driver/Debian ติดตั้งแพคเกจตามลำดับที่แสดงไว้ข้างต้น โดยใช้ GDebi หรือ dpkg ก็ได้ ในกรณีของผมเองปัจจุบันใช้ Debian wheezy อยู่ จะขาดแพคเกจที่จำเป็นไปหนึ่งแพคเกจคือ gs-eps ผมก็แก้ไขโดยการเพิ่ม Repository ของ squeeze ลงไปใน sourcelist ก็พบแพคเกจ gs-eps ซึ่งเป็น Transition Package อยู่ ติดตั้งแพคเกจนี้เข้าไปก็ใช้ได้แล้ว

ติดตั้งเครื่องพิมพ์ลงบน CUPS
เมื่อติดตั้งแพคเกจทั้งสองเสร็จสิ้นให้เริ่ม CUPS ใหม่ซึ่งทำได้โดยใช้คำสั่ง

# /etc/init.d/cups restart

จากนั้นก็เพิ่มเครื่องพิมพ์ลงในคิวของ CUPS ด้วยคำสั่ง

#  lpadmin -p LBP3050 -m CNCUPSLBP3050CAPTK.ppd -v ccp://localhost:59687 -E

เมื่อข้อมูลหลังตัวเลือกต่าง ๆ มีความหมายดังนี้
  • -p [Printer Name] ระบุชื่อที่ระบบเราจะใช้เรียก จะตั้งว่าอะไรก็ได้
  • -m [ppd file] ระบุแฟ้ม ppd ที่เป็นตัว Driver ของเครื่องพิมพ์ของเรา ในรายละเอียดการติดตั้งที่ให้มาพร้อมกับแพคเกจจะบอกว่าเครื่องพิมพ์ของเราจะต้องใช้แฟ้ม ppd ตัวไหน ส่วนรายการเครื่องพิมพ์สำหรับ wheezy จะอยู่ที่ /usr/share/cups/model
  • -v [Device URI] ระบุ URI ที่เราจะให้เป็นทางผ่านข้อมูลสำหรับเครื่องพิมพ์ของเรา URI ที่เขียนไว้ตรงนี้ลอกมาจากรายละเอียดการติดตั้ง จากที่ค้น ๆ ดู ในกรณีทั่ว ๆ ไป ใคร ๆ ก็ใช้ URI นี้ มีบางกรณีของ UBUNTU ที่จะใช้ URI เป็น FIFO0 อันนี้ยังไม่ได้ลองว่าใช้ได้หรือไม่
  • -E เป็นการสั่ง Enable เครื่องพิมพ์
กระบวนการทั้งหมดนี้น่าจะสามารถทำด้วย GUI ได้ด้วย ระบบ->ดูแลระบบ->เครื่องพิมพ์ เพื่อเรียกเครื่องมือตั้งค่าเครื่องพิมพ์ของ GNOME โดยที่ตัวเลือก -E ข้างหลังสุดของคำสั่งนั้นคือการ Enable เครื่องพิมพ์นั่นเอง [ยังไม่ได้ลอง]

หลังจากนี้ถ้าเราเปิดเครื่องมือการตั้งค่าเครื่องพิมพ์ของ GNOME ดู ควรจะเห็นเครื่องพิมพ์ของเราปรากฏอยู่ในรายการด้วย ดังเช่นที่แสดงในรูปนี้ (เราต้องรู้ชื่อของเครื่องพิมพ์ของเรา เพราะเราเป็นคนตั้งชื่อเองด้วยตัวแปรหลังตัวเลือก -p ของคำสั่ง lpadmin)
ตอนนี้ยังใช้เครื่องพิมพ์ไม่ได้นะครับ 
[ตามความเข้าใจของผม] เพราะเราใช้ CAPT ซึ่งมีการ "ยำ" ข้อมูลก่อนส่งออกเครื่องพิมพ์ ดังนั้นถ้าเราส่งไปตรง ๆ เหมือนส่งเข้าเครื่องพิมพ์ประเภทอื่น ๆ เครื่องพิมพ์ก็จะไม่เข้าใจ จึงจำเป็นต้องมีข้อต่อไป

ผูกเครื่องพิมพ์เข้ากับ Service ccpd 
ซึ่งทำได้โดย

# ccpdadmin -p LBP3050 -o /dev/usb/lp0

นี่เป็นการบอก ccpd ว่าเครื่องพิมพ์ CUPS ชื่อ LBP3050 นั้นต่ออยู่กับช่องทางไหน ทีนี้ในคู่มือการติดตั้งเตือนไว้เหมือนกันว่า ถ้ามีเครื่องพิมพ์ USB หลายเครื่อง ลำดับก่อนหลังของ lp จะเรียงตามลำดับการต่อเครื่องพิมพ์เข้ากับคอมพิวเตอร์ ในอินเตอร์เนตมีคำแนะนำการตั้งค่าบริการ ccpd ให้ตั้งค่าเองเมื่อเสียบสาย USB ของเครื่องพิมพ์เข้ากับคอมพิวเตอร์ (แต่เป็นของ UBUNTU นะ) พอดีผมมีเครื่องพิมพ์เครื่องเดียวและต่ออยู่ตลอดเลยไม่สนใจสถานการณ์ที่ว่านี้

หลังจากนี้ ถ้าเริ่มบริการ ccpd ก็จะสามารถพิมพ์ได้เลย การเริ่มบริการ ccpd ทำได้โดย

# /etc/init.d/ccpd start

หรือไม่ก็ใช้ ระบบ->ดูแลระบบ->บริการระบบ เริ่มบริการ ccpd ก็ได้ ถ้าอยากให้บริการ ccpd เริ่มเองทุกครั้งก็ไปขั้นตอนถัดไป

ใช้ insserv สั่งให้เริ่มบริการ ccpd โดยอัตโนมัติทุกครั้งที่มีการเริ่มระบบใหม่
ในการนี้เราจะต้องแก้ไขแฟ้ม /etc/init.d/ccpd โดยการเพิ่มคอมเมนต์ต่อไปนี้เข้าไปในบรรทัดที่สามของ ccpd คอมเมนต์ที่จะเติมนี้ขอแนะนำว่าให้ไปคัดลอกจากต้นฉบับที่คู่มือการติดตั้ง ซึ่งอยู่ใน #Canon/CAPT_Printer_Driver_for_Linux_v220_uk_EN/Doc โดยตรงจะดีที่สุด

### BEGIN INIT INFO
# Provides:         ccpd
# Required-Start:   $local_fs $remote_fs $syslog $network $named
# Should-Start:     $ALL
# Required-Stop:    $syslog $remote_fs
# Default-Start:    3 5
# Default-Stop:     0 1 2 6
# Description:      Start Canon Printer Daemon for CUPS
### END INIT INFO
ผมก็สงสัยว่ามันเป็นคอมเมนต์ แล้วไปเติมมันทำไม เลยเข้าไปอ่านรายละเอียดของ insserv จึงได้ทราบว่าเจ้า insserv นี่จะเข้าไปอ่านคอมเมนต์ในสคริปต์แล้วเติมบริการนี้เข้าไปในรายการที่จะต้องเริ่มอัตโนมัติตอนเริ่มระบบ แต่ว่าแค่เติมคอมเมนต์นี่เข้าไปยังไม่พอ จะต้องเรียกคำสั่ง

# insserv ccpd

ด้วย ในระบบของผมมี Warning ขึ้นมานิดหน่อย แต่อ่าน ๆ ดูแล้วคิดว่าไม่สำคัญมาก ก็ปล่อยผ่านไป ทีนี้ถ้าท่านค้นข้อมูลจากอินเตอร์เนตท่านอาจจะพบวิธีการแก้ไขสคริปต์ที่ว่านี้หลาย ๆ แบบ ผมก็ลองแล้วปรากฏว่าไม่เวิร์ก อาจจะด้วยหลาย ๆ เหตุผล เช่นผมคัดลอกสคริปต์มาไม่สมบูรณ์หรืออะไรก็สุดแท้แต่ สุดท้ายผมมาใช้สคริปต์ต้นฉบับตามคู่มือของ Canon เองนั่นแหละ ถึงได้พิมพ์ได้ในที่สุด

ถึงขั้นตอนนี้ถ้าทั้ง cups และ ccpd กำลังทำงานอยู่ เราก็สามารถพิมพ์งานได้เลย

ระบบ CAPT ให้เครื่องมือกับเรามาอีก 2 อย่างก็คือ
  • captstatusui และ
  • cngplp (ผมเดาว่าคงจะเป็น Canon General Purpose lp)
# captstatusui -P LBP3050

สั่งงานพิมพ์จากบรรทัดคำสั่ง (แทน lpr) ได้โดย

# cngplp -p [Filename to be printed]


หมายเหตุ
ในคู่มือระบุว่าระบบ CAPT จะทำงานได้สมบูรณ์จำเป็นจะต้องติดตั้ง libstdc++5 เสียก่อน แม้ว่าระบบของผมมี libstdc++6 อยู่แล้ว แต่เพราะผมไม่ทราบแน่ชัดว่าใช้ได้หรือไม่ จึงติดตั้ง libstdc++5 ลงไปอยู่ดี

ที่พิมพ์ได้ตอนนี้คือ PS PDF และ ODT ที่ไม่มีรูปภาพ :o ตอนนี้กำลังหาทางอยู่ว่าถ้าจะพิมพ์รูปด้วยจะต้องทำอย่างไร :(

ที่ต้องลำบากลำบนขนาดนี้ เป็นเพราะว่าต้องพยายามใช้เครื่องที่มีอยู่ในเกิดประโยชน์ หากเป็นการพิจารณาซื้อเครื่องใหม่ ท่านอาจมีตัวเลือกที่สะดวกกว่าโดยเฉพาะเครื่องพิมพ์ที่มีรายการ Driver อยู่ใน CUPS อยู่แล้วเป็นต้น