4

โปรแกรมนี้เป็นโปรแกรมตีกลอง เขียนด้วย SDL เป็นตัวจัดการด้านกราฟิคและส่วนประกอบอื่น ๆ (เนื่องจากผมขี้เกียจเปิดหนังสือ Win32 นั่นเอง) และส่วนที่เกี่ยวกับเสียงทั้งหมดจะเป็น OpenAL ครับ ผมเขียนขึ้นมาเป็น Concept Prove ดูว่าถ้าทำขึ้นมาจริง ๆ มันจะเล่นได้มั้ย

OpenAL ก็คล้าย ๆ กับ OpenGL แต่เป็นเรื่องเสียงครับ ประวัติศาสตร์ของมันเป็นไงก็ไปหาอ่านเองนะ (ฮา) ที่แน่ๆ คือวิธีใช้มันใกล้เคียงกับ OpenGL มาก ๆ แต่มีเรื่อง Device กับ Device Context เพิ่มขึ้นมา OpenAL เป็นระบบเสียงสามมิติโดยกำเนิด ดังนั้นโปรแกรมนี้ก็จะเป็นระเบบเสียงสามมิติเช่นกันครับ จะฟังออกเลยว่าเสียงไหนอยู่ตรงไหน ถ้าใช้หูฟังดี ๆ นะ

ผมเพิ่มความน่าสนใจเข้าไปอีกหน่อยโดยการใส่เอฟเฟค Reverb เข้าไป ซึ่งตัว OpenAL Effect Extension นั้นจะเป็นรูปแบบ Send/Return เหมือนกับพวกโปรแกรมทำเพลงอยู่แล้ว และ Reverb ตัวนี้เสียงเข้าท่าทีเดียว (ขอเตือนก่อนว่า ท่านใดที่เครื่องไม่รองรับ Effect Extension จะรันไม่ขึ้นนะครับ ... คือผมขี้เกียจเช็คเงื่อนไขน่ะ 555 แหมเอาแค่ทดลองเฉย ๆ เอง)

โปรแกรมนี้ทำงานง่าย ๆ ก็คือ พอมี Event กดคีย์บอร์ดปุ๊บ ก็ไปเล่นเสียงปั๊บ ง่ายมาก ๆ ใช่มั้ยครับ โดยโปรแกรมนี้ปัจจุบันจะใช้ทั้งหมด 8 เสียง คือ เบสดรัม แสนร์ดรัม ทอมทอม ฟลอร์ทอม ไฮแฮทปิด-เปิด ไรด์ แล้วก็ แครช ครับ อิอิ (ขี้เกียจเปลี่ยนภาษาอีกแล้ว) มีจุดสำคัญตรงที่ ไฮแฮทปิดกับเปิดนั้น ต้องไม่เล่นพร้อม ๆ กัน ต้องหยุดเสียงใดเสียงนึงก่อนที่จะเล่นอีกเสียง ไม่งั้นมันจะฟังประหลาดครับ

หน้าตามันตอนนี้ไม่มีอะไรเลย (เนื่องจากขี้เกียจอีกแล้ว) เป็นหน้าต่างดำ ๆ ก็ปล่อยมันไว้งั้นแหละ

ปุ่มที่ใช้มีดังนี้ครับ
Bass Drum - Space Bar
Snare Drum - f หรือ j
Closed Hi-Hat - g หรือ h
Opened Hi-Hat - t หรือ y
Tom Tom - d หรือ k
Floor Tom - s หรือ l
Ride Cym - r หรือ u
Crash Cym - e หรือ i

ลองเล่น ๆ ดูนะครับ ก่อนจะเล่นคุณจะต้องลง OpenAL Runtime ก่อนนะครับ แล้วก็ต้องมี Microsoft Visual C++ 2008 Redistributable Package (เพราะผมเขียนด้วย VC 2008 ครับ) สำหรับคนที่เล่นเกม ส่วนใหญ่น่าจะมีทั้งสองตัวนี้อยู่แล้วครับ (มันมากับหลายเกมมาก ๆ ) ส่วนอีกสองไฟล์ที่ต้องใช้ (SDL.dll กับ alut.dll) ผมติดมาให้แล้ว เผื่อว่าไม่มีครับ

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

สิ่งที่ผมทดลองตรงนี้ มีดังนี้ครับ

  • ทดสอบว่า การวางปุ่มแบบนี้มันเล่นถนัดมั้ย ?
  • เช็คเรื่อง Latency ของ OpenAL ว่ามันเยอะขนาดเล่นไม่ได้เลยหรือไม่
  • ระบบเสียง 3มิติ และ เอฟเฟค reverb ว่าเป็นยังไง

ซึ่งกับเครื่องผมนี่ ให้ผลที่น่าพอใจมาก ๆ ครับ สำหรับท่านที่โหลดไปลอง จะให้คอมเม้นท์ในสามเรื่องนี้ด้วยก็ดีนะครับ 😀

ปล. อันนี้ไม่มีโค๊ดให้ อย่าเอาไปเลย ยังเละอยู่ ...

คราวนี้ขอแปะแต่โค๊ดก่อนนะครับ อธิบายจะตามมาวันหลัง ง่วงแล้ว (ตีสองครึ่งแล้วนะ !!)

ที่ว่าแบบ stream ก็คือ จะไม่อ่านทีเดียวทั้งไฟล์ เก็บไว้ในเมมโมรี่ก่อน (มันเปลือง) แต่จะใช้วิธีค่อย ๆ บัฟเฟอร์ไฟล์มาเก็บไว้ แล้วค่อย ๆ เล่นทีละส่วน (ซึ่งเป็นเทคนิคทีเรียกว่าการทำ streaming)

หลักการคร่าว ๆ ก็คือ

  1. เติมข้อมูลทุก ๆ บัฟเฟอร์ เว้นบัฟเฟอร์สุดท้ายไว้ (จริง ๆ ไม่ต้องเว้นก็ได้ แต่ที่เว้นเพื่อความสะดวกในการเขียนโค้ด)
  2. เมื่อบัฟเฟอร์แรกถูกเล่น บัฟเฟอร์สุดท้ายจะถูกเติมข้อมูล
  3. เมื่อบัฟเฟอรฺ์แรกหมด จะมีการเรียก call back จากระบบมาบอกว่าเล่นบัฟเฟอร์หมดแล้ว ให้ก๊อปบัฟเฟอร์ที่สองใส่บัฟเฟอร์ระบบไป แล้วเติมบัฟเฟอร์แรกที่เล่นจบไปแล้ว
  4. ถ้าหากระหว่างเติมบัฟเฟอร์ ดันจบเพลงซะก่อน ก็ seek ไปยังตำแหน่งเริ่มต้น loop ใหม่ซะ แล้วบัฟเฟอร์ต่อจนเต็ม

เอาแค่นี้ก่อนละกัน เดี๋ยวค่อยมาแก้เพิ่มครับ :-)

#include 
#include 

#include 
#include 

const int BUFFER_COUNT = 6;
const int NUM_SAMPLE = 4096*4;
const int BUFFER_SIZE = 2 * 2 * NUM_SAMPLE; //2 channels * 2byte per sample * NUM_SAMPLE

int current_buffer = 0;
char bgm_buffer[BUFFER_COUNT][BUFFER_SIZE];

const int loop_start = 291784;  // loop point in #sample, this is song-specific setting
								// and suits only for supplied ogg file.
								// Updates this value to test with other file.

SDL_mutex *mutex;
OggVorbis_File file;


// Macros for a lazy guy like me 😛
#define if_error_display_then_exit(x, y)  
	if( (x) != 0 ) { std::cout<<(y)< )
		SDL_Delay(100);

		SDL_Event event;
		while(SDL_PollEvent(&event))
		{
			switch(event.type)
			{
			case SDL_QUIT:
				goto LEAVE_LOOP;
			}
		}
	}

LEAVE_LOOP:
	// Clean Up
	SDL_DestroyMutex(mutex);
	SDL_CloseAudio();

	ov_clear(&file);
	return 0;
}

void fill_audio_callback(void *userdata, Uint8 *stream, int len)
{
	static int filling_buffer_index = 0;
	// input will be unsigned 16bit array, length = len.
	unsigned short* buffer = (unsigned short*)stream;

	memcpy(stream, bgm_buffer[filling_buffer_index], len);

	filling_buffer_index++;
	if(filling_buffer_index == BUFFER_COUNT)
		filling_buffer_index = 0;

	// buffer copy is done, decode new chunks of data
	SDL_CreateThread(fill_bgm_buffer, NULL);
}

int fill_bgm_buffer(void* data)
{
	// lock mutex
	if_error_display_then_exit(SDL_LockMutex(mutex), 
		"Unable to lock mutex.");

	// Start Critical Section
	long read = 0;
	long total_read = 0;

	while(total_read != BUFFER_SIZE)
	{
		int current_bitstream;
		//hard coded for little endian, 2 channels, signed data
		read = ov_read(&file, bgm_buffer[current_buffer]+total_read, 
			BUFFER_SIZE - total_read, 0, 2, 1, ¤t_bitstream); 
		total_read += read;

		// reach EOS ? seek to the loop start position
		if(read == 0)
			ov_pcm_seek(&file, loop_start);
	}

	current_buffer++;
	if(current_buffer == BUFFER_COUNT) 
		current_buffer = 0;

	// End of Critical section, release mutex.
	if_error_display_then_exit(SDL_UnlockMutex(mutex), 
		"Unable to unlock mutex.");
	return 0;
}

ต่อไปก็ Source Code กับตัวโปรแกรมนะครับ
Source
Binary

ตัวโปรแกรม (น่าจะ) ต้องใช้กับ VS 2008 Runtime นะครับ ถ้าไม่มีก็คงรันไม่ได้มั้ง ??? แต่ก็เอาโค๊ดไปคอมไพล์เองเป็นเวอร์ชั่นอื่นก็ได้ครับ