วันเสาร์ที่ 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 อยู่แล้วเป็นต้น

วันเสาร์ที่ 26 กุมภาพันธ์ พ.ศ. 2554

การสร้างสัญญาณ Decaying Sinusoid ด้วยตัวกรองดิจิตอลโดยใช้ pd

แบบจำลองของเสียงจากเครื่องดนตรีแบบหนึ่งคือ Decaying Sinusoid ซึ่งก็คือสัญญาณตระกูลไซนูซอยด์ที่มีขนาดลดลงด้วยอันดับเอกโพเนนเชียลเมื่อเวลาผ่านไป สัญญาณลักษณะนี้สามารถเขียนเป็นสมการได้
amath y(t) = A*e^{-b*t}*sin( omega_{c} * t ) endamath
เมื่อ amath A endamath คือขนาดของสัญญาณ amath a endamath คืออัตราการลดทอน amath omega_{c} endamath คือความถี่มีหน่วยเป็น rad/sec และ amath t endamath คือเวลา
สัญญาณ Decaying Sinusoid (เส้นสีแดงและเส้นสีเขียวเป็นเพียงเส้นนำสายตาเท่านั้น)
กลยุทธ์การสังเคราะห์เสียงลักษณะนี้ก็คือการใช้ตัวกรองที่มีผลตอบสนองอิมพัลส์ (Impulse Response) ที่มีหน้าตาแบบนี้เลย คือถ้าใส่อิมพัลส์เข้าไป ให้ตัวกรองส่งสัญญาณหน้าตาแบบนี้ออกมา ซึ่งเราสามารถสร้างตัวกรองดังกล่าวได้โดยใช้กรรมวิธีการออกแบบตัวกรองดิจิตัลแบบอิมพัลส์ไม่เปลี่ยนแปลง (Impulse Invariant Method)

ด้วยวิธี Impulse Invariant Method เราสามารถเขียนผลตอบสนองของตัวกรองดิจิตัลในรูปแบบสัญญาณดิสครีตได้ดังนี้
amath y(n) = A*e^{-k*n}*sin(omega_{d}*n) endamath
เมื่อ amath k endamath เป็นอัตราลดทอนที่แปลงมาจาก amath b endamath amath omega_{d} endamath เป็นความถี่ในโดเมนดิสครีตมีหน่วยเป็น rad/sample และ amath n endamath คือ index ของสัญญาณดิสครีต

ตัวกรองที่มีผลตอบสนองอิมพัลส์เป็นสัญญาณนี้คือตัวกรองที่มี Transfer Function - H(z) เป็น z-Transform ของสัญญาณนี้นั่นเอง ซึ่งจากตารางการแปลง z ทำให้เราทราบว่า
amath A*e^{-k*n}*sin(omega_{d}*n) -> (e^{-k}*sin(omega_{d})*z^{-1})/(1-2*e^{-k}*cos(omega_{d})*z^{-1}+e^{-2*k}*z^{-2}) endamath
เราสามารถสร้างตัวกรองนี้ใน puredata ได้โดยตรงโดยใช้ออบเจ็ค fexpr~ สร้างสมการผลต่าง (Difference Equation) ไดัเป็น
amath y(n) = e^{-k}*sin(omega_{d})*x(n-1)+2*e^{-k}*cos(omega_{d})*y(n-1)-e^{-2*k}y(n-2) endamath
และต่อเป็นแพตช์ใน puredata ได้ดังรูป

ซึ่งเราสร้างขึ้นมาเป็น sub-patch ให้รับอินพุตได้ 4 ค่าคือ
  1. อิมพัลส์ (x1 ใน fexpr~) เป็นตัวกระตุ้นตัวกรอง ซึ่ง puredata patch สำหรับอิมพัลส์สามารถดูได้จากผลงานเมื่อคราวก่อน
  2. อัตราการลดทอน (f2 ใน fxpr~ - หมายถึงอินพุตที่ 2 และเป็นตัวแปรประเภท Float) amath e^{-k} endamath
  3. ความถี่ในโดเมนดิสครีต หน่วยเป็น rad/sample (ระบุผ่าน f3 - amath cos(omega_{d}) endamath และ f4 - amath sin(omega_{d}) endamath)
  4. อัตราชักตัวอย่าง (Sampling Rate -fs) ไว้ใช้คำนวณค่า amath omega_{d} endamath ก่อนส่งเข้าไปใน fexpr~
ในโอกาสต่อไปจะนำเสนอการนำ Decaying Sinusoid ไปสร้างเสียงสังเคราะห์ที่ซับซ้อนมากขึ้น

วันอังคารที่ 8 กุมภาพันธ์ พ.ศ. 2554

การสร้าง Impulse (Discrete) ใน pd เพื่อใช้ทดสอบตัวกรอง

ผมหัดใช้ pd มาได้สักพักแล้ว ตอนนี้กำลังทำโครงงานที่สอง (เลยนึกได้ว่าไม่เคยบันทึกโครงงานแรกลงในบล๊อกเลย) ในโครงงานนี้เราจะสร้างตัวกรอง (Filter) เพื่อสังเคราะห์สัญญาณเสียงขึ้นมา ซึ่งตัวกรองจะทำงานได้ต้องมีอินพุต และอินพุตที่เราจะใช้นั้นเป็นอิมพัลส์ (Impulse) ในดิสครีตโดเมน

ใน pd-extended นั้นจะมีออบเจ็ค dirac~ ให้ใช้อยู่ แต่เนื่องจากผมใช้เดเบียนซึ่งใน Repository มีแต่ pd-vanilla ซึ่งไม่มีออบเจ็ค dirac~ ให้ใช้ ก็ต้องสร้างเอง

การสร้างได้แนวทางมาจาก http://www.mail-archive.com/pd-list@iem.at/msg12771.html และหลังจากลองผิดลองถูกมาได้สักพัก ก็สามารถสร้าง Discrete Impulse ขึ้นมาจนได้ดังรูป
หลักการทำงานก็คือ
  1. รับข้อความ \$1 0 มาจาก inlet เมื่อ \$1 เป็นตัวแปรที่ส่งเข้ามาเพื่อใช้เป็นขนาดของอิมพัลส์
  2. ส่งข้อความไปให้ vline~ สร้างสัญญาณเป็น \$1 ทันทีที่ได้รับข้อความ
  3. หลังจากนั้น ข้อความ \$1 0 จะส่งทริกเกอร์ไปยังการคำนวณคาบเวลา T=1/fs (ในที่นี้ใช้ 1.5/fs แทน เผื่อไว้กันเหนียว)
  4. สร้างข้อความ 0 0 \$1 เมื่อ \$1 ระยะเวลาเป็นหน่วยมิลลิวินาทีที่รอก่อนที่ vline~ จะสั่งให้ตั้งค่าเป็นศูนย์ เราพยายามจะตั้งให้ได้ 1 Sample=1/fs พอดี แต่เพื่อผลที่แน่นอนใน Patch นี้เราเลยเลือกใช้เป็น 1.5/fs แทน
  5. ส่งข้อความไปให้ vline~ เพื่อตั้งค่าให้เป็น 0
  6. ส่งออกเป็น Stream ไปที่ outlet~
ทดลองแล้วใช้ได้ผลดี ถ้าต้องการตรวจสอบผล ให้เพิ่มออบเจ็ค print~ ไปที่เอาต์พุตของ vline~ และให้รับ bang มาจากข้อความไหนก็ได้ใน Patch ด้วย จะเห็นผลลัพธ์ดังรูปต่อไปนี้
ต่อไปนี้ก็สามารถทดสอบตัวกรองต่าง ๆ ได้ ซึ่งถ้ามีอะไรน่าสนใจก็จะนำมาเสนอต่อไป

วันอังคารที่ 18 มกราคม พ.ศ. 2554

การเขียนวิทยานิพนธ์ ม.ข. ด้วย LaTeX - สร้างสารบัญ

ข้อกำหนดหนึ่งของวิทยานิพนธ์บัณฑิตศึกษา มหาวิทยาลัยขอนแก่นคือสารบัญ
ข้อกำหนดคือ
  1. สารบัญ และส่วนประกอบอื่น ๆ เช่น กิตติกรรมประกาศ มีศักดิ์ศรีเทียบเท่าบท ดังนั้นหัวข้อของสารบัญจึงใช้หัวข้อของบท แต่ไม่ต้องมีเลขบท คือเป็นตัวอักษรแบบตัวหนาขนาด 18 พอยต์ (Bold 18 points) อยู่กึ่งกลางหน้ากระดาษ
  2. หากสารบัญมีมากกว่าหนึ่งหน้า หน้าต่อ ๆ ไปของสารบัญจะต้องขึ้นหัวข้อว่า "สารบัญ (ต่อ)" ด้วยอักษรตัวหนาขนาด 18 พอยต์เช่นเดียวกัน (ข้อนี้ไม่มีคำสั่งเป็นลายลักษณ์อักษร แต่ตัวอย่างที่มีให้เป็นแบบนี้ และวิทยานิพนธ์ทุก ๆ เล่มที่ผมเข้าถึงได้ ก็เป็นแบบนี้)
  3. เนื้อสารบัญควรบอก บทที่ ชื่อบท และเลขหน้าของบทนั้น ๆ เริ่มตั้งแต่บทคัดย่อ จนไปจบที่ประวัติผู้เขียนเป็นรายการสุดท้าย ใช้อักษรแบบธรรมดาขนาด 14 พอยต์ (Normal 14 points)
  4. หัวข้อย่อยของแต่ละบทนั้น แสดงลึกไม่เกิน 1 ระดับ คือสารบัญจะแสดงรายละเอียดถึง 1.1 หมายถึง บทที่ 1 หัวข้อที่ 1 เท่านั้น จะไม่แสดงรายละเอียดถึง 1.1.1
  5. หัวข้อย่อยควรจัดให้ตรงกับบท (ไม่บังคับ แต่ตัวอย่างเป็นแบบนี้)
  6. เลขลำดับของหัวข้อย่อย จะมีหรือไม่มีก็ได้
  7. ระยะห่างระหว่างชื่อบท หรือหัวข้อสารบัญ ถึงบรรทัดแรกของสารบัญ ให้เว้น 2 บรรทัด (ของอักษรขนาดกี่พอยต์?)
ค้นไปค้นมา พบว่าไม่ต้องเขียนอะไรมาก เอา แพคเกจที่มีคนทำไว้แล้วมาใช้ก็ได้ แพคเกจที่หยิบมาใช้คือ titletoc ซึ่งติดตั้งมาพร้อมกับ titlesec ซึ่งอยู่ในแพคเกจเดเบียน texlive-latex-extra

สิ่งที่ต้องใช้ใน preemble ก็คือ เรียกคลาสที่จำเป็นขึ้นมา
\RequirePackage{titletoc}
\RequirePackage{ifthen}
\RequirePackage{lineno}

ตั้งค่าความลึกของหน้าสารบัญใหม่เป็น 1 (คือลงลึกแค่ชั้นเดียว) และตั้งค่าคำสั่ง \tableofcontents ใหม่
\setcounter{tocdepth}{1}
\renewcommand\tableofcontents{%
% Header of the first page of the TOC
\chapter*{\contentsname\@mkboth{\MakeUppercase\contentsname}
{\MakeUppercase\contentsname}}%
\begin{flushright}
\textbf{หน้า}
\end{flushright}

% Counter for tocitem
\newcounter{tocitem}
\setcounter{tocitem}{0}

% Turn on the linenumber counts, but do not want to display it.
\modulolinenumbers[50]
\linenumbers[1]
\@starttoc{toc}%
\setcounter{tocitem}{0}
\clearpage
}

สร้างคำสั่งใหม่สำหรับตรวจสอบว่าบรรทัดปัจจุบัน หรือ จำนวนหัวข้อที่แสดงในสารบัญเกินค่าทึ่กำหนดหรือยัง หากเกินแล้วให้ตัดขึ้นหน้าใหม่ รีเซตตัวนับ tocitem รีเซตตัวนับบรรทัดใหม่ และเขียนหัวของหน้าสารบัญใหม่ (เช่น สารบัญ (ต่อ) ในกรณีนี้)
\newcommand{\tocheadcont}[3]{
\ifthenelse{#1>32 \OR #2>32}{
%Should be a newpage here.
\pagebreak[4]
\resetlinenumber[1]
\setcounter{tocitem}{0}
\begin{ċenter}
{\Large \textbf{#3 (ต่อ)} }
\end{center} 
\begin{flushright}
\textbf{หน้า}
\end{flushright}
}{}
}

การจัดรายการสารบัญโดยใช้คำสั่ง \titlecontents จากแพคเกจ titletoc
% tocitem format for chapter
\titlecontents{chapter}[0mm]
{\stepcounter{tocitem}\tocheadcont{\thelinenumber}{\thetocitem}{สารบัญ}}
{\chaptername\hspace{1ex}\thecontentslabel\hspace{2mm}}
{}{\hfill \contentspage}

% tocitem format for section
\titlecontents{section}[0mm]
{\stepcounter{tocitem}\tocheadcont{\thelinenumber}{\thetocitem}{สารบัญ}}
{\hspace{\firstindentlength}\thecontentslabel\hspace{2mm}}{\hspace{\firstindentlength}\thecontentslabel\hspace{2mm}}
{\hfill \contentspage}

ผลลัพธ์ที่ได้แสดงในรูป


กล่าวโดยสรุปเราใช้
  • \titlecontents จากแพคเกจ titletoc เพื่อจัดรูปแบบของรายการสารบัญ
  • \thelinenumber จากแพคเกจ lineno เพื่อนับบรรทัดในหน้า
  • \newcounter{tocitem} สร้างตัวนับรายการสารบัญ
  • \ifthenelse จากแพคเกจ ifthen เพื่อตรวจเงื่อนไข
อันที่จริงเราอาจใช้ทางอื่นในการสร้างหัวสารบัญในหน้าถัดไปได้ เช่นเอาหัวสารบัญไปใส่ในส่วนหัวของหน้าแล้วจัด Margin ใหม่ก็ได้ แต่ผมไม่ได้คิดถึงทางออกนี้จนกระทั่งแก้ปัญหามาทางนี้เกือบเสร็จแล้ว หากใครมีทางเลือกอื่น ๆ เพื่อจัดหน้าสารบัญสำหรับวิทยานิพนธ์ ม.ข. ก็จะขอแบ่งประสบการณ์ด้วยครับ

วันอาทิตย์ที่ 12 ธันวาคม พ.ศ. 2553

3BB กับ Debain

วันนี้ติดตั้ง 3BB ที่บ้านเรียบร้อย บริการรวดเร็วทันใจ ผู้รับเหมาที่มาติดตั้งก็นิสัยดีมาก คือทำงานไม่ชุ่ยและแสดงให้เห็นว่าพยายามจะให้ผลงานออกมาเรียบร้อยถูกใจลูกค้า

สมัครเมื่อวาน ติดตั้งเสร็จวันนี้ เร็วทันใจดีจริง

แต่เอ...คู่มือที่ให้มามีแต่สำหรับ Window นะนี่ อ่ะ ไม่เป็นไรระบบพอจะคุ้น ๆ อยู่

ระบบที่เขามาติดตั้งให้จะมีลักษณะอย่างนี้ครับ คือช่างเขาจะพ่วงสายทองแดงจากจุดเชื่อมต่อนอกบ้านเข้ามาในบ้าน พอเข้ามาในบ้านแล้วก็จะเป็นเส้นเล็ก ๆ คล้าย ๆ สายโทรศัพท์ (ผมว่ามันคือสายโทรศัพท์จริง ๆ นั่นแหละ เส้นหนึ่งแดง เส้นหนึ่งเขียว) จากสายโทรศัพท์จะต่อเข้ากล่องดำ ๆ กล่องหนึ่ง ช่างบอกว่าเรียกว่าเราเตอร์ จากเราเตอร์จะมีสายแลนต่อออกมาจากเราเตอร์เข้ามาที่การ์ดเน็ตเวิร์ก หรือการ์ดแลนของเรา ซึ่งเรามีการ์ดเดียวคือ eth0 แน่นอน

ดังนั้นใช้ ifconfig ปลุก eth0 ให้ตื่นซะก่อน แปลงร่างเป็นผู้มีอภิสิทธิ์ในระบบซะแล้วก็

> ifconfig eth0 up

หลังจากนั้น ก็ให้ DHCP ของ 3BB (หรือว่าของเราเตอร์ก็ไม่รู้ล่ะ) กำหนด IP ให้เรา

> dhclient eth0

ตอนนี้ควรจะใช้การได้แล้ว จะลอง ping ลอง host ดูว่าเชื่อมต่อกับเน็ตเวิร์กแล้วหรือยังก็ได้เช่น

> host google.com

ถ้าแสดง IP ของ google.com มาได้ก็แสดงว่าใช้ได้แล้ว เคยถามที่จุดให้บริการเรื่อง proxy เขาว่าไม่ต้องใช้ ก็ให้เอา proxy ของ browser ออกซะ แล้วก็ลองใช้อินเตอร์เนตดู   

ทีนี้ล่ะ Debian ของผมมีปัญหา เข้าบางเว็บไม่ได้ แต่เครื่องของแฟนเข้าได้!!?? คิดไปคิดมา เฮ้ย เราเคยเจอปัญหาอย่างนี้มาหนหนึ่งแล้วนี่นา กับ True ของน้องชายไง สาเหตุอันเนื่องมาจาก DNS ที่เราใช้มันใช้ไม่ได้ หรือมันใช้ไม่ดี ยังไงก็ไม่ทราบ เอ้า! ลองเปลี่ยน DNS ดู

> vi /etc/resolve.conf

แล้วแก้ไข nameserver ให้เป็นค่าอื่นที่เราคิดว่าใช้ได้ เช่น DNS ของ กูเกิลเป็นต้น

อย่างไรก็ตามผมก็ยังอยากรู้เหมือนกันว่า  DNS ของ 3BB ที่ถูกต้อง ควรตั้งเป็นค่าไหนกันแน่ เพราะค้นจากเว็บ จาก FAQ จาก ฯลฯ ก็ไม่พบข้อมูลที่เป็นทางการสักที ก็ต้องใช้ DNS ของกูเกิลไปก่อน หากมีใครทราบโปรดอนุเคราะห์ด้วยเถอะ ไหน ๆ ก็เสียเงินให้เขาไปแล้วอยากใช้ให้คุ้ม ไม่อยากไปใช้ที่อื่น

ป.ล. ผมเองก็จะถามไปที่ 3BB เหมือนกัน แต่ว่าไม่ค่อยแน่ใจว่าเขาจะเข้าใจคำถามของผมหรือเปล่าน่ะสิ เขาอาจจะถามกลับมาเรียบ ๆ ก็ได้ว่า ก็แล้วทำไมพี่ไม่ใช้ Window ล่ะ (ไม่ฮา)