วันพฤหัสบดีที่ 1 มีนาคม พ.ศ. 2555

หัดเขียนแอนดรอยด์ - โปรแกรมฆ้องวง

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

แต่เมื่อปลายปีที่ผ่านมามีโอกาสได้จับแทบเบล็ตของเพื่อน ลองเล่นดูทำให้มองเห็นว่าที่มีคนกล่าวกว่า Mobile Computing is already here น่าจะเป็นจริง ก็เลยคิดว่าควรหาความรู้ด้านนี้ใส่ตัวไว้บ้าง

หลายปีก่อนผมเคยพยายามเรียนรู้ Java แต่ไม่ประสบความสำเร็จเนื่องจากไม่มีความสามารถที่จะทำความเข้าใจระบบคลาสที่ซับซ้อนของ Java ได้ ศาสตร์ของ Java เองก็ถือว่ากว้างขวางจนกระทั่งเมื่อเริ่มเรียนรู้ขั้นต้นแล้วแล้วจะไปต่อขั้นกลาง ขั้นสูงก็ไปไม่ถูก (แต่ก็ได้พื้นฐานของ Java มาเล็กน้อย พออ่านโปรแกรมออก)

ในทางกลับกัน ถึง Android จะใช้พื้นฐานภาษา Java (ซึ่งผมขยาดเอาเลยทีเดียว) แต่ระบบคลาสของ Android ชัดเจนกว่ามาก ทำให้เกิดทัศนคติต่อตัวเองว่าการเรียนรู้ Android น่าจะง่ายกว่า ซึ่งหลังจากที่ลองลงมือทำก็พบว่าจริงทีเดียว

แม้ว่า Android จะเป็น Open Source แต่การติดตั้ง SDK และ IDE ลงบน Debian กลับเป็นเรื่องไม่ง่ายนัก คือใช้ apt-get หรือ synaptic ก็ไม่ได้ นี่เป็นอุปสรรคสำหรับผมอย่างมากจนกระทั่งผมได้พบกับ Developing Android Applications on Debain ที่ workaround.org ซึ่งมีคำแนะนำอย่างละเอียด

เมื่อดำเนินการตามขั้นตอนที่แนะนำแล้วก็เริ่มต้นเรียนรู้ Android จาก Hello, World พอเริ่มเข้าใจเครื่องไม้เครื่องมือและมองเห็นศักยภาพของ Android แล้วก็เกิดไอเดียบรรเจิดว่าอย่างทำโน่นอยากทำนี่ สิ่งแรกที่อยากทำบนแอนดรอยด์คือโปรแกรมดนตรีไทย เริ่มต้นที่ฆ้องวงก่อน เพราะมีทรัพยากรครบ เสียงต้นแบบก็มีแล้ว ภาพลูกฆ้องก็มีแล้ว กลไกการสังเคราะห์ก็มีแล้ว

โปรแกรมฆ้องวงรุ่นแรกสุดใช้ ImageButton เป็นรูปฆ้อง ใช้ Absolute Layout ระบุตำแหน่งลงใน XML และใช้ MediaPlayer เพื่อเล่นเสียงฆ้องที่บันทึกไว้ล่วงหน้า (ไม่ได้สังเคราะห์) โปรดสังเกตว่าคลาสชื่อ HelloButtonActivity ที่เป็นเช่นนี้เพราะว่าโปรแกรมนี้เริ่มมาจากการศึกษาโปรแกรม HelloButton ของ Android Tutorial บทแรก ๆ แล้วค่อย ๆ ขยายขอบเขตออกมา (โปรแกรมที่แสดงนี้ลดรูป คือเหลือลูกฆ้องเพียงลูกเดียวเท่านั้น)
public class HelloButtonActivity extends Activity {

        ImageButton gongButton01;
 
        OnClickListener gongListener = new OnClickListener() {
         @Override
  public void onClick(View v){
          if (v==gongButton01)
   {
           MediaPlayer mp = MediaPlayer.create(HelloButtonActivity.this, R.raw.gong01);
           mp.setOnCompletionListener(new OnCompletionListener() {
                                 public void onCompletion(MediaPlayer mp) {                
                                         mp.stop();
                                         mp.release();
                                 }
    });
    mp.start(); 
      }
         }

         @Override
 public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
        
                setContentView(R.layout.main);

                addListenerOnButton();
        }

        public void addListenerOnButton(){
             gongButton01 = (ImageButton) findViewById(R.id.gongButton01);
        }
}

วิธีแบบนี้มีข้อจำกัดอยู่หลายประการคือ
  1. เมื่อเป็น ImageButton แล้ว Event ที่ตรวจจับได้คือ Click ซึ่งหมายความว่าต้อง "กด" และ "ปล่อย" จึงจะนับเป็น Click Event และโปรแกรมจะส่งเสียงออกมา ในมุมมองของผู้ใช้ถือว่าไม่สมจริง
  2. วิศวกรของ Android ไม่แนะนำให้ใช้ Absolute Layout เนื่องจากหน้าจอขอโทรศัพท์มีหลายขนาด
  3. MediaPlayer ที่เล่นเสียงของแต่ละลูกฆ้องถือเป็น 1 อินสแตนซ์ เรียกมาเล่นแล้วต้องลบทิ้ง ไม่เช่นนั้นแล้วโปรแกรมจะกินพื้นที่หน่วยความจำมากขึ้นเรื่อย ๆ และวิธีที่ผมใช้เขียนทำให้เล่นได้ทีละ 1 เสียงเท่านั้น ในทางปฏิบัติการบรรเลงฆ้องจะใช้สองมือ ควรเล่นได้พร้อมกันอย่างน้อย 2 เสียง โปรแกรมเมอร์หลายคนแนะนำว่าให้ใช้ SoundPool ดีกว่า เพราะ SoundPool 1 อินสแตนซ์เรียกเสียงมาเล่นได้พร้อม ๆ กันหลายเสียง
หลังจากนั้นได้มีโอกาสชมผลงานโครงงานนักศึกษาภาควิชาวิศวกรรมคอมพิวเตอร์ มีหลายโครงงานใช้แอนดรอยด์ เลยได้เข้าไปสอบถามหาความรู้เพิ่มเติม นักศึกษาแนะนำว่าใช้ Canvas ดีกว่า ตอนนั้นยังไม่รู้ว่า Canvas คืออะไร ก็มาถามเฮียกู เฮียเลยแนะนำให้รู้จักกับ Android Tutorial: 2D Canvas Graphics ที่ Programmer XR พบว่าการวาดรูปลง Canvas นั้นไม่ยาก ดูจากตัวอย่างแล้วดัดแปลงนิด ๆ หน่อย ๆ ก็ใช้ได้ ไอ้ที่ยากน่ะคือให้รูปใน Canvas มันดักอีเวนต์การแตะหน้าจอ (Tap) ต่างหาก

จากการดู Source Code ของชาวบ้านประกอบกับคำอธิบายใน stackoverflow (นี่แหละข้อดีของโอเพนซอร์ส) ถึงได้รู้ว่าเขาใช้คลาส onTouch แล้วดักด้วย OnTouchListener กัน แต่การทำแบบนี้จะถือว่าฆ้องทั้ง 16 ลูกเป็นรูปภาพเพียงรูปเดียว ดัก OnTouchListener ฟังก์ชันเดียว แล้วจำแนกการเล่นเสียงของฆ้องแต่ละลูกตามตำแหน่งสัมบูรณ์ใน Canvas

พอดัก OnTouchListener ได้แล้วก็กำหนดให้โปรแกรมเรียก SoundPool มาเล่นเสียงที่บันทึกไว้ล่วงหน้าเมื่อเกิด onTouch ขึ้น การใช้ SoundPool นี้ได้ข้อมูลจาก The Game Programming Wiki ลดรูปตัดมาใช้เฉพาะที่จำเป็น (โปรแกรมที่แสดงนี้ลดรูปเช่นเดียวกัน คือเหลือลูกฆ้องเพียงลูกเดียว)
public class GongwongActivity extends Activity {
        SoundPool soundPool;
 int gong01ID; 
 
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                requestWindowFeature(Window.FEATURE_NO_TITLE);        
                setContentView(new Panel(this));
                setVolumeControlStream(AudioManager.STREAM_MUSIC);
                soundPool = new SoundPool(16, AudioManager.STREAM_MUSIC,0);            
  gong01ID = soundPool.load(getBaseContext(), R.raw.gong01, 1);
    }
    
    class Panel extends View {
     Rect dst = new Rect();
     float xTouch, yTouch;
     
     public Panel(Context context) {
      super(context);
     }
     
     public boolean onTouchEvent(MotionEvent event){
      int action = event.getAction();
      
      switch(action){
      case MotionEvent.ACTION_DOWN:
       xTouch = event.getX();
       yTouch = event.getY();
       if (xTouch > 44 & xTouch < 44 + 68 & yTouch > 224 & yTouch < 224 + 68){
        soundPool.play(gong01ID,1,1,0,0,1);
       }
      }
      return true;
     }
     
     @Override
     public void onDraw(Canvas canvas) {
      dst.set(0,0,480,800);
      Bitmap gongwong = BitmapFactory.decodeResource(getResources(), R.drawable.gongimg);
      canvas.drawColor(Color.BLACK);
      canvas.drawBitmap(gongwong, null, dst,null);
     }
    }
}


ผลลัพธ์ก็คือโปรแกรม gongwong โดย SDK ที่ใช้เป็นรุ่น Android 2.0 ไม่น่ามีปัญหากับอุปกรณ์แอนดรอยด์ส่วนใหญ่ หากท่านในสนใจทดลองใช้งาน ก็สามารถดาวน์โหลดแฟ้ม APK ได้ที่นี่ (ขอเตือนก่อนว่าโปรแกรมรุ่นนี้ถือเป็น Alpha Version อาจมีข้อบกพร่องอยู่มาก) เมื่อเรียกใช้โปรแกรมบนแทบเบล็ตก็จะเห็นหน้าตาโปรแกรมดังรูป และเมื่อแตะบนหน้าจอในบริเวณลูกฆ้องก็จะได้ยินเสียงฆ้องแต่ละลูก ทั้งนี้จากการทดลองพบว่าหูฟังจะให้คุณภาพเสียงที่ดีกว่าลำโพงก็ขอให้แนะนำให้ใช้กับหูฟังก็แล้วกัน

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

สิ่งที่ต้องปรับปรุง
  1. แม้ว่า SoundPool จะเล่นเสียงได้พร้อมกันหลายเสียง แต่ onTouch ที่ตรวจจับได้ก็จับได้ทีละ Touch ยังไม่สามารถขยายไปถึง Multi Touch ได้
  2. รูปฆ้องสำหรับฆ้องลูกเล็ก เล็กเกินไปโดยแม้กระทั่งกับแทบเบล็ตที่มีหน้าจอขนาด 8 นิ้ว รูปลูกฆ้องไม่จำเป็นต้องย่อส่วนตามมาตรส่วนคงที่ก็ได้
  3. การกำหนดขอบเขตของฆ้องแต่ละลูก ใช้การกำหนดค่าคงที่ตายตัวลงในโปรแกรม นี่ไม่ใช่การเขียนโปรแกรมที่ดี ควรหาทางจัดเก็บข้อมูลตามรูปแบบมาตรฐาน หรือไม่เช่นนั้นก็ให้หันไปใช้แนวทางให้ลูกฆ้องแต่ละลูกเป็น Sprite ที่แต่ละตัวมี OnTouchListener ของตัวเองก็ได้ ถ้าเป็นอย่างนี้ ก็ไม่ต้องกำหนดขอบเขตลูกฆ้องแต่ต้องกำหนดตำแหน่งแทน
  4. หากฟังก์ชันพื้นฐานสมบูรณ์ดีแล้ว ควรทดสอบโปรเจ็คตามกรรมวิธีมาตรฐานของ Android ด้วย (ยังทำไม่เป็น)

ไม่มีความคิดเห็น: