Kurs C na Atari cz.3 by Ilmenit 2010-08-26 20:18:14

Jakub "Ilmenit" Dębski napisał:



Odtwarzanie muzyki i dźwięków

Odtwarzanie muzyki i dźwięków pokażę na przykładzie "Raster Music Trackera". Podczas pisania tego tekstu jego aktualna wersja to 1.28 (rmt128.zip). Pakiet "Raster Music Tracker" ułatwia wykorzystanie muzyki w programach. W katalogu rmt128/asm_src/sfx znajduje się przykład pliku RMT zawierającego muzykę i dźwięki oraz program w asemblerze odtwarzający je. Dla osoby znającej asembler jest to przykład bardzo prosty w użyciu, ponieważ wystarczy dołączyć ten odtwarzacz do własnego asemblerowego kodu. Spróbujmy równie prostym uczynić odtwarzanie muzyki w C :-). W tym celu zmodyfikowałem odtwarzacz asemblerowy, oraz stworzyłem cztery funkcje dostępne z języka C (pliki sounds.c oraz sounds.h):

void init_sfx();
void play_sfx(unsigned char effect);
void play_music(unsigned char track_number);
void stop_music();


W naszym przykładzie wykorzystamy plik sfx.rmt z powyższego katalogu (rmt128/asm_src/sfx), ponieważ zawiera on przykładową muzykę i efekty dźwiękowe, ma również zerową ścieżkę pustą, a mój kod wykorzystuje ją, aby możliwe było przerwanie odtwarzania muzyki. Program w C odtwarzający muzykę i dźwięki wygląda następująco:


#include < stdio.h >
#include < conio.h >
#include < peekpoke.h >
#include "atari_defs.h"
#include "sounds.h"

int main(void)
{
unsigned char key;
puts("\nPress a key to:\n");
puts("1: Play music");
puts("2: Stop music");
puts("A-H: Play sound");

// Turn off the keyboard sound
POKE(NOCLIK,1);

init_sfx();

for(;;)
{
key=cgetc();
if (key=='1')
play_music(2); // Track number
else if (key=='2')
stop_music();
else if (key > ='a' && key < ='h')
play_sfx(key-'a'+1);
}
return 0;
}


Prosto, prawda?

Żeby było tak prosto potrzebujemy ustawić kilka rzeczy. Przede wszystkim musimy ustalić miejsce, pod które będzie ładowana muzyka i jej odtwarzacz. Wykorzystamy do tego przesunięcie początku naszego programu za pomocą STARTADDRESS z pliku konfiguracyjnego linkera. Aby sprawdzić ile miejsca potrzebuje muzyka otwieramy plik sfx.rmt w "Raster Music Tracker". W tym przypadku pokaże również informację, że jest on „ogołocony” z nazw utworów i instrumentów:



Takie „ogołocenie” jest jedną z optymalizacji wielkości utworu, którą przygotował Raster w RMT. Ten przykładowy utwór został już przygotowany do odtwarzania w programach spod adresu $4000 do $42AA. My chcemy umieścić go nisko w pamięci, w okolicach $2000, aby nasz program był ponad tym. W tym celu wybieramy menu "File->Export As… RMT stripped song file":



Po wybraniu nazwy naszym oczom ukaże się okno eksportowania. Włączamy „SFX support” zaś adres ustawiamy na jakiś charakterystyczny, na przykład $2345, co przyda nam się w analizie stworzonego pliku, którą niestety będziemy musieli przeprowadzić przy optymalizacji jego wielkości.



Klikamy na „Copy all to clipboard” i to, co jest w schowku wklejamy do pliku sfx/rmt_feat.a65. Informacje w pliku rmt_feat.a65 są opcjami kompilacji odtwarzacza. Kompilowane są tylko elementy niezbędne do poprawnego odtworzenia utworu, przez co plik wynikowy jest mniejszy. Następnie klikamy OK.

Teraz musimy skompilować kod odpowiadający za odtwarzanie tego utworu (lub utworów i dźwięków, jeżeli jest ich więcej w danym pliku .rmt). Potrzebujemy do tego "MADS" dostępny na tej stronie. Ścieżkę do pliku mads.exe najwygodniej dodać do systemowej %PATH%.

W katalogu PiszemyGre8/sfx przygotowałem specjalną wersję asemblerowego odtwarzacza. Przy jego kompilacji jest założone, że plik z muzyką znajduje się w tym katalogu i nazywa się sfx.rmt. Uruchamiamy sfx.bat, który kompiluje odtwarzacz i powstaje plik wynikowy sfx.xex. Raster zadbał o to, żeby plik wynikowy był jak najmniejszy, ale przez to musimy ręcznie ustawić trzy adresy w kompilacji. Żeby je określić najwygodniej użyć programu "CHKXEX" dostępnego tutaj. Jest on również dostępny w katalogu PiszemyGre8/sfx i jest automatycznie uruchamiany przez sfx.bat – tworzy plik sfx.info dla poprawnie skompilowanego pliku:

sfx>chkxex.exe sfx.xex
$2082 - $2091 : $0010
$2100 - $21BF : $00C0
$2200 - $261F : $0420
$2345 - $25EF : $02AB
$3FE0 - $403B : $005C
RUN $3FFE
File 'sfx.xex' is OK.


Widzimy tutaj segmenty pliku wykonywalnego i adresy, pod które są ładowane. Zależą one od liczby instrumentów użytych w utworze, efektów specjalnych, których użył muzyk, itp. Musimy doprowadzić do sytuacji, w której pierwszy segment zaczyna się od $2000, a kolejne nie zachodzą na siebie. Do prostej modyfikacji tych adresów stworzyłem plik sfx/addresses.a65. Zawiera on wszystkie adresy, które musimy ręcznie ustawić:

PLAYER equ $2300
MODUL equ $2345
SFX_CODE equ $3FE0


Pierwsza wartość PLAYER określa adres odtwarzacza RMT. Raster pisze w dokumentacji:

;* 1. RMT player routine needs 19 itself reserved bytes in zero page (no accessed
;* from any other routines) as well as cca 1KB of memory before the "PLAYER"
;* address for frequency tables and functionary variables. It's:
;* a) from PLAYER-$03c0 to PLAYER for stereo RMTplayer
;* b) from PLAYER-$0320 to PLAYER for mono RMTplayer
;*
;* 2. RMT player routine MUST (!!!) be compiled from the begin of the memory page.
;* i.e. "PLAYER" address can be $..00 only!


Tak więc wartość PLAYER musi być formatu $..00. Ona odpowiada za pierwszy segment w pliku (tu: $2082 dla wartości PLAYER equ $2300). Obniżenie PLAYER na $2200 spowoduje, że pierwszy segment zacznie się od $1F82, a my chcemy minimum od $2000, czyli pozostawiamy wartość poprzednią.

Druga wartość MODUL musi być taka, jak ustawiliśmy w "Raster Music Tracker" przy "Export As…". Przy ustawieniu wartości $2345 widzimy, że zachodzi ona na segment poprzedni, który jest aż do $261F. Musimy zatem ponownie wyeksportować plik sfx.rmt, przy czym jako adres eksportowania dać adres większy niż $261F, czyli $2620. Tą samą wartość wpisajemy do pliku sfx/addresses.a65.



Uruchamiamy ponownie kompilację odtwarzacza (sfx.bat), żeby zobaczyć jakąś wartość musi przyjąć SFX_CODE. Adres SFX_CODE odpowiada za ostatni segment i musimy umieścić go tak, aby nie zachodził na segment przedostatni. Dla segmentu przedostatniego $2620 - $28CA, SFX_CODE musi być co najmniej $28CB.

Uruchamiamy kompilację po raz ostatni i otrzymujemy plik sfx.xex, który wygląda następująco:

$2082 - $2091 : $0010
$2100 - $21BF : $00C0
$2200 - $261F : $0420
$2620 - $28CA : $02AB
$28CB - $2926 : $005C
RUN $28E9
File 'sfx.xex' is OK.


Segmenty nie zachodzą już na siebie, pierwszy zaczyna się tak blisko $2000 jak to możliwe, zaś ostatni segment kończy się w $2926. Mamy zatem skompilowany plik odtwarzacza, który zawiera również muzykę. Musimy teraz połączyć plik odtwarzacza z naszym głównym plikiem wykonywalnym. W głównym pliku wykonywalnym robimy miejsce na ten plik (w .cfg ustalamy STARTADDRESS: default = $2927;). Następnie wykorzystujemy mój program "merge-xex", który łączy dwa pliki wykonywalne w jeden. Jest on automatycznie wywoływany w przykładowym skrypcie budującym program:

cl65 -Osir -C atari.cfg -t atari main.c sounds.c -o temp.xex
merge-xex/merge-xex.exe sfxsfx.xex temp.xex hello.xex
del temp.xex


To wszystko – mamy działający program odtwarzający muzykę i dźwięki:



Wyjaśnienia wymaga jeszcze skąd nasz kod w C wie, pod jakimi adresami znajdują się funkcje odtwarzacza będące w innym pliku wykonywalnym. Wyjaśnienie jest w pliku budującym odtwarzacz, czyli sfx/sfx.bat:

mads.exe -hc:sfx.h sfx.a65 /o:sfx.xex

Dodanie opcji „-hc:sfx.h” generuje plik nagłówkowy języka C, który zawiera adresy wszystkich wykorzystywanych etykiet, procedur i zmiennych. Jest on dołączany w sounds.c.

I to już wszystko. Całość byłaby mniej skomplikowana, gdyby kompilować odtwarzacz ze wszystkimi możliwymi bajerami, ale skompilowany w powyższy sposób jest znacznie mniejszy, co ma wielkie znaczenie przy pisaniu gry.

Wszystkie pliki trzeciej części kursu dostępne są tutaj.
Kaz 2010-08-26 21:53:37

Tym razem w listingu wcielo slash, ech...

0xF 2010-08-27 09:31:46

Chyba backslash?

Kaz 2010-08-27 10:00:04

Tak, backslash.

Ilmenit 2010-08-27 11:13:22

Poprawiłem formatowanie wrzuconego przez Kaza artka.

Informacyjnie dla przerażonych tymi ustawieniami adresów segmentów :-) To nie kwestia zastosowanego języka C, ale tego w jaki sposób Raster napisał odtwarzacz muzyki. Te same operacje trzeba by zrobić, gdybyśmy pisali grę w ASMie.

Kaz 2010-08-27 11:40:57

Dzieki. Ale zamiana backslash-y na slashe albo ich usuniecie (puts("nPress a key to:n") to raczej polowiczne rozwiazanie. Ale skorzystam z podeslanego przez Ciebie linku na rozwiazanie stale w CuteNews, dzieki :)

jhusak 2010-08-28 01:25:14

Ilmenit, prościzna!

insert 2010-08-28 14:15:23

dzieki za kolejna czesc!