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.