Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek

©2024 mgr Jerzy Wałaszek
I LO w Tarnowie

ROZDZIAŁ 7

Dźwięk

Podrozdziały
Komputer domowy ATARI 400/800™ posiada duże możliwości sprzętowej generacji dźwięku. Istnieją cztery niezależnie sterowane kanały dźwiękowe, które mogą odtwarzać dźwięk równocześnie. Każdy kanał posiada rejestr częstotliwości określający nutę oraz rejestr sterujący głośnością i zawartością szumu. Kilka opcji pozwalają ci wstawiać filtry górnoprzepustowe, wybierać podstawy taktowania, ustawiać alternatywne tryby pracy i modyfikować liczniki LFSR.

Definicje terminów i konwencji

Dla celów dyskutowanych w tym rozdziale kilka terminów i konwencji musi zostać wyjaśnionych:

1 Hz (herc) to jeden impuls w ciągu jednej sekundy
1 kHz (kiloherc) to 1.000 impulsów na sekundę
1 MHz (megaherc) to 1.000.000 impulsów na sekundę

"Impuls" jest nagłym wzrostem napięcia, po którym nieco później następuje nagły spadek napięcia. Jeśli jakiś impuls zostanie wysłany na głośnik telewizora, usłyszysz pojedynczy trzask, pyknięcie.

"Fala" jest używana tutaj w znaczeniu ciągłego szeregu impulsów. Istnieją różne rodzaje fal, które rozróżniamy po kształcie indywidualnych impulsów. Fale tworzone przez komputer Atari są falami prostokątnymi (jak na rys.7-2). Instrumenty dęte zwykle wytwarzają fale trójkątne, a śpiewak wytwarza fale sinusoidalne (przedstawione na rys.7-15).

Rejestr przesuwający jest jak komórka pamięci (ponieważ przechowuje dane binarne), której zawartość może zostać przesunięta bitowo w prawo o jedną pozycję, tj. bit 5 otrzyma poprzednią zawartość bitu 4, a bit 4 otrzyma to, co było w bicie 3, itd. Stąd ostatni bit z prawej strony jest wypychany na zewnątrz, a pierwszy bit z lewej strony otrzymuje wartość z linii wejściowej (patrz rys.7-1).

obrazek

Rys.7-1 Przepływ bitów w rejestrze przesuwającym

AUDF1-4 należy czytać jako "dowolny z rejestrów częstotliwości audio o numerze od 1 do 4". Ich adresy to: $D200, $D202, $D204, $D206 (53760, 53762, 53764, 53766).

AUDC1-4 należy czytać jako "dowolny z rejestrów sterujących audio o numerze od 1 do 4". Adresy: $D201, $D203, $D205, $D207 (53761, 53763, 53765, 53767).

W rozdziale tym częstotliwość jest miarą liczby impulsów w danym okresie czasu; na przykład nuta o częstotliwości 100 Hz oznacza, iż w ciągu jednej sekundy pojawi się dokładnie 100 impulsów. Im częściej (stąd określenie częstotliwość) występują te impulsy w nucie, tym wyższa jest nuta. Na przykład śpiewak śpiewa z wysoką częstotliwością (być może 5 kHz), a krowa ryczy z niską częstotliwością (być może 100 Hz). Słowa "częstotliwość", "nuta", "ton", "wysokość dźwięku" są używane zamiennie.

"Szum" i "zniekształcenie" są używane zamiennie, chociaż ich znaczenie nie jest takie samo. "Szum" jest bardziej dokładnym określeniem funkcji wykonywanej przez komputer ATARI.

Przerwanie występujące z częstotliwością 60 Hz, do którego odwołujemy się dalej w tym rozdziale, jest zwane również przerwaniem wygaszania pionowego (ang. vertical blank interrupt).

Wszystkie przykłady są w języku BASIC, chyba że zaznaczono inaczej. Wpisuj je dokładnie tak, jak są podane. Jeśli nie ma numerów wierszy, nie wpisuj żadnych; a jeśli kilka rozkazów jest w tym samym wierszu, wpisze je też w ten sposób.


Na początek:  podrozdziału   strony 

Sprzęt dźwiękowy

Dźwięk jest generowany w komputerze ATARI przez układ POKEY, który obsługuje również szeregową magistralę we/wy oraz klawiaturę.

obrazek
POKEY

Układ POKEY musi być poddany inicjalizacji, zanim będzie mógł poprawnie pracować. Inicjalizacja jest wymagana po każdej operacji na magistrali szeregowej (magnetofon kasetowy, stacja dysków, drukarka, odczyt/zapis poprzez RS-232). Aby zainicjować układ POKEY z poziomu BASIC'a, wykonaj pusty rozkaz dźwiękowy, tj. SOUND 0,0,0,0. W języku maszynowym umieść 0 w rejestrze AUDCTL ($D208 = 53768) i 3 w SKCTL ($D20F = 53775, z kopią w RAM pod adresem $232 = 562).

AUDF1-4

Każdy kanał audio posiada odpowiedni rejestr częstotliwości, który steruje odtwarzaniem nuty przez komputer. Rejestr częstotliwości przechowuje liczbę "N" używaną w obwodzie podziału przez N. Ten podział nie jest w sensie matematycznym, lecz raczej czymś dużo prostszym: dla każdych N impulsów wchodzących, wychodzi 1 impuls. Na przykład, rys.7-2 pokazuje funkcję podziału przez 4:

obrazek

Rys.7-2 Operacja podziału przez 4

Gdy N rośnie, impulsy wyjściowe pojawiają się rzadziej, co powoduje obniżenie nuty.

AUDC1-4

Każdy kanał posiada również odpowiedni rejestr sterujący. Rejestry te pozwalają ustawić głośność oraz zniekształcenie w każdym z kanałów. Przydział bitów dla AUDC1-4 jest następujący:

AUDC1-4

Numer bitu 1 2 3 4 5 6 7 8
  zniekształcenie bit tylko
głośności
   głośność   

Rys.7-3 Przydział bitów w rejestrach AUDC1-4

Głośność

Sterowanie głośnością dla każdego kanału audio jest bezpośrednie. Dolne 4 bity każdego rejestru sterującego audio zawierają 4-bitową liczbę, która określa głośność dźwięku. Zero w tych bitach oznacza zerową głośność, a 15 oznacza głośność maksymalną. Suma głośności wszystkich kanałów nie powinna przekraczać 32, ponieważ powoduje to przesterowanie wyjścia dźwiękowego. Wytwarzany dźwięk ma wtedy tendencję do utraty swojej głośności i brzmi jak brzęczyk.

Zniekształcenie

Rys.7-3 pokazuje, że każdy kanał posiada w rejestrze sterującym trzy bity sterujące zniekształceniem. Zniekształcenie jest wykorzystywane do tworzenia specjalnych efektów dźwiękowych, gdy czysty ton nie jest pożądany.

Komputerowe wykorzystanie zniekształcania oferuje dużą różnorodność i możliwość modyfikacji dźwięków. Możliwe jest syntezowanie prawie nieskończonej różnorodności dźwięków, od huków, grzechotów i skrzeków do kliknięć, świstów i dźwięków środowiska.

Zniekształcenia w używanej tutaj postaci nie odpowiadają standardowej interpretacji. Na przykład "zniekształcenia intermodulacyjne" i "zniekształcenia harmoniczne" są kryteriami jakościowymi określanymi dla systemów hi-fi stereo. Te typy zniekształceń odnoszą się do degeneracji przebiegu fali, gdzie kształt fali jest nieco zmieniany z powodów błędów w układzie elektronicznym. Zniekształcenia komputerowe nie zmieniają fal (zawsze są one falami prostokątnymi), lecz raczej usuwają z przebiegu fali wybrane impulsy. Ta technika nie jest adekwatnie precyzowana za pomocą słowa "zniekształcenie". Bardziej opisowym i odpowiednim terminem dla tych metod zniekształceń będzie "szum".

Zanim w pełni opanujesz znaczenie zniekształceń, musisz zrozumieć działanie rejestru przesuwającego z liniowym sprzężeniem zwrotnym – LFSR (ang. linear feedback shift register). Rejestry LFSR są zatrudnione w komputerze ATARI w charakterze źródła przypadkowych impulsów wykorzystywanych w generacji szumu. Liczniki LFSR komputera ATARI używają rejestru przesuwającego pracującego przy częstotliwości 1,79 MHz. Zawartość rejestru przesuwającego jest przesuwana i wprowadzana z powrotem na wejście; tworzy to pseudolosowy ciąg bitów na wyjściu tego rejestru przesuwającego.

Na przykład na poniższym schemacie stara wartość bitu 5 zostanie wypchnięta z rejestru przesuwającego i stanie się kolejnym impulsem wyjściowym, a bit 1 stanie się funkcją EX-NOR bitów 3 i 5:

obrazek

Rys.7-4 Rejestr przesuwający LFSR

Procesor bitów pobiera wartości z pewnych bitów w rejestrze przesuwającym (u góry są to bity 3 i 5) i przetwarza je w sposób nieistotny dla tej dyskusji. Na wyjściu wytwarza on wartość, która staje się bitem nr 1 rejestru przesuwającego LFSR.

Te rejestry LFSR nie produkują przebiegu w pełni przypadkowego, ponieważ powtarzają swój ciąg bitów po upływie pewnego okresu czasu. Jak podejrzewasz, ich częstotliwość powtarzania zależy od liczby bitów w rejestrze LFSR, tj. dłuższe rejestry wymagają wielu taktów, zanim nastąpi powtórka, natomiast krótsze powtarzają częściej.

W komputerze ATARI zniekształcenie jest osiągane przez wykorzystanie przypadkowych impulsów z tych rejestrów LFSR w układzie wybierającym, który właściwie jest cyfrowym komparatorem, lecz określenie "układ wybierający" jest bardziej opisowe. Jedyne impulsy, które przedostają się na wyjście, to impulsy współgrające z impulsami przypadkowymi. W ten sposób przypadkowo z przebiegu są eliminowane różne impulsy. Rys.7-5 ilustruje tę metodę wybierania. Linią przerywaną połączono impulsy, które ze sobą współgrają.

obrazek

Rys.7-5 Funkcja wybierająca wykorzystywana do wstawiania zniekształcenia

Efekt końcowy polega na tym, iż niektóre impulsy z układu dzielnika częstotliwości zostają usunięte. Oczywiście, jeśli usunie się impulsy z przebiegu, to nuta będzie brzmiała inaczej. W ten sposób wprowadza się zniekształcenie do kanału dźwiękowego.

Ponieważ rejestry LFSR powtarzają swoją sekwencję bitową, to ich wyjściowy wzór impulsów jest cykliczny. A ponieważ układ wybierający wykorzystuje ten wzór wyjściowy do usuwania impulsów z oryginalnej nuty, to zniekształcona nuta będzie zawierała ten sam powtarzający się wzór. Pozwala to sprzętowo tworzyć dźwięki takie jak brzęczenie, warkot silników oraz inne posiadające powtarzający się wzór.

Komputer ATARI wyposażony jest w trzy rejestry LFSR o różnych długościach, które można łączyć ze sobą na różne sposoby w celu wytworzenia interesujących efektów dźwiękowych. Mniejsze rejestry LFSR (4 i 5-cio bitowe) powtarzają wystarczająco często, aby stworzyć brzęczące dźwięki, które narastają i opadają szybko, natomiast większy rejestr LFSR (o długości 17 bitów) powtarza z tak dużym okresem, iż nie można rozpoznać w nim żadnego wzoru. Ten 17-to bitowy rejestr LFSR może zostać wykorzystany do tworzenia nieregularnych dźwięków eksplozji, syku pary oraz dowolnego dźwięku, gdzie wymagany jest przypadkowy przebieg impulsów.

Każdy kanał audio  oferuje sześć różnych kombinacji tych czterech rejestrów LFSR:

obrazek
Uwaga: "zegar" oznacza częstotliwość wejściową
"x" oznacza, że stan bitu nie ma znaczenia
Rys.7-6

Te trzy górne bity rejestrów AUDC1-4 sterują trzema przełącznikami w obwodzie audio, jak pokazano niżej. Schemat ten pozwoli ci zrozumieć, dlaczego tabelka z rys.7-6 ma taką właśnie postać:

obrazek

Rys.7-7 Schemat blokowy sterowania AUDC1-4

Każda kombinacja rejestrów LFSR oferuje unikalny dźwięk. Co więcej, zniekształcone dźwięki brzmią inaczej przy różnych częstotliwościach. Z tego powodu niezbędne jest testowanie różnych kombinacji, aby znaleźć zniekształcenie i częstotliwość, które wytworzą pożądany efekt dźwiękowy. Poniżej przedstawiono kilka wskazówek na początek:

obrazek

Rys.7-8 Dźwięki wytwarzane przez różne kombinacje zniekształceń i częstotliwości

Dźwięk tworzony tylko głośnością

Bit nr 4 rejestrów AUDC1-4 określa tryb tworzenia dźwięku tylko głośnością. Jeśli zostanie ustawiony, to wartość głośności w bitach 0...3 jest wysyłana bezpośrednio na głośnik telewizora i nie jest modulowana częstotliwością określoną w rejestrach AUDF1-4.

Aby w pełni zrozumieć wykorzystanie tego trybu pracy, musisz zrozumieć, jak działa głośnik i co się z nim dzieje, gdy otrzymuje impuls. Każdy głośnik posiada membranę, która porusza się na zewnątrz i do wewnątrz.

obrazek
Głośnik

Pozycja membrany w dowolnej chwili jest bezpośrednio proporcjonalna do napięcia otrzymywanego z komputera. Jeśli wysłane napięcie ma wartość zero, to membrana znajduje się w pozycji spoczynkowej. Gdy membrana porusza się, przesuwa powietrze, co jest odbierane przez nasze ucho jako dźwięk.

Z definicji impulsu wiadomo, że składa się on z narastającego napięcie, po którym następuje napięcie opadające. Jeśli miałbyś wysłać impuls na głośnik, to wypchnąłby on membranę przy narastającym napięciu i wciągnąłby ją z powrotem przy napięciu opadającym, co wytworzyłoby falę powietrzną odbieraną przez ucho jako trzask. Poniższe polecenia wytworzą taki trzask w głośniku telewizora przez wysłanie pojedynczego impulsu:

POKE 53761,31:POKE 53761,16

Strumień impulsów (lub fala) wywołałby ciągły ruch membrany głośnika, co byłoby słyszalne jako ciągłe brzęczenie lub nuta. Im szybciej są wysyłane te impulsy, tym wyższa nuta. W ten sposób komputer generuje dźwięk poprzez głośnik telewizora.

Ważne jest zauważenie, iż w trybie tylko głośności dźwięku głośność wysłana na głośnik nie spada automatycznie do zera, lecz raczej pozostaje na stałym poziomie aż program ją zmieni. Aby utworzyć dźwięk, program powinien modulować tę głośność wystarczająco często. Teraz wypróbuj poniższe polecenia, nasłuchując uważnie po każdym z nich:

POKE 53761,31
POKE 53761,31

Za pierwszym razem usłyszysz trzask, czego można się spodziewać. Głośnik wypchnął powietrze membraną. Lecz za drugim razem nic nie usłyszysz. Dzieje się tak, ponieważ membrana głośnika znajduje się już w pozycji wypchniętej i drugi rozkaz nie zmieni tej pozycji, dlatego nic nie było słychać. Teraz spróbuj tego:

POKE 53761,16
POKE 53761,16

Jak poprzednio, usłyszysz trzask za pierwszym razem, gdy głośnik cofnie membranę do pozycji spoczynkowej, a za drugim razem nic nie usłyszysz, ponieważ membrana głośnika jest już w pozycji spoczynkowej.

W ten sposób bit tylko głośności daje programowi pełną kontrolę nad pozycją membrany głośnika w dowolnej chwili. Chociaż podane przykłady są jedynie przykładami binarnymi (albo głośnik włączony, albo wyłączony), to w żaden sposób nie jesteś ograniczony do tego typu modulacji głośnika. Możesz ustawić membranę głośnika na dowolną z 16 pozycji.

Na przykład, prosta fala trójkątna (podobna do przebiegu wytwarzanego przez instrumenty dęte) może zostać wygenerowana przez wysłanie głośności 8, po której następują głośności 9, 10, 11, 10, 9, 8, 7, 6, 5, 6, 7 i z powrotem na 8 oraz szybkie powtarzanie tego ciągu w kółko. Przez wystarczająco szybką zmianę głośności można wytworzyć praktycznie każdy przebieg. Przykładowo nadaje się to do wykonywania syntezy mowy przy wykorzystaniu tej techniki. Wymaga to użycia języka asemblera. W następnym rozdziale podano więcej informacji na temat tego bitu.

AUDCTL

Jako dodatek do bajtów (AUDC1-4) sterujących niezależnie kanałami dźwięku istnieje opcjonalny bajt (AUDCTL), który wpływa na wszystkie cztery kanały. Każdy bit w AUDCTL ma przypisaną określoną funkcję:

AUDCTL ($D208 = 53768)
Bit
AUDCTL
Funkcja bitu, jeśli został ustawiony
0 Przełącza podstawę głównego zegara z 64 kHz na 15k Hz
1 Wstawia filtr górnoprzepustowy do kanału 2, który jest taktowany kanałem 4
2 Wstawia filtr górnoprzepustowy do kanału 1, który jest taktowany kanałem 3
3 Łączy ze sobą kanały 3 i 4 w kanał 16-bitowy
4 Łączy ze sobą kanały 1 i 2 w kanał 16-bitowy
5 Taktuje kanał 3 zegarem 1,79 MHz
6 Taktuje kanał 1 zegarem 1,79 MHz
7 Przełącza 17-bitowy rejestr LFSR w rejestr 9-bitowy

Rys.7-9. Funkcje bitów w rejestrze AUDCTL

Taktowanie

Zanim przejdziemy do wyjaśnień opcji rejestru AUDCTL, należy wyjaśnić nowe pojęcie: taktowanie. Ogólnie zegar jest ciągiem impulsów do synchronizacji milionów wewnętrznych operacji, które zdarzają się w każdej sekundzie w dowolnym komputerze. Zegar centralny wytwarza impulsy w sposób ciągły, a każdy z nich nakazuje układom sprzętowym wykonać następny krok w ich operacjach. Być może pamiętasz, że dzielnik częstotliwości przez N wytwarza impuls wyjściowy co każde N impulsów wejściowych. Może zastanawiałeś się, skąd się biorą te impulsy wejściowe. Jest jeden główny zegar impulsów wejściowych, który pracuje z częstotliwością 1,79MHz; może on dostarczać tych impulsów. Są również drugorzędne zegary, które można wykorzystać jako zegary wejściowe. Rejestr AUDCTL pozwala ci wybrać, który z zegarów będzie wykorzystywany jako wejście do obwodu dzielenia przez N. Jeśli wybierzesz inny zegar, to wyjście z dzielnika częstotliwości ulegnie drastycznej zmianie.

Na przykład, wyobraź sobie, iż używasz zegara 15 kHz, a rejestr częstotliwości został ustawiony na dzielenie przez 8. Na wyjściu obwodu dzielenia przez N otrzymasz około 2 kHz. Lecz jeśli zmieniłeś wybór zegara na zegar 64 kHZ, a nie zmieniłeś rejestru częstotliwości, to co się stanie? Układ podziału przez N wciąż będzie wysyłał na wyjście jeden impuls po każdych 8 impulsach wejściowych, lecz częstotliwość wejściowa będzie teraz wynosiła 64 kHz. Wynikowa częstotliwość wyjściowa będzie równa 8 kHz.

Wzór na częstotliwość wyjściową (z dzielnika przez N) jest zupełnie prosty:

fwy – częstotliwość wyjściowa
fz – częstotliwość zegara
N – dzielnik

Ustawienie bitu nr 1 w rejestrze AUDCTL przełącza zegar 64 kHz na zegar 15 kHz. Istotne jest spostrzeżenie, iż po ustawieniu tego bitu na 1 każdy kanał dźwiękowy taktowany zegarem 64 kHz będzie teraz używał zegara 15 kHz. Podobnie poprzez ustawienie bitów 5 lub 6 możesz odpowiednio taktować kanały 3 lub 1 zegarem 1,79 MHz. Wytworzy to dużo wyższą nutę, jak demonstruje poniższy przykład:

SOUND 0,255,10,8       Włącz kanał 1, niski ton
POKE 53768,64 Set      Bit nr 6 AUDCTL

Opcja częstotliwości 16-bitowej

Ośmiobitowa rozdzielczość częstotliwości w rejestrach sterujących wydaje się udostępniać więcej niż wystarczającą rozdzielczość dla zadania wyboru dowolnej pożądanej częstotliwości. Są jednakze sytuacje, gdzie osiem bitów nie wystarcza. Zastanów się przykładowo, co się stanie, jeśli każemy wykonać komputerowi poniższe polecenia:

FOR I=255 TO 0 STEP -1:SOUND 0,I,10,8:NEXT I

Dźwięk początkowo płynnie staje się coraz wyższy, lecz gdy osiąga koniec swojego zakresu, częstotliwość rośnie w coraz większych krokach, co staje się zauważalnie niezdarne. Dzieje się tak dlatego, iż częstotliwość zegara jest dzielona przez coraz mniejsze liczby. 15 kHz podzielone przez 255 daje prawie to samo, co 15 kHz podzielone przez 254; lecz 15 kHz podzielone przez 2 jest bardzo odległe na skali wysokości dźwięku od 15 kHz podzielone przez 1. Jedynym sposobem rozwiązania tego problemu jest użycie jakiejś większej liczby, co pozwoli określić naszą częstotliwość z większą dokładnością. Środki realizacji tego celu są wbudowane w układ POKEY.


Wiosełka Atari

Bity 3 i 4 rejestru AUDCTL pozwalają połączyć ze sobą dwa 8-bitowe kanały dźwiękowe w jeden kanał 16-bitowy z rozszerzonym dynamicznym zakresem częstotliwości. Normalnie podzielnik częstotliwości w każdym kanale ma zakres od 0 do 255 (dzielnik 8 bitowy przez N). Połączenie dwóch kanałów zwiększa zakres tego dzielnika do wartości od 0 do 65535 (dzielnik 16 bitowy przez N). W tym trybie można zejść z częstotliwością poniżej 1 Hz. Poniższy program wykorzystuje dwa kanały w trybie 16-bitowym, oraz dwa wiosełka jako wejście częstotliwości. Podłącz zestaw wiosełek do portu 1, a następnie wpisz i uruchom poniższy program:

10 SOUND 0,0,0,0: REM Inicjalizacja dźwięku
20 POKE 53768,80: REM Zegar w kanale 1 1,79 Mhz. Kanał 2 taktowany z kanałem 1
30 POKE 53761,160:POKE 53763,168: REM Wyłącz kanał 1, włącz kanał 2 (czyste tony)
40 POKE 53760,PADDLE(0):POKE 53762,PADDLE(1)
50 GOTO 40: REM Przepisz stan wiosełek do rejestrów częstotliwości 

Prawe wiosełko zmienia częstotliwość dźwięku zgrubnie, a lewe wiosełko zmienia częstotliwość płynnie pomiędzy zmianami wprowadzonymi przez wiosełko prawe.

Program najpierw ustawia bity 4 i 6 rejestru AUDCTL, co oznacza "taktowanie kanału 1 częstotliwością 1,79 MHz i połączenie kanału 2 z kanałem 1". Gdy to się stanie, 8-bitowe rejestry obu kanałów staną się pojedynczym rejestrem 16-bitowym, który następnie będzie wykorzystywany do podziału częstotliwości zegara wejściowego. Następnie głośność kanału 1 jest ustawiana na zero. Ponieważ kanał 1 nie posiada już swojego własnego bezpośredniego wyjścia, to jego ustawienia jego głośności są dla nas bez znaczenia i zerujemy je. Rejestr częstotliwości kanału 1 jest używany jako młodszy bajt liczby podziałowej N przy generacji dźwięku, a rejestr częstotliwości kanału 2 zawiera starszy bajt N. Na przykład zapis 1 do rejestru częstotliwości kanału 1 spowoduje, że ta para będzie dzieliła przez 1. Zapis 1 do rejestru częstotliwości kanału 2 spowoduje podział przez 256. Natomiast wpisanie 1 do obu tych rejestrów spowoduje, że ta para będzie dzieliła zegar wejściowy przez 257.

Bit 3 rejestru AUDCTL można użyć do dokładnie takiego samego połączenia kanału 4 z kanałem 3.

Następujące instrukcje demonstrują niektóre ciekawe aspekty 16-bitowego dźwięku.

SOUND 0,0,0,0
POKE 53768,24
POKE 53761,168
POKE 53763,168
POKE 53765,168
POKE 53767,168
POKE 53760,240:REM spróbuj wstawić inne liczby do tych 4 następnych komórek
POKE 53764,252
POKE 53762,28
POKE 53766,49 

Filtry górnoprzepustowe

Bity 1 i 2 rejestru AUDCTL sterują filtrami górnoprzepustowymi w kanałach odpowiednio 2 i 1. Filtr górnoprzepustowy przepuszcza tylko wyższe częstotliwości. W przypadku tych filtrów przez wyższe częstotliwości rozumiemy częstotliwość wyższą niż częstotliwość wyjściowa innego kanału wybranego przez kombinację bitową rejestru AUDCTL. Na przykład, jeśli kanał 3 odtwarza ryk krowy "muu", a bit 2 w AUDCTL został ustawiony, to tylko dźwięki o częstotliwości wyższej od tego "muu" będą słyszane na kanale 1 (wszystko niższe od "muu" zostanie odfiltrowane):


Rys.7-10. Efekt filtru górnoprzepustowego wstawionego do kanału 1 i taktowanego kanałem 3

Filtr jest programowalny w czasie rzeczywistym, ponieważ kanał filtrujący może się zmieniać w locie. Otwiera to szerokie możliwości dla programisty. Filtry stosuje się głównie do tworzenia efektów specjalnych. Wypróbuj poniższe polecenia:

SOUND 0,0,10,0
POKE 53768,4
POKE 53761,168:POKE 53765,168
POKE 53760,254:POKE 53764,127 

Zmiana rejestru LFSR z 17 bitów na 9 bitów

Bit 7 w AUDCTL zmienia 17-bitowy rejestr LFSR na 9-bitowy. Im krótszy rejestr LSFR, tym częściej powtarza się wzór zniekształcenia i staje się on bardziej rozpoznawalny. Stąd zmiana rejestru LSFR 17-bitowego w 9-bitowy sprawi, iż generowany szum stanie się bardziej powtarzalny i przez to bardziej dostrzegalny. Wypróbuj poniższą demonstrację opcji 9-bitowego rejestru LSFR, słuchając uważnie, gdy jest wykonywany rozkaz POKE:

SOUND 0,80,8,8: REM Użyj 17-bitowego rejestru LSFR
POKE 53768,128: REM Zmień na rejestr 9-bitowy

Na początek:  podrozdziału   strony 

Sposoby programowej generacji dźwięku

Istnieją dwa podstawowe sposoby używania systemu generacji dźwięku w komputerze ATARI: statyczny i dynamiczny. Statyczna generacja dźwięku jest prostszą z tych dwóch: program ustawia kilka generatorów dźwiękowych; przez chwilę wykonuje inne zadania, a następnie wyłącza je. Dynamiczna generacja dźwięku jest trudniejsza: komputer musi ciągle uaktualniać parametry generatorów dźwiękowych podczas wykonywania programu. Na przykład:

Dźwięk statyczny     Dźwięk dynamiczny
SOUND 0,120,8,8
 
FOR X=0 TO 255
  SOUND 0,X,8,8
NEXT X

Statyczna generacja dźwięku

Statyczny dźwięk zwykle ograniczony jest do pisków, klików i brzęków. Są wyjątki. Dwoma przykładami są programy obrazujące efekty specjalne w podrozdziałach o filtrach górnoprzepustowych i 16-bitowym dźwięku. Innym sposobem uzyskania interesujących efektów jest wykorzystanie interferencji, jak w poniższym przykładzie:

SOUND 0,255,10,8
SOUND 1,254,10,8
Ten dziwny efekt jest wynikiem nakładania się na siebie fal o bliskiej częstotliwości. Zbadaj rys.7-11.  Pokazuje on dwa kanały niezależnie produkujące fale sinusoidalne o nieco różnych częstotliwościach oraz ich sumę. Krzywa sumy pokazuje ten dziwny wzór interferencyjny, gdy te dwa kanały zostaną zsumowane.


Rys. 7-11. Dwie fale sinusoidalne o różnych częstotliwościach i ich suma

Rys.7-11 pokazuje, iż w niektórych punktach fale się wspomagają, a w innych przeszkadzają sobie. Dodanie dwóch fal, których grzbiety zachodzą na siebie da w wyniku falę o dwukrotnie większej mocy lub głośności. Podobnie dodanie głośności dwóch fal, gdy jedna osiąga maksimum, a druga minimum spowoduje wygaszenie obu z nich. Na wykresie krzywej sumy możemy zaobserwować ten efekt. Na końcach wykresu głośność rośnie, ponieważ grzbiety i doliny fal w obu kanałach są blisko siebie, co prawie podwaja dźwięk. W kierunku środka wykresu fale przeszkadzają sobie wzajemnie i wynikowa krzywa staje się płaska. Interesującym projektem mogłoby być napisanie programu rysującego wykres wzoru zmieszania 2, 3 lub 4 kanałów jak na rys.7-11. Mógłbyś w ten sposób wykryć niektóre unikalne dźwięki.

Im mniejsza jest różnica częstotliwości pomiędzy dwoma kanałami, tym dłuższy jest wzór powtarzania. Aby to zrozumieć, narysuj kilka wykresów podobnych do rys.7-11 i przestudiuj interakcje. Jako przykład, wypróbuj poniższe rozkazy:

SOUND 0,255,10,8
SOUND 1,254,10,8
SOUND 1,253,10,8
SOUND 1,252,10,8

Gdy różnica częstotliwości rośnie, okres powtarzania skraca się.

Dźwięk dynamiczny

Bardziej skomplikowane efekty dźwiękowe zwykle wymagają użycia dynamicznych technik dźwiękowych. Dla programisty komputerów ATARI 400/800 dostępne są trzy metody dynamicznej generacji dźwięku: dźwięk w języku BASIC, dźwięk generowany w przerwaniach 60 Hz oraz dźwięk w kodzie maszynowym.

Dźwięk w języku BASIC

Język BASIC jest nieco ograniczony w swoich możliwościach generacji dźwięków. Jak zapewne zauważyłeś, rozkaz SOUND usuwa wszystkie specjalne ustawienia rejestru AUDCTL.  Problem ten daje się rozwiązać poprzez bezpośrednie wstawianie wartości do rejestrów dźwiękowych zamiast używania rozkazu SOUND.

Dodatkowo BASIC ogranicza jego szybkość działania. Jeśli program nie jest w całości przeznaczony do generacji dźwięku, to rzadko pozostaje wystarczająca ilość czasu procesora do wykonywania czegoś więcej od tworzenia statycznego dźwięku lub niezgrabnego dźwięku dynamicznego. Jedyną alternatywą pozostaje chwilowe wstrzymanie innych zadań podczas generowania dźwięku.

Inny problem może się pojawić przy używaniu tego komputera do odtwarzania muzyki w kilku kanałach. Jeśli użyte zostaną wszystkie cztery kanały, to odstęp czasu pomiędzy pierwszym rozkazem a czwartym może być wystarczający, aby dało się wyczuć opóźnienie pomiędzy różnymi kanałami.

Poniższy program przedstawia rozwiązanie tego problemu:

10 SOUND 0,0,0,0:DIM SIMUL$(16)
20 RESTORE 9999:X=1
25 READ Q:IF Q<>-1 THEN SIMUL$(X)=CHR$(Q):X=X+1:GOTO 25
27 RESTORE 100
30 READ F1,C1,F2,C2,F3,C3,F4,C4
40 IF F1=-1 THEN END
50 X=USR(ADR(SIMUL$),F1,C1,F2,C2,F3,C3,F4,C4)
55 FOR X=0 TO 150:NEXT X
60 GOTO 30
100 DATA 182,168,0,0,0,0,0,0
110 DATA 162,168,182,166,0,0,0,0
120 DATA 144,168,162,166,35,166,0,0
130 DATA 128,168,144,166,40,166,35,166
140 DATA 121,168,128,166,45,166,40,166
150 DATA 108,168,121,166,47,166,45,166
160 DATA 96,168,108,166,53,166,47,166
170 DATA 91,168,96,166,60,166,53,166
999 DATA -1,0,0,0,0,0,0,0
9000 REM
9010 REM
9020 REM te dane zawierają program w języku maszynowym,
9030 REM który zostanie wczytany do SIMUL$
9999 DATA 104,133,203,162,0,104,104,157,0,210,232,228,203,208,246,96,-1

W powyższym programie zmienna SIMUL$ przechowuje mały program w języku maszynowym, który bardzo szybko zapisuje danymi wszystkie cztery kanały dźwiękowe. Program w języku BASIC używający SIMUL$ może szybko manipulować wszystkimi czterema kanałami. Każdy program może wywołać procedurę w SIMUL$ przez wstawienie wartości dla rejestrów dźwiękowych do wnętrza wywołania funkcji USR w wierszu 50 programu demonstracyjnego. Parametry powinny występować w pokazanej tutaj kolejności: po wartości dla rejestru częstotliwościowego następuje wartość dla rejestru sterującego z powtórzeniem tej kolejności od jednego do czterech razy dla czterech kanałów dźwiękowych, które mają zostać ustawione.

Dla szybkości i wygody procedura maszynowa w SIMUL$ pozwala ci określić dźwięk dla mniej niż czterech kanałów, tj. 1, 2 i 3 lub 1 i 2 lub tylko dla kanału 1. Po prostu nie umieszczaj nieużywanych parametrów wewnątrz funkcji USR.

SIMUL$ oferuje jeszcze jedną bezpośrednią korzyść dla programisty w języku BASIC. Jak wspomniano wcześniej, rejestr AUDCTL jest resetowany przy wykonywaniu jakiegokolwiek rozkazu SOUND w języku BASIC. Jednakże w trakcie używania SIMUL$ nie są wykonywane żadne rozkazy SOUND, a zatem ustawienia AUDCTL są zachowywane.

Istnieje inna, lecz niepraktyczna metoda generacji dźwięku w języku BASIC. Wykorzystuje ona bit głośności dowolnego z czterech rejestrów sterujących audio. Wpisz i uruchom poniższy program:

SOUND 0,0,0,0
10 POKE 53761,16:POKE 53761,31:GOTO 10

Program ustawia bit samodzielnej głośności w kanale 1 i moduluje tę głośność wartościami 0 i 15 tak szybko, jak potrafi to wykonać język BASIC. Program zużywa cały czas procesora dostępny w języku BASIC, a mimo to tworzy jedynie niski ton.

Przerwanie 60 Hz.

Ta technika jest prawdopodobnie najbardziej wszechstronna i praktyczna ze wszystkich metod dostępnych dla programisty komputera ATARI.

Dokładnie co 1/60 sekundy (w naszym systemie jest to 1/50 sekundy) układy komputera automatycznie generują przerwanie. Gdy tak się stanie, komputer chwilowo opuszcza program główny (program działający w danej chwili w systemie, np. BASIC, STAR RAIDERS™). Wykonuje on następnie procedurę obsługi przerwania, która jest małą procedurą zaprojektowaną specjalnie do obsługiwania tych przerwań. Gdy ta procedura obsługi przerwania kończy swoje działanie, wykonuje specjalną instrukcję w języku maszynowym, która przywraca wykonywanie programu głównego. Odbywa się to w taki sposób (jeśli zostanie wykonane poprawnie), iż wykonujący się w danym momencie program nie zostaje zakłócony, a nawet nie ma pojęcia, że w ogóle został przerwany!

Wbudowana w komputer ATARI 400/800 procedura przerwania po prostu uaktualnia liczniki, odczytuje i przetwarza stan kontrolerów gier i wykonuje różne inne zadania, które wymagają ciągłej uwagi.

Zanim ta procedura obsługi przerwania powróci do głównego programu, można ją zmusić do wykonania dowolnej procedury użytkownika, tj. twojej procedury generacji dźwięku. Jest to idealne dla generacji dźwięku, ponieważ czas jest precyzyjnie kontrolowany oraz głównie dlatego, iż inny program może się wykonywać bez troszczenia się o generator dźwięku.

Jeszcze bardziej imponująca jest jej wszechstronność. Ponieważ jest to program w języku maszynowym, to będzie on współpracował równie dobrze z programem głównym napisanym w dowolnym języku – w BASIC'u, asemblerze, FORTH, PASCAL'u. W rzeczywistości generator dźwięku będzie wymagał niewielu modyfikacji (lub żadnych), aby współpracować z innym programem lub nawet z innym językiem.

Procedura sterowana tablicą oferuje maksimum wszechstronności i prostoty przy takich zastosowaniach. "Sterowanie tablicą: odnosi się do pewnego typu programu, który uzyskuje dostęp do tablic danych w pamięci w celu pobrania swojej informacji. W przypadku generatora dźwięku, tablice danych zawierają wartości częstotliwości oraz być może wartości dla rejestrów sterujących dźwiękiem. Procedura po prostu odczytuje następne dane w takiej tabeli i wstawia je do odpowiednich rejestrów dźwiękowych. Przy zastosowaniu tej metody nuty mogą się zmieniać 60 razy (50 razy) na sekundę, co jest wystarczająco szybkie dla większości aplikacji.

Gdy taki program został napisany i umieszczony w pamięci (powiedzmy pod adresem $600), należy go zainstalować jako część procedury obsługi przerwania 60-Hz. Wykonuje się to za pomocą metody zwanej wykradaniem wektorów.

Komórki pamięci o adresach $224,$225 zawierają adres małej procedury zwanej XITVBL (eXIT Vertical BLank Interrupt service routine – wyjście z procedury obsługi przerwania przy wygaszaniu pionowym). XITVBL została zaprojektowania do uruchomienia po zakończeniu całości przetwarzania przerwania 60-Hz, przywracając komputer do wykonywania programu głównego, o którym dyskutowaliśmy wcześniej.

Procedura instalacji twojej procedury dźwiękowej jest następująca:

  1. Umieść swój program w pamięci.
  2. Upewnij się, że ostatnią jego instrukcją jest skok JMP $E462 ($E462 jest adresem XITVBL, zatem spowoduje to kontynuację programu głównego).
  3. Załaduj rejestr X górnym bajtem adresu swojej procedury (w tym przypadku jest to 6).
  4. Załaduj rejestr Y dolnym bajtem adresu swojej procedury (w tym przypadku jest to 0).
  5. Załaduj akumulator A liczbą 7.
  6. Wykonaj JSR $E45C (aby ustawić komórki pod adresem $224,$225).

Kroki 3...6 są niezbędne do bezbłędnej zmiany zawartości komórek $224,$225. Wywoływaną procedurą jest SETVBV (SET Vertical Blank Vectors – ustawianie wektorów wygaszania pionowego), która po prostu wstawi adres twojej procedury do komórek $224,$225. Po instalacji, system będzie działał następująco, gdy wystąpi przerwanie:

  1. Wykonywana jest procedura przerwania komputera.
  2. Wykonuje ona skok do programu, którego adres znajduje się w komórkach $224,$225, który teraz jest twoją procedurą.
  3. Twoja procedura wykonuje się.
  4. Twoja procedura skacze następnie do XITVBL.
  5. XITVBL przywraca stan komputera i każe mu kontynuować normalną pracę.

Jeśli nie chcesz implementować takiego programu samodzielnie, możesz uzyskać gotowy program z Atari Program Exchange. Pakiet nosi nazwę INSOMNIA (Interrupt Sound Initializer/Alterer). Pozwala on na tworzenie i modyfikowanie danych dźwiękowych w locie, gdy ich słuchasz. Program posiada generator dźwiękowy na przerwaniach, który jest sterowany tablicą i jest kompatybilny z dowolnym językiem programowania.

Generacja dźwięku w kodzie maszynowym

Bezpośrednie sterowanie rejestrami dźwiękowymi za pomocą języka maszynowego otwiera nowe drzwi do generacji dźwięków. Metoda jest następująca: napisz program podobny do procedury przerwania 60Hz w tym, iż również będzie sterowany za pomocą tablicy, lecz teraz jego główna procedura ma być poświęcona generacji dźwięku. Przeznaczająca dużo więcej czasu procesora na generację dźwięków, możesz wytworzyć dźwięki o wyższej jakości. Rozważmy przykładowo efekt pracy typowej procedury muzycznej 60Hz (50Hz):

Rys.7-12. Przykład 3 nut muzycznych odgrywanych przez procedurę sterowaną przerwaniami 60 Hz

Ponieważ w programie asemblerowym mamy do dyspozycji o wiele więcej czasu przetwarzania, możemy zmieniać częstotliwość z dużą prędkością podczas czasu odgrywania nuty, aby symulować jakiś instrument. Na przykład, załóżmy, że odkryliśmy, iż przy każdym naciśnięciu klawisza pianina wytwarzany jest charakterystyczny ciąg częstotliwości, jak na obrazku poniżej:

Rys.7-13. Wykres przebiegu częstotliwości dla dźwięku pianina

Nazwijmy powyższy wykres "obwiednią pianina". Aby symulować pianino, należałoby bardzo szybko zastosować obwiednię pianina na czysty dźwięk z generatora. W ten sposób odtwarzana nuta zostanie nieco zmieniona podczas jej odgrywania. Na przykład, symulacja pianina dla tych 3 nut wyglądałaby następująco:

Rys.7-14. Przykład 3 nut z rys.7-10 odgrywanych z obwiednią pianina

W zasadzie mamy ten sam dźwięk, który produkuje standardowa procedura muzyczna na rys.7-12, jedynie nuty posiadają teraz brzmienie pianina, a dźwięk jest znacznie ładniejszy od czystych pisków. Niestety musieliśmy poświęcić całe przetwarzanie, aby uzyskać brzmienie pianina. Kanał dźwiękowy nie jest już uaktualniany tylko raz dla każdej nuty, lecz być może 100 razy podczas jej trwania.

Dźwięk tworzony samą głośnością

Wcześniej eksperymentowaliśmy z bitami głośności rejestrów AUDC1-4. lecz okazało się że są one mało użyteczne w języku BASIC. Spowodowane jest to całkowicie faktem, iż BASIC jest zbyt wolny, aby używać ich efektywnie. Lecz z kodem maszynowym tak nie jest.

Jak wspomniano wcześniej, bity te oferują niesamowicie wiele przy dokładnym odtwarzaniu dźwięku. Tworzenie prawdziwych przebiegów (przy uwzględnieniu limitów czasowych i rozdzielczości oferowanych przez komputer) staje się możliwe za pomocą tego bitu. Zamiast jedynie naśladować dźwięk pianina w muzyce, można teraz odtworzyć dźwięk bliski brzmieniu prawdziwego pianina. Niestety, nigdy nie można precyzyjnie powielić dźwięku instrumentu. Rozdzielczość 4-bitowa (16 wartości) głośności nie wystarcza do uzyskania dźwięków o prawdziwie wysokiej jakości. Niemniej jednak technika ta produkuje dźwięki zadziwiająco dobre. Poniższy program demonstruje użycie tylko jednego bitu głośności. Jeśli masz asembler, wpisz ten program i wypróbuj go:

0100 ;
0110 ; VONLY      Bob Fraser 7-23-81
0120 ;
0130 ;
0140 ;Procedura testowa bitu głośności
0150 ;
0160 ;
0170 ;
0180 ;
0190 AUDCTL =    $D208
0200 AUDF1  =    $D200
0210 AUDC1  =    $D201
0220 SKCTL  =    $D20F
0230 ;
0240 ;
0250     *=  $B0
0260 TEMPO  .BYTE 1
0270 MSC    .BYTE 0
0280 ;
0290 ;
0300 ;
0310     *=   $4000
0320     LDA  #0
0330     STA  AUDCTL
0340     LDA  #3
0350     STA  SKCTL
0360     LDX  #0
0370 ;
0380     LDA  #0
0390     STA  $D40E  ; wyłącz VBI
0400     STA  $D20E  ; wyłącz IRQ
0410     STA  $D400  ; wyłącz DMA
0420 ;
0430 ;
0440 ;
0450 L00 LDA  DTAB,X
0460     STA  MSC
0470 ;
0480     LDA  VTAB,X
0490 L0  LDY  TEMPO
0500     STA  AUDC1
0510 L1  DEY
0520     BNE  L1
0530 ;
0540 ; Zmniejsz licznik najbardziej znaczący
0550     DEC  MSC
0560     BNE  L0
0570 ;
0580 ;
0590 ; Nowa nuta
0600 ;
0610    INX
0620    CPX  NC
0630    BNE  L00
0640 ;
0650 ; Przewiń wskaźnik nut
0660    LDX #0
0670    BEQ L00
0680 ;
0690 ;
0700 NC   .BYTE 28;  Licznik nut
0710 ;
0720 ; Tablica kolejnych głośności do odtworzenia
0730 VTAB
0740     .BYTE 24,25,26,27,28,29,30,31
0750     .BYTE 30,29,28,27,26,25,24
0760     .BYTE 23,22,21,20,19,18,17
0770     .BYTE 18,19,20,21,22,23
0780 ;
0790 ; Ta tablica zawiera czas trwania każdej z powyższych głośności
0800 DTAB
0810     .BYTE 1,1,1,2,2,2,3,6
0820     .BYTE 3,2,2,2,1,1,1
0830     .BYTE 1,1,2,2,2,3,6
0840     .BYTE 3,2,2,2,1,1

Co zadziwiające, szybkość nie jest tutaj żadnym problemem. Przebieg posiada prawie 60 kroków, a program może odtwarzać go wciąż z częstotliwością do 10 kHz.

Usuń wiersze 390-400 i wypróbuj program ponownie. Dźwięk będzie zaburzony. Powodem jest przerwanie 60 Hz omówione w poprzednim rozdziale. Możesz usłyszeć przerwania, ponieważ w trakcie ich wystąpienia wszystkie dźwięki są zatrzymywane.

Wiersz 410 wyłącza DMA (ang. Direct Memory Access – bezpośredni dostęp do pamięci) ekranu. To dlatego ekran przyjmuje kolor tła po uruchomieniu programu. Spełnia to dwa zadania: przyspiesza procesor oraz umożliwia utrzymanie stałego odmierzania czasów, ponieważ DMA podkrada procesorowi cykle zegarowe w nierównych odstępach.

W tym programie demonstracyjnym tworzona jest fala sinusoidalna. Fala jest zadziwiająco czysta i rzeczywiście brzmi jak fala sinusoidalna. Na wykresie wygląda tak:

15-I            ------
14-I         ---      ---
13-I       --            --
12-I     --                --
11-I   --                    --
10-I  -                        -
 9-I -                          -
 8=I-----------------------------------------------------------
 7-I                              -                          -
 6-I                               -                        -
 5-I                                --                    --
 4-I                                  --                --
 3-I                                    --            --
 2-I                                      ---      ---
 1-I                                         ------

Rys.7-15. Wykres danych z wartościami głośności dla powyższego programu

W tym rozdziale przedyskutowane zostały zagadnienia techniczne generacji dźwięku przy pomocy komputera ATARI. Programista musi również rozumieć szerszą rolę dźwięku w kompletnych pakietach programów.

Twórcy filmowi już dawno zrozumieli wagę muzyki w tle, która nadaje widowni odpowiedni nastrój. Wspaniałymi przykładami są filmy przygód w kosmosie nakręcone przez George'a Lucasa.

Gdy zły charakter wchodzi do pomieszczenia, wiadomo od razu, że należy się go bać i nienawidzić z rytmów w tle, które towarzyszą jego wejściu. A radośnie klaszczesz w dłonie, gdy bohater ratuje księżniczkę przy dźwiękach heroicznej muzyki odgrywanej w tle. Podobnie filmy grozy mogą przerazić cię samą muzyką, chociaż odgrywane sceny są zupełnie normalne.

W grze SPACE INVADERS zagrożenie tworzone są przez echo kroków. Gdy tempo wzrasta, kostki u dłoni ściśniętych na dżojstiku bieleją a zęby zaczynają zgrzytać. Gdy Zylon ze STAR RAIDERS™ wystrzeliwuje torpedę fotonową, w panice naciskasz kontrolki gry, aby uniknąć uderzenia. Gdy przebija się ona prosto w ciebie, czas zwalnia i słyszysz coraz głośniejsze syczenie w miarę zbliżania się pocisku. Tuż przed uderzeniem robisz unik i usuwasz się z fotela.

Impresjonistyczne dźwięki wpływają na naszą podświadomość i stan umysłu. Dzieje się tak prawdopodobnie dlatego, iż dźwięk, jeśli jest obecny, ciągle wywiera działanie na nasz umysł bez względu na to, czy aktywnie go słuchamy, czy też nie. Z drugiej strony bodźce wzrokowe wymagają uwagi użytkownika. Jeśli nasza uwaga zostanie odwrócona od odbiornika telewizyjnego, to przestajemy się koncentrować na obrazie i opuszcza on nasz umysł. Dlatego dźwięk oferuje programiście bezpośrednią drogę do umysłu użytkownika – omijając jego procesy myślowe i koncentrując się na jego uczuciach.


Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2024 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.