Программирование игр для Windows. Советы профессионала

Использование функций драйвера для проигрывания VOC-файлов


Давайте подытожим разговор об используемых функциях. Вообще-то они потрясающе просты и вы наверняка сможете самостоятельно написать неболь­шую программу и поэкспериментировать с цифровыми каналами ввода-вывода звуковой карты Sound Blaster... Как бы не так! Неужели я вас брошу одних в этом цифровом чистилище? Ни в коем случае. Давайте-ка вместе загрузим драйвер и проиграем какой-нибудь VOC-файл. То, что у нас получится, вы можете впоследствии использовать в собственных играх.

Первое, что мы должны сделать, это загрузить CT-VOICE.DRV в память. Для этого нужно просто выделить некоторое количество памяти, открыть файл как двоичный и загрузить его байт за байтом. Есть, правда, одна проблема: драйвер должен быть загружен от границы сегмента. Это значит, что сегмент может, быть любым, но смещение начала драйвера должно быть нулевым. Ни одна функция из семейства allocate этого делать не умеет. Здесь необходима функция, которая способна резервировать память на границе параграфа. Такая функция называется _dos_allocmem(). Итак, нам надо сделать следующее:

§

Открыть файл CT-VOICE.DRV;

§          Определить его размер;

§          Отвести под него память;

§          Загрузить его.

Это делает функция, приведенная в Листинге 9.1.

Листинг 9.1. Выделение памяти для CT-VOICE.DRV.

///////////////////////////////////////////////////////////////

void Voc_Load_Driver(void)

// загрузить ct-voice.drv

int driver_handle;



unsigned errno,segment,offset,num_para,bytes_read;

// открыть

файл драйвера

_dos_open("CT-VOICE.DRV", _O_RDONLY, &driver_handle);

// выделить

память

num_para = 1 + (filelength(driver_handle))/16;

_dos_allocmem(num_para, &segment);

//установить указатель на область данных драйвера

_FE_SEG(driver_ptr) = segment;

_FP_OFF(driver_ptr) = 0;

// загрузить

код драйвера

data_ptr = driver_ptr;

do {

_dos_read(driver_handle,data_ptr, 0х4000, &bytes_read);




data_ptr += bytes_read;

} while (bytes_read==0x4000);

// закрыть

файл

_dos_close(driver_handle);

} // конец функции

Мы можем разбить функцию из Листинга 9.1 на три части:

§          Вначале мы открываем файл CT-VOICE.DRV в чисто двоичном режиме.Мы не  должны делать никаких преобразований символов - это было бы катастрофой! Мы же читаем реальный код, а не ASCII-файл;

§          Затем программа вычисляет длину файла и выделяет под него соответствующее количество памяти. Отметим, что мы резервируем память блоками, причем каждый блок — это параграф из 16 байт;

§          Наконец, драйвер загружается по 32К за один прием. Эта одно из замечательных отличий функции _dos_read () от стандартной функции getch(): мы можем читать большие куски кода за один раз.

Теперь, после загрузки драйвера мы должны сохранить указатель так, чтобы впоследствии можно было найти начало драйвера. Этот указатель мы будем хранить в виде глобальной переменной с именем driver_ptr. (Я думаю, что это имя достаточно содержательно.)

Замечание

Давайте немного отвлечемся. Когда вы пишете компьютерные игры (или любое другое программное обеспечение), пожалуйста, используйте такие имена файлов и функций, которые несут смысловую нагрузку и отражают назначение объекта. Постарайтесь избегать таких имен, как t, j, k и им подобных. Используйте имена типа index_1, sprite_alive и так далее. Поверьте моему опыту: когда вы закончите писать компьютерную игру и вернетесь к ней через неделю, вы подумаете: «Не Фон Нейман ли это написал, да здесь сам черт ногу сломит!» Ведь если вы используете иероглифы вместо имен, кто кроме специалиста по иероглифам сможет в .них разобраться? Правильно? Тогда вернемся к нашим баранам.

Верите вы или нет, но загрузка драйвера была самой трудной частью. Посмотрим на код для загрузки VOC-файла. Он выглядит точно так же, как и функция загрузки драйвера.


То есть мы должны:

§          Открыть файл в бинарном режиме;

§          Отвести под него память;

§          Загрузить VOC-файл в отведенный буфер. Это делает функция Листинга 9.2.

Листинг 9.2. Загрузка VОС-файла.

char far *Voc_Load_Sound(char *filename,

unsigned char *header_length)

{ // загрузка звукового файла с диска в память

// и установка указателя на его начало

char far *temp_ptr;

char far *data_ptr;

unsigned int sum;

int sound_handle,t;

unsigned errno, segment, offset, num_para, bytes_read;

// открыть

звуковой файл

_dos_open(filename, _O_RDONLY, &sound_handle);

// выделить

память

num_para =1 + (filelength(sound_handle))/16;

_dos allocmem(num_para, &segment) ;

// установить указатель на выделенную память

_FP_SEG(data_ptr) = segment;

_FP_OFF(data_ptr) = 0;

// загрузить звуковые данные

temp_ptr

= data_ptr;

do

{

dos_read(sound_handle,temp_ptr, 0х4000, &bytes__read) ;

temp_ptr += bytes_read;

sum+=bytes_read;

} while(bytes_read==0x4000);

// Проверить на всякий случай, звуковые ли это данные.

// Для этого проверяется присутствие слова "Creative".

if ((data_ptr[0] != 'С') || (data_ptr[1] != 'r'))

{

printf("\n%s is riot a voc file!",filename);

_dos_freemem(_FP_SEG(data_ptr) ) ;

return(0);

} // конец звукового файла

header_length = (unsigned char)data_ptr[20];

// закрыть

файл

_dosclose(sound_handle) ;

return(data_ptr) ;

} // конец функции

Наиболее замечательное в этой функции то, что она возвращает адрес области памяти (точнее, указатель на него), куда был загружен VOC-файл. Мы используем этот указатель позже при проигрывании звука.

Все остальные функции, которые нам потребуются, тривиальны. Мы используем ассемблерные вставки для настройки регистров и команду процессора CALL для вызова драйвера с помощью указателя driver_ptr.

Рассмотрим несколько таких функций, чтобы понять особенности их написания.


Драйвер должен быть инициализирован прежде, чем мы сможем его использовать. Это очевидное требование. Делать это мы должны с помощью функции 3, «Инициализировать драйвер». Листинг 9.3 содержит текст программы этой функции.

Листинг 9.3. Инициализация драйвера.

int Voc_Init_Driver(void)

{

// инициализация драйвера функция возвращает слово состояния

int status;

_asm

{

mov bx,3        ; функция инициализации драйвера имеет номер 3

call driver_ptr ; вызов

драйвера

mov status,ax   ; сохранение номера версии

} // конец ассемблерной вставки

// возвратить слово состояния

printf("\nDriver Initialized");

return(status);

} // конец

функции

Другая важная функций сообщает драйверу адрес переменной для передачи слова cостояния операции. Текст программы, которая устанавливает эту переменную (я назвал ее ct_voice_status), содержится в Листинге 9.4.

Листинг 9.4. Установка переменной слова состояния драйвера.

Voc Set_Status_Addr(char _far *status)

{

unsigned segm, offm;

segm = _FP_SEG(status);

offm = _FP_OFF(status) ;

asm{

mov bx,5        ; функция задания переменной слова состояния

; имеет номер 5

mov es,segm     ; в регистр ES

загружается сегмент переменной

mov di,offm   ; в регистр DI

загружается смещение переменной

call driver_ptr ; вызов

драйвера

} // конец ассемблерной вставки

} // конец функции

Наконец, посмотрим реализацию функции 6, «Начать вывод», которая используется для проигрывания звука, хранящегося в памяти. Листинг 9.5 содержит текст этой программы.

Листинг 9.5. Проигрывание VОС-файла из памяти.

int Voc_Play_Sound(unsigned char far *addr,

unsigned char header_length)

{

// проигрывает загруженный в память VOC-файл

unsigned segm, offm;

segm = _FP_SEG(addr);

offm = _FP_OFF(addr) + header_length;

_asm{

mov bx,6        ; функция 6 - воспроизведение VOC-файла

mov ax,segm

mov es,ax       ; в регистр ES

загружается сегмент

mov di,offm     ; в регистр DI загружается смещение

call driver_ptr; вызов

драйвера

} // конец ассемблерной вставки



} // конец функции

Функция Voc_Play_Sound из Листинга 9.5 работает следующим образом:

§          Адрес VOC-файла, который мы хотим проиграть, из памяти передается в функцию;

§          Затем функция использует две крайне полезные макрокоманды FP_SEG () и FP_OFF(), получая таким образом сегмент и смещение стартового адреса буфера с VOC-файлом;

§          Сегмент и смещение помещаются в регистровую пару ES:DI в соответствии с требованиями драйвера;

§          Вызывается драйвер.

И, пожалуйста - звучит музыка!

В первых ваших собственных играх, так же, как и в игре, которую мы напишем в этой книге, я полагаю, вы будете придерживаться оцифрованных звуков. Воспроизведение такой музыки не слишком сложно, однако и это все же требует некоторых усилий и понимания. В Листинге 9.6 показан полный текст программы, включающий простое меню для воспроизведения нескольких простых мелодий. Перед запуском программы убедитесь, что;

§          Все VOC-файлы расположены в текущем каталоге;

§          CT-VOICE.DRV также находится в текущем каталоге.

Последнее замечание по поводу воспроизведения оцифрованного звука: после того, как ваша программа начнет проигрывание мелодии, она может делать, что угодно. Лучше всего — продолжить игровой цикл. Операции по воспроизведению звука будут полностью выполняться картой Sound Blaster и аппаратным обеспечением прямого доступа к памяти. Вашей программе не нужно ничего делать, за исключением собственно запуска этого процесса (и затем - его остановки), а это занимает всего несколько микросекунд.

Листинг 9.6. Полная программа воспроизведения звука.

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ////////////////////////////////////////

#include <io.h>

#include <stdio.h>

#include <stdlib.h>



#include <dos.h>

#include <bios.h>

#include <fcntl.h>

// ГЛОБАЛЬНЫЕ ПEPEMEHHЫE //////////////////////////////

char   far *driver_ptr;

unsigned version;

char _huge *data_ptr;

unsigned ct_voice_status;

// ФУНКЦИИ ////////////////////////////

void Voc_Get_Version(void)

{

// получить версию драйвера и вывести, ее на экран

_asni

{                                                                                                                            

mov bx,0        ; функция 0 возвращает номер версии

call driver_ptr ; вызов

драйвера

mov version,ax

; сохранить номер версии

} // конец

ассемблерной вставки

printf("\nVersion of Driver = %Х.0%Х",

((version>>8) & 0x00ff), (version&0x00ff));

}

// конец

функции

////////////////////////////////////////////////////////////

int Voc_lnit_Driver(void)

{

// инициализация драйвера функция возвращает слово состояния

int status;

_asm

{

mov bx,3        ; функция инициализации драйвера имеет номер 3

call driver_ptr ; вызов

драйвера

mov status,ах   ; сохранение номера версии

}// конец ассемблерной вставки

// возвратить слово состояния

printf("\nDriver Initialized");

return(status);

} // конец

функции

////////////////////////////////////////////////////////////

int Voc_Terminate_Driver(void)

{

// прекратить работу драйвера

_asm {

mov bx,9        ; функция 9 прекращает работу драйвера

call driver_ptr ; вызов драйвера

} // конец ассемблерной вставки

// освобождение памяти

_dos_freemem(_FP_SEG(driver_ptr));

printf("\nDriver Terminated");

} // конец функции

////////////////////////////////////////////////////////////

void Voc_Set_Port(unsigned port) {

// установить адрес порта ввода/вывода Sound Blaster

_asm

{

mov bx,l        ; функция 1 устанавливает адрес порта ввода/вывода

mov ax,port     ; поместить адрес порта ввода/вывода в регистр АХ

call driver_ptr ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции



////////////////////////////////////////////////////////////

void Voc_Set_Speaker(unsigned on)

{

// включить/выключить вывод звука

_asm {

mov bx,4       ; функция 4 включает или выключает вывод звука

mov ах,on       ; поместить флаг включить/выключить в регистр АХ

call driver_ptr ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции

////////////////////////////////////////////////////////////

int Voc_Play_Sound(unsigned char far *addr,

unsigned char header_lehgth)

{

// проигрывает загруженный в память VOC-файл

unsigned segm,offm;

segm = _FP_SEG(addr);

offm = _FP_OFF(addr) + header_length;

asm

{

mov bx,6        ;Функция 6: воспроизведение"VOC-файла

mov ax,segm

mov es,ax       ; в регистр ES загружается сегмент

mov di,offm     ; и регистр DI загружается смещение

call driver_ptr ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции

////////////////////////////////////////////////////////////

int Voc_Stop_Sound(void)

{

// прекращает воспроизведение звука

_asm

{ mov bx,8        ; функция 8 прекращает воспроизведение звука

call driver_ptr ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции

////////////////////////////////////////////////////////////

int Voc_Pause_Sound(void)

{

// приостанавливает воспроизведение звука

_asm

{

mov bx,10       ; функция 10 приостанавливает

                ; воспроизведение звука

call driver_ptr ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции /////////////////////////////////////////////////

int Voc_Continue_Sound(void)

{ // продолжает воспроизведение приостановленного звука

asm

{

mov bx,11        ; функция 11 продолжает воспроизведение

                 ; приостановленного звука

call driver_ptr  ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции /////////////////////////////////////////////////

int Voc_Break_Sound(void)

{

// прерывает цикл воспроизведения звука

_asm

{

mov bx,12       ; функция 12 прерывает цикл воспроизведения звука



call driver_ptr ; вызов

драйвера

} // конец ассемблерной вставки

} // конец функции //////////////////////////////////////////////////////////

void Voc_Set_DMA(unsigned dma)

{

// устанавливает номер прерывания прямого доступа к памяти

_asm

{

mov  bx,2        ; функция 2 устанавливает номер прерывания

                 ; прямого доступа к памяти

mov ax,dma       ; поместить в регистр АХ

                 ; номер прерывания прямого доступа в память

call driver_ptr  ; вызов драйвера

} // конец ассемблерной вставки

} // конец функции ////////////////////////////////////////////////////////////

Voc Set_Status_Addr(char _far *status)

{

unsigned segni,offm;

segm = _FP_SEG(status);

offm = _FP_OFF(status);

// задает переменную слова состояния

asm               

{

mov bx,5        ; функция задания переменной слова состояния

                ; имеет номер 5

mov еs, segm    ; в регистр ез загружается сегмент переменной

mov di, offm    ; в регистр di загружается смещение переменной

call driver_ptr ; вызов

драйвера

} // конец ассемблерной вставки

} // конец функции

///////////////////////////////////////

void Voc_Load_Driver(void) {

// загрузить CT-VOICE.DRV

int driver_handle; unsigned errno,segment_offset,num_para,bytes_read;

// открыть

файл драйвера

_dos_open("CT-VOICE.DRV", _O_RDONLY, &driver_handle);

// выделить

память

num_para= 1 + (filelength(driver__handle))/16;

_dos_allocmem(num_para, &segment);

// установить указатель на область данных драйвера

_FP_SEG(driver_ptr) = segment;

_FP_OFF(driver_ptr) =0;

// загрузить

код драйвера data_ptr = driver_ptr;

do

{

_dos_read(driver_handle,data_ptr, 0х4000, &bytes_read);

data_ptr += bytes_read;

} while (bytes_read==0x4000);

// закрыть

файл

_dos_close(driver_handle);

} // конец

функции

////////////////////////////////////////////////////////////

char far *Voc_Load_Sound(char *filename,

unsigned char *header_length)

{ // загрузка звукового файла с диска в память



// и установка указателя на его начало

char far *temp_ptr;

char far *data_ptr;

unsigned int sum;

int sound handle,t; unsigned errno, segment, offset, num_para, bytes_read;

// Открыть

звуковой файл

_dos_open(filename, _O_RDONLY, &sound_handle);

// Выделить

память

num_para = 1 + (filelength(sound_handle))/16;

_dos_allocmem(num_para,&segment);

// Установить указатель на выделенную память

_FP_SEG(data_ptr) = segment;

_FP_OFF(data_ptr} = 0;

// Загрузить звуковые данные

temp_ptr

= data_ptr;

do

{

_dog_read(sound_handle,temp_ptr, 0х4000, &bytes_read) ;

temp_ptr += bytes_read;

sum +=bytes_read;

} while (bytes_read==0х4000);

// проверить на всякий случай, звуковые ли это данные;

// для этого проверяется присутствие слова "Creative"

if ((data_ptr[0] !='C') || (data_ptr[1] != 'r'))

{

printf("\n%s is not a voc file!",filename);

_dos_freemem(_FP_SEG(data_ptr));

return(0);

} // конец звукового файла

*header_length = (unsigned char)data_ptr[20];

// закрыть

файл

_dos_close(sound_handle) ;

return(data_ptr);

} //конец

функции

/////////////////////////////////////////////////////////////

void Voc_Unload_Sound(char far *sound_ptr) {

// удаление звуковых данных из памяти

_dos_freemem(_FP_SEG(sound_ptr));

) // конец

функции

////////////////////////////////////////////////////////////

void main(void)

{

char far *sounds[4];

unsigned char lengths[4];

int done=0,sel;

Voc_Load_Driver();

Voc_Init_Driver ();

Voc_Set_Port (0х220);

Voc_Set_DMA(5) ;

Voc_Get_Version();

Voc Set_Status_Addr((char _far *) &ct_voice_status) ;

// загрузка

звуковых файлов

sounds[0] = Voc_Load_Sound("beav.voc" , & lengths[0]);

soundsll] = Voc_Load_Sound("ed209.voc", &lengths[1]);

sounds[2] = Voc_Load_Sound{"term.voc",   &lengths[2]);

sounds[3] = Voc_Load_Sound("driver.voc"f &lengths[3]);

Voc_Set_Speaker(1);

// главный цикл событий;.позволяет пользователю

// выбрать звуковой файл.



// Заметьте, что воспроизведение текущего звука можно прервать

while(!done)

{

printf("\n\nSound Demo Menu");

printf("\nl - Beavis");

printf("\n2 - ED 209") ;

printf("\n3 - Terminator");

printf("\n4 - Exit");

printf("\n\nSelect One ? ");

scant("%d",&sel);

switch (sel)

{

case 1:

{

Voc_Stop_Sound();

Voc Play_Sound(sounds[0] , lengths[0]);

} break;

case 2:

{

Voc_Stop_Sound();

Voc_Play_Sound(sounds[1],  lengths[1]);

} break;

case 3:

{

Voc_Stop_Sound(l ;

Voc_Play_Sound(sounds[2] , lengths[2]);

} break;

case 4:

{

done = 1;

} break;

default:

{

printf("\nFunction %d is not a selection.",sel) ;

} break;

} // конец оператора switch

} // конец цикла while

// закончить работу

Voc_Play_Sound(sounds[3], lengths[3]);

// ожидание окончания звуковой последовательности

// слово состояния имеет значение -1 при воспроизведении звука

// и 0 - в;противном случае.

while(ct_voice_status()) {}

Voc_Set Speaker(O);

// выгрузить

звуковые файлы

Voc_Unload_Sound(sounds[0]);

Voc_Unload_Sound(sounds[1]) ;

Voc_Unload_Sound(sounds[2]);

Voc_Unload_Sound(sounds[3]);

Voc_Terminate_Driver ();;

} // конец функции main


Содержание раздела