แสดงบทความที่มีป้ายกำกับ sound synthesis แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ sound synthesis แสดงบทความทั้งหมด

วันพฤหัสบดีที่ 18 ตุลาคม พ.ศ. 2555

สังเคราะห์เสียงฆ้องบน Android ด้วย libpd ตอนที่ 1

หลังจากหัดเขียนโปรแกรมบน Android และทดลองเขียนโปรแกรมฆ้องวงขึ้นมา ก็รู้สึกถึงข้อจำกัดของการใช้เสียงฆ้องที่บันทึกไว้ล่วงหน้าซึ่งก็คือ
  1. มีข้อจำกัดด้านคุณภาพของการบันทึกเสียงและ
  2. ไม่สามารถปรับค่าความถี่และอัตราลดทอนของเสียงได้
แต่การสังเคราะห์เสียงโดยใช้ภาษา Java โดยตรงดูจะไม่ใช่ทางเลือกที่ดีนัก ยิ่งไม่ใช่ทางเลือกที่ดีสำหรับคนที่ไม่เชี่ยวชาญ Java อย่างผม หลังจากที่เสาะหากรรมวิธีเหมาะ ๆ ได้สักพักก็เจอเครื่องมือด้านการสังเคราะห์เสียงจำนวนหนึ่งคือ
ผมได้ทดลองใช้ Chuck กับ Python เพื่อสังเคราะห์เสียงบน Linux ดูแล้วก็ใช้งานได้สะดวกดี แต่ Chuck ไม่มีเครื่องมือที่จะเชื่อมต่อกับ Java และ Android (หรือมีแต่ผมหาไม่เจอ) ส่วน Csound นั้นผมไม่เคยใช้ ค้นดูก็ไม่เจอเครื่องมือที่จะเชื่อมต่อกับ Java และ Android เช่นกัน

สำหรับ puredata นั้นเดิมผมเคยยอมแพ้มันมาแล้วหนหนึ่งด้วยเหตุว่าเขียนแพตช์ (Patch) ของ puredata ไปแล้วก็เอาไปใช้ในโปรแกรมอื่นเหมือน Chuck ไม่ได้...จนกระทั่งผมได้พบกับ libpd จึงได้ทราบว่าผมคิดผิด

อันที่จริง puredata สามารถสื่อสารกับภาษาโปรแกรมอื่น ๆ ได้ผ่านทาง libpd ขั้นตอนการพัฒนาโปรแกรมโดยใช้ libpd จะเป็นดังนี้
  1. ผู้พัฒนาจะสร้างแพตช์ puredata ขึ้นมาก่อน โดยกำหนดชื่อตัวแปรควบคุมไว้แทนอินเลต
  2. เปิดแพตช์ที่สร้างขึ้นจากคำสั่ง openPatch
  3. ส่ง Bang Float Message ผ่านคำสั่ง sendBang sendFloat และ sendMessage ไปยังแพตช์เป้าหมายตามลำดับ
เนื่องจากมีขั้นตอนที่เกี่ยวข้องแยกเป็น 2 ส่วนใหญ่ ๆ ที่ไม่เกี่ยวข้องกันก็คือส่วนการสังเคราะห์เสียงด้วย pd เป็นส่วนหนึ่ง และการตั้งค่าและเรียกใช้ libpd ในแอนดรอยด์เป็นอีกส่วนหนึ่ง ในตอนนี้จึงขอบันทึกไว้เฉพาะส่วนการสังเคราะห์เสียงก่อน

การสังเคราะห์เสียงฆ้องด้วย pd
จากการวิเคราะห์เสียงฆ้องในเบื้องต้นพบว่าคุณลักษณะพื้นฐานที่โดดเด่นของเสียงฆ้องคือองค์ประกอบ Exponentially Decayed Sinusoid ซึ่งสามารถสร้างได้โดยแพตช์ดังแสดงในรูป
andgong.pd
อินเลตของแพตช์นี้จะเป็นค่าความถี่ของลูกฆ้อง เมื่อมี float เข้ามาทางอินเลต bang จะถูกกระตุ้นและจะสร้างอาเรย์ของตัวแปรเวลา t ขึ้นมา ตัวแปรเวลานี้จะคูณเข้ากับอัตราการลดทอนของเสียงฆ้องซึ่งในที่นี้เป็น -2 เมื่อข้อมูลนี้ผ่านออบเจ็กต์ [exp~] ก็จะเป็นฟังก์ชัน
amath f(t)=e^{-2t} endamath
ส่วนตัวเลขความถี่ที่ป้อนเข้ามาแต่แรกก็จะไปกระตุ้น [osc~] เพื่อให้สร้างสัญญาณไซนูซอยด์ออกมา เมื่อนำมาคูณกับเอกโพเนนเชียลในตอนแรกและคูณกับ 0.3 ก่อนส่งออกทางเอาต์เลตจะทำให้ได้ฟังก์ชัน
amath g(t) = 0.3 e^{-2t} cos(2\pi f t) endamath
จะเป็นเสียงฆ้องลูกทวนหรือว่าลูกยอดก็แล้วแต่ว่าความถี่ที่ป้อนเข้าทางอินเลตนั้นเป็นเท่าใด

แต่ [timearray] ไม่ใช่ออบเจกต์มาตรฐานของ pd แต่เป็นออบเจกต์ที่ผมสร้างขึ้นมาเองเพื่อสร้างตัวแปร t ที่วิ่งจาก 0 ถึง 5000 ในเวลา 5 วินาที (ขั้นละ 1 มิลลิวินาที) ทุกครั้งที่มีการกระตุ้นออบเจกต์ด้วย [bang( ซึ่งกลไกของ [timearray] แสดงดังรูปต่อไปนี้
timearray.pd
เมื่อมี [bang( เข้ามาทางอินเลต ข้อความ 0 จะถูกกระตุ้นเพื่อส่งให้ [line~] กระโดดจากค่าใด ๆ ก็ตามขณะนั้นไปที่ [0( ทันที หลังจากนั้น 1 มิลลิวินาที [bang( ที่ถูกหน่วงเวลาไว้ก็จะกระตุ้นข้อความ [5 5000( เพื่อส่งให้ [line~] ทำให้เอาต์พุตของ [line~] เปลี่ยนแปลงจาก 0 ไปเป็น 5 ภายในเวลา 5000 มิลลิวินาที นั่นหมายความว่าเอาต์เลตของแพตช์นี้ก็คือค่าเวลาเป็นวินาทีขั้นละ 1 มิลลิวินาทีเป็นเวลา 5 วินาทีนั่นเอง

เนื่องจาก libpd อนุญาตให้มีแพตช์ Instance เดียว เราก็เลยต้องสร้างลูกฆ้องทั้ง 16 ลูกในแพตช์โดยให้ฆ้องแต่ละลูกถูกกระตุ้นโดยตัวแปร h01 - h16 ด้วยตัวเลขค่าความถี่
gongwong.pd
แม้ว่าจะตีฆ้องทีละลูกหรืออย่างมาก 2 ลูก เพื่อให้การเชื่อมต่อสัญญาณกับ [dac~] ไม่ติดขัดเพราะเชื่อมกับอินพุตมากเกินไปเราเลยใช้ [+~] รวมสัญญาณจากฆ้องแต่ละลูกเข้าด้วยกัน แต่ Hot Inlet คืออินเลตทางซ้ายเท่านั้น เราเลยต้องให้ตัวแปรความถี่ลูกฆ้องลูกที่ 2 - 16 กระตุ้น [bang( เพื่อกระตุ้น Hot Inlet ของ [+~] ตัวแรกด้วย นอกจากนี้ก่อนจะส่งสัญญาณเสียงออกมาทาง [dac~] เราจะป้องกันองค์ประกอบไฟตรง (DC Component) โดยใช้ตัวกรองความถี่สูงผ่าน [hip~] โดยตั้งค่าความถี่ตัดไว้ที่ 1 เฮิร์ทซ และป้องกันการทำงานผิดพลาดของ libpd โดยการขริบสัญญาณขาออกไว้ให้ไม่เกิน +/- 1

เราสามารถทดสอบแพตช์ gongwong.pd ใน puredata ได้โดยการเปลี่ยน [r h01] ถึง [r h16] ให้เป็นข้อความ [xxx01( - [xxx16( รวม 16 ข้อความเมื่อ xxx01 - xxx16 คือค่าความถี่เสียงที่เราต้องการ และทดลองฟังเสียงโดยการคลิ๊กที่ข้อความแต่ละข้อความนั้น ๆ

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

วันเสาร์ที่ 14 พฤษภาคม พ.ศ. 2554

แนวคิดใหม่ (ใหม่สำหรับผม) สำหรับโปรแกรมเกี่ยวกับดนตรีไทย

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

หลังจากที่ได้รู้จักกับ Chuck ที่นำเสนอไว้ในบล๊อกก่อนหน้านี้ ก็พบว่าการเขียนโค้ดไพธอนใหม่เพื่อรับผิดชอบงานทั้งหมดตั้งแต่การเขียนเพลงไปจนถึงบรรเลงเพลงดูจะเป็นงานซ้ำซ้อน (Rebuilding the wheel) โดยเฉพาะการเขียนโค้ดไพธอนให้บรรเลงเพลงได้หลาย ๆ เสียงพร้อมกัน (Concerent Programming) ซึ่งเคยมีคนทำแล้วด้วยซ้ำโดยใช้ภาษาจาวา

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

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

*.tmn => *.abc => *.mid

นามสกุล TMN นั้นย่อมาจาก Thai Music Notation ซึ่งจะเป็นแฟ้มข้อความธรรมดาแต่มีรูปแบบการบันทึกโน้ตตามมาตรฐานของนักดนตรีไทยดังรูป
 บรรทัดแรกคือชื่อเพลง โน้ตเพลงจะเป็นแบบ 2 บรรทัดโดยให้บรรทัดแรกเป็นโน้ตสำหรับมือซ้ายและบรรทัดที่สองเป็นโน้ตสำหรับมือขวาแต่ละบรรทัดเพลงจะมี 8 ห้อง แต่ละห้องคั่นด้วยเครื่องหมาย | และเว้นหนึ่งบรรทัดเมื่อจะขึ้นบรรทัดเพลงใหม่ บันทึกแฟ้มด้วยรหัส UTF-8

ส่วนแฟ้ม ABC ก็เป็นการบันทึกโน้ตเพลงด้วยตัวอักษรเช่นเดียวกัน แต่เป็นอักษรภาษาอังกฤษ โดยให้ C = เสียงโด D = เสียงเร เรียงกันไป ระบบนี้สามารถปรับความยาวเสียงได้ รองรับเสียง # และเสียง b และเทคนิคทางดนตรีสากลอื่น ๆ เกือบครบถ้วน รายละเอียดของระบบการบันทึกโน้ตเพลงด้วยตัวอักษร ABC นี้เรียกว่า ABC Notation ตัวอย่างของแฟ้ม ABC แสดงดังรูป
ในระบบ ABC สัญลักษณ์ | หมายถึงการกั้นห้องเช่นเดียวกัน สัญลักษณ์ z หมายถึงไม่มีเสียง หากมีตัวโน้ตสองตัวอยู่ในกรอบเดียวกันเช่น [AB] หมายความว่าให้บรรเลงโน้ต ลา และ ที พร้อม ๆ กัน

ระบบการจดบันทึกเสียงดนตรีไทยของเรามีความซับซ้อนน้อยกว่า คือไม่ระบุเทคนิคการบรรเลง (เบา - ดัง) ไม่มีครึ่งเสียง # หรือ b ความยาวเสียงของแต่ละโน้ตเท่ากันหมดคือเท่ากับหนึ่งโน้ตตัวดำ (1 จังหวะ) และแต่ละห้องจะมี 4 จังหวะตายตัวเสมอ หากมีการพลิกแพลงเป็นลูกเล่นแบบใด ๆ ลูกเล่นเหล่านี้นักดนตรีมักจะเรียนกับครูโดยตรง เรียกว่าต่อเพลง หรือไม่เช่นนั้นก็พัฒนาแนวทางของตนเองขึ้นมาแบบด้นสด (Improvise) แต่จะไม่มีระบบการจดบันทึกที่เป็นมาตรฐานเดียวกัน ดังนั้นการแปลงแฟ้ม TMN ไปเป็น ABC จึงทำได้ง่าย

พื้นฐานที่ใช้คือสร้าง Empty List ขึ้นมา แล้วให้ไพธอนอ่านแฟ้ม TMN ไปทีละบรรทัด ในแต่ละบรรทัดเมื่อพบตัวอักษรก็เทียบอักษรไทยเข้ากับอักษรอังกฤษ เช่นเทียบ "ด" เป็น "C" เป็นต้น ถ้าหากตัวอักษรมีตัวเปลี่ยนขั้นเสียง เช่น ดํ จะให้หมายถึงเสียง โดสูง หรือ "c" ในระบบ ABC และ ดฺ จะให้หมายถึงเสียง โดต่ำ หรือ "C," ในระบบ ABC ดำเนินการเช่นเดียวกันกับโน้ตเพลงบรรทัดที่สอง

L1 = [];
 for i in range(len(line1)):
  if line1[i]=='|':
   L1.append('|');

  if line1[i]==':':
   L1.append(':');

  if line1[i]=='-':
   L1.append('z');

  if line1[i]==u'ด':
   if line1[i+1]==u'\u0e4d':
    L1.append('c');
   elif line1[i+1]==u'\u0e3a':
    L1.append('C,');
   else:
    L1.append('C'); 

เมื่อได้ List L1 สำหรับบรรทัดที่หนึ่งและ L2 สำหรับบรรทัดที่สองมาแล้วก็นำ List ทั้งสองมาเทียบกัน หากในจังหวะเสียงเดียวกันมีโน้ตทั้งสองบรรทัด ก็ให้บรรเลงโน้ตทั้งสองตัวพร้อมกัน ถ้ามีเพียงโน้ตตัวเดียวก็บรรเลงตัวเดียว ถ้าไม่มีโน้ตทั้งคู่ ก็ให้เงียบเสีย

L = [];
 for i in range(len(L1)):
  if L1[i]=='|':
   L.append('|');
  elif L1[i]==':':
   L.append(':');
  elif L1[i]=='z' and L2[i]=='z':
   L.append('z');
  elif L1[i]=='z' or L2[i]=='z':
   if L1[i]=='z':
    L.append(L2[i]);
   else:
    L.append(L1[i]);

  else:
   L.append('['+L1[i]+L2[i]+']');



นี่คือการแปลงโน้ตอักษรไทยไปเป็นโน้ตอักษรภาษาอังกฤษในระบบ ABC ผมตั้งชื่อโปรแกรมนี้ว่า tmn2abc (Thai Music Notation to ABC) เรียกใช้ดังนึ้

# python tmn2abc test.tmn test.abc

หลังจากนี้เราสามารถใช้เครื่องมือที่มีอยู่แล้วคือโปรแกรม abc2midi แปลงจากแฟ้ม ABC นี้ให้เป็น MIDI ได้ทันที

# abc2midi test.abc -o test.mid

สิ่งที่ต้องการจะทำเพิ่มเติมคือ
  1. รองรับระบบโน้ตบรรทัดเดียว สำหรับโน้ตซอ โน้ตขลุ่ย ฯลฯ
  2. เพิ่มการระบุชั้นเพลง คือ หนึ่งชั้น สองชั้น สามชั้น
  3. เพิ่มส่วนกำหนดเครื่องดนตรี แต่เรื่องนี้ต้องศึกษามาตรฐาน MIDI เกี่ยวกับการกำหนดช่องสัญญาณและโปรแกรมเพิ่มเติม
ก่อนหน้านี้เคยเขียนโปรแกรมนี้ไว้แล้ว แต่ตอนสำรองข้อมูลก่อนลงระบบใหม่เมื่อตอนทดสอบ Ubuntu ไม่สมบูรณ์ โปรแกรมทั้งชุดจึงหายไปหมด ตอนนี้เขียนใหม่แล้วเลยอัพโหลดไว้เสียเลย ท่านใดสนใจสามารถดูต้นฉบับโปรแกรมได้ sourceforge.net ครับ หากมีปัญหาหรือข้อแนะนำใด ๆ หากจะทิ้ง Comment ไว้ที่ Blog นี้ได้ก็จะขอขอบคุณล่วงหน้าครับ

วันเสาร์ที่ 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 เพื่อสังเคราะห์เสียงอื่น ๆ ต่อไปดู

วันเสาร์ที่ 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 ไปสร้างเสียงสังเคราะห์ที่ซับซ้อนมากขึ้น