วันพฤหัสบดีที่ 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 เพื่อเขียนโปรแกรมสำหรับแอนดรอยด์ได้อย่างไร