Die Rechner der ATARI-STE- und TT-Reihe bieten die einfache Möglichkeit, digitalisierte GerĂ€usche oder Musik in verschiedenen (aber festen) Frequenzlagen abzuspielen. Leider bietet das Betriebssystem keine entsprechende Funktion, welche diese Arbeit erledigt, so daĂ man darauf angewiesen ist, selber dafĂŒr zu sorgen.
Dies erledigt die Unit in Listing 1. Sie stellt einfach zu benutzende Funktionen zum Laden und Abspielen von Soundsamples bereit. In Listing 2 findet sich ein kurzes Programm zum Testen der obigen Unit.
Ăber Samples ...
Als Samples bezeichnet man im allgemeinen digitalisierte GerĂ€usche oder Musik. Analoge Signale werden ĂŒber einen A/D-(Analog-Digital)-Konverter in digitale Signale umgewandelt. Da analoge Signale beliebige Amplitudenwerte (Pegel) besitzen können, mĂŒssen sie auf ein festes digitales Raster abgebildet werden.
... und ihre Darstellung im ATARI
Im Falle der DMA im ATARI, welche fĂŒr die Verarbeitung der Samples zustĂ€ndig ist, wird jedem Analog- ein Digitalwert zwischen -128 (negative Amplitude) und +127 (positive Amplitude) zugeordnet. Allerdings gibt es auch Samples, deren binĂ€re Darstellung derart ist, daĂ das (negative) Minimum bei 0 und das (positive) Maximum bei 255 liegt. Solche Samples klingen natĂŒrlich auf einem ATARI nicht korrekt, können aber durch ein einfaches Programm umgewandelt werden. Es wird also, in beiden FĂ€llen, jeweils ein Byte (8 Bit) zur Kodierung verwendet. (Im Gegensatz z.B. zu CD-Playern. Diese verwenden i.a. 16 Bit.)
Nun mĂŒssen diese Daten auch wiedergegeben werden können. Hierzu findet ein zu oben umgekehrter Vorgang Verwendung. Ein Digital-Analog-Konverter berechnet aus den (recht wenigen) 255 möglichen Pegelwerten wieder das analoge Signal. Man erhĂ€lt also in keinem Fall wieder den Verlauf des originalen analogen Pegels, sondern nur eine (mehr oder weniger) gute AnnĂ€herung.
Die Geschwindigkeit, mit welcher diese Umrechnung erfolgt bzw. mit welcher das Sample wiedergegeben wird, lĂ€Ăt sich einstellen. Sie wird als Sample-Rate bezeichnet. In der Analog-Digital-Konvertierung gilt folgendes: Je höher die Sample-Rate ist, desto genauer, d.h. in kleineren, engeren, Zeitintervallen kann ein analoges Signal abgetastet werden, um so eine bessere Kopie des originalen Signals zu erhalten. Bei der Digital-Analog-Konvertierung gibt die Sample-Rate primĂ€r die Abspielgeschwindigkeit des Samples an, wobei sie natĂŒrlich mit der Sample-Rate der A/D-Konvertierung identisch sein sollte, um eine originalgetreue Wiedergabe zu ermöglichen. Beim ATARI sind dies 6.25,12,25 oder 50 kHz. Zum Beispiel werden bei 12 kHz 12000 digitale Daten (also etwa 12 KBytes) in der Sekunde in ein analoges Signal umgerechnet und wiedergegeben. Dies ist recht viel und erklĂ€rt auch, wieso Samples i.a. soviel Speicherplatz benötigen.
Des weiteren gibt es noch die Möglichkeit, die Daten in Stereo wiederzugeben. In diesem Fall wird jeweils immer abwechselnd ein Byte auf dem rechten und dem linken Kanal ausgegeben. Damit verdoppelt sich natĂŒrlich der Speicherbedarf fĂŒr ein einziges Sample.
Als letztes gibt es noch die Möglichkeit ein einzelnes Sample nur ein einziges Mal abzuspielen oder beliebig oft zu wiederholen.
Implementierung
Funktion sam_load (Dateiname): Ergebnis.
Parameter:
- Dateiname: Pfadname des zu ladenden Samples
- Ergebnis: negativer Wert: Fehler, positiver Wert: Referenznummer des geladenen Samples
Ein Sample wird durch obige Funktion geladen und in einem Speicherbereich, der angefordert wird, abgelegt. Zur Verwaltung aller eingeladenen Samples wird ein Feld-Record benutzt, der jeweils die LĂ€nge des Samples und die Anfangsadresse des reservierten Speicherbereiches enthĂ€lt. Bei erfolgreichem Laden erhĂ€lt man ein Handle, mit dem das Sample referenziert wird. Sollte nicht genĂŒgend Platz fĂŒr alle zu ladenden Samples in dem Record sein, kann die Konstante max_samples, zu finden im Kopf der Unit, entsprechend hochgesetzt werden.
Prozedur sam_play(ld,Wie_oft, Frequenz, Mono_Stereo);
Parameter:
- Id: Referenznummer des abzuspielenden Samples
- Wie-oft: Sample stoppen (0), Sample einmal abspielen (1), Sample dauernd abspielen (3)
- Frequenz: 6.25 kHz(O), 12.5 kHz(1), 25 kHz(2), 50 kHz(3)
- Mono_Stereo: Mono (1), Stereo (0)
Die Prozedur ĂŒbernimmt das Abspielen der Samples. Ihr werden auĂer der Referenznummer eines Samples die oben erwĂ€hnten Charakteristika ĂŒbergeben (Es empfiehlt sich, die Konstantenvereinbarungen aus dem Unit-Kopf zu verwenden.) Diese Daten werden nun in die entsprechenden DMA-Register des ATARI ĂŒbertragen. Da die Register in einem geschĂŒtzten Speicherbereich liegen, muĂ der Zugriff im Supervisormodus erfolgen. Dies erledigt die erste Anweisung in der Prozedur. Sobald alle Daten in die Register geschrieben wurden, beginnt die DMA mit dem Abspielen des Samples. Dies geschieht im Hintergrund und allein durch die DMA, d.h. der Hauptprozessor wird durch den Vorgang nicht beeintrĂ€chtigt.
Prozedur sam_free(Id);
Parameter:
- Id: Referenznummer des zu entfernenden Samples aus dem Speicher, bzw. -1: alle Samples werden aus dem Speicher entfernt.
Vor Verlassen eines Programmes, in dem die Unit Verwendung findet, sollten die Speicherbereiche, in denen die Samples abgelegt sind, durch obige Prozedurfreigegeben werden. Andernfalls könnte man plötzlich einige hundert Kilobyte RAM-Speicher vermissen.
Listing 2 stellt eine kleine Testumgebung fĂŒr die Unit dar. Ein Sample kann ausgewĂ€hlt werden, und die Abspiel-Charakteristika lassen sich einstellen. Klicken auf Abbruch in der Fileselectorbox fĂŒhrt zum Verlassen des Programmes.
Die Unit ist recht einfach und modular aufgebaut, und es sollte keine zu groĂen Probleme bereiten, sie fĂŒr andere Pascal-Versionen oder auf andere Sprachen anzupassen.
DMA-Hardware-Register
Alle in Bild 1 aufgefĂŒhrten Register sind Wortregister, sie haben also eine LĂ€nge von 2 Byte, von denen allerdings immer nur das niederwertige Byte (Registeradresse+1) benutzt wird.
(* Laden und Abspielen von Soundsamples auf *)
(* Atari STE und TT Computer *)
(* (w) Marco Feikert (10'92) & (1'93) *)
(* (c) MAXON Computer 1993 *)
{$X+}
UNIT sample;
INTERFACE
USES tos;
CONST
max_samples = 150;
stop = 0;
einmal = 1;
dauernd = 3;
mono = 1;
stereo= 0;
fr_6 = 0;
fr_12 = 1;
fr_25 = 2;
fr_50 = 3;
VAR
i: INTEGER;
FUNCTION sam_load(name: STRING): INTEGER;
PROCEDURE sam_free(was: INTEGER);
PROCEDURE sam_play(id, wie_oft, frequenz, mono_stereo: INTEGER);
(* -------------------------------------------*)
IMPLEMENTATION
TYPE
sam_arr = RECORD
start: POINTER; (* Startadresse *)
len: LONGINT; (* LĂ€nge *)
END;
VAR
sam: ARRAY[0 .. max_samples] OF sam_arr;
(* LĂ€dt Sample name, return: Id-Nummer bzw. *)
(* -1: Datei nicht gefunden, *)
(* -2: Speicherreservierungsfehler *)
(* -3: Einladefehler *)
(* -4: Recordarray vollstÀndig belegt *)
FUNCTION sam_load(name: STRING): INTEGER;
VAR
frei, i, fh, return: INTEGER;
amount, len: LONGINT;
p: POINTER;
dat: DTAPtr;
BEGIN
P:=NIL;
len:=0;
return:=-4;
i: =0;
(* Freien Platz suchen *)
REPEAT
IF sam[i].start=NIL THEN
return:=i
ELSE
INC(i);
UNTIL (i>max_samples) OR (return<>-4);
IF return<>-4 THEN BEGIN (* DateilÀnge bestimmen *)
i:=FSFIRST(name,0);
IF i=0 THEN BEGIN
dat:=FGETDTA;
len:=dat^.d_length;
END;
(* DateilÀnge=0 => nicht gefunden *)
IF len=0 THEN
return:=-1
ELSE BEGIN
frei:=return;
(* Speicherplatz reservieren *)
p:=Malloc(len);
IF p=NIL THEN
return:=-2
ELSE BEGIN
return:=-3;
fh: =Fopen(name,FO_READ);
IF fh>0 THEN BEGIN
(* Sample einiesen *)
amount:=Fread(fh,len.p);
IF amount=len THEN BEGIN
sam[frei].start:=p;
sam[frei].len:=len;
return:=frei;
END;
Fclose(fh);
END;
END;
END;
END;
(* Einladefehler, Speicher freigeben *)
IF return=-3 THEN
Mfree(p);
sam_load:=return;
END;
(* Speicherplatz von Samples freigeben *)
(* was: >=0: FĂŒr ein Sample freigeben *)
(* -1: FĂŒr alle Samples freigeben *)
PROCEDURE sam_free(was: INTEGER);
VAR
i: INTEGER;
BEGIN
IF was>=0 THEN BEGIN
IF sam[was].start<>NIL THEN BEGIN
Mfree(sam[was].start);
sam[was].start:=NIL;
sam[was].len:=0;
END;
END
ELSE BEGIN
i:=0;
REPEAT
IF sam[i].start<>NIL THEN BEGIN
Mfree(sam[i].start);
sam[i].start:=NIL;
sam[i].len:=0;
END;
INC(i);
UNTIL i>max_samp1es;
END;
END;
(* 1 Longint in 4 Bytes aufteilen *)
(* b1: Highbyte ... b4: Lowbyte *)
PROCEDURE split_var(v: LONGINT; VAR b1, b2, b3, b4: BYTE);
VAR
p: POINTER;
BEGIN
p:=ADDR(v);
b1:=BYTE(p^);
b2:=BYTE(POINTER(LONGINT(p)+1)^);
b3:=BYTE(POINTER(LONGINT(p)+2)^);
b4:=BYTE(POINTER(LONGINT(p)+3)^);
END;
(* Sample abspielen *)
(* id: Samplekennung aus samâload() *)
(* wie_oft: 0: Stop, 1: Einmal, 3: Endlos *)
(* frequenz: 0: 6.25 Khz, 1: 12.5 Khz, *)
(* 2: 25 Khz, 3: 50 Khz *)
(* mono_stereo: 0: Stereo, 1: Mono *)
(* bzw. Konstantenvereinbarungen aus *)
(* Programmkopf benutzen *)
PROCEDURE sam_play(id, wie_oft, frequenz, mono_stereo: INTEGER);
VAR
l, Start, ende, os: LONGINT;
b1, b2, b3, b4: BYTE;
BEGIN
IF sam[id].len>0 THEN BEGIN (* GĂŒltige Id ? *)
(* Supervisormodus an *)
os:=Super(NIL);
(* Startadresse eintragen *)
l:=$ff8902;
start:=LONGINT(sam[id].start);
split_var(start,b1,b2,b3,b4);
BYTE(POINTER(l+1)^):=b2;
BYTE(POINTER(l+3)^):=b3;
BYTE(POINTER(l+5)^):=b4;
(* Endadresse eintragen *)
l:=$ff890e;
ende:=LONGINT(sam[id].start)+sam[id].len;
split_var(ende,b1,b2,b3,b4);
BYTE(POINTER(l+1)^):=b2;
BYTE(POINTER(l+3)^):=b3;
BYTE(POINTER(l+5)^):=b4;
(* Frequenz eintragen *)
1:=$ff8920;
IF mono_stereo=1 THEN
INC(frequenz, 128);
WORD(POINTER(l)^):=frequenz;
(* Abspielen starten/stoppen *)
l:=$ff8900;
WORD(POINTER(1)^):=wie_oft;
(* Supervisormodus aus *)
Super(POINTER(os));
END;
END;
BEGIN
(* Record initialisieen *)
i:=0;
REPEAT
sam[i].start:=NIL;
sam[i].len:=0;
INC(i);
UNTIL i>maxsamples;
END.
(* Kurzes Beispielprogram zum Testen *)
(* der Sample-Unit *)
{$X+}
PROGRAM test_sample;
USES gem,sample;
VAR
s1, s2, s3: STRING;
len, i, x: INTEGER;
freq, m_s: INTEGER;
BEGIN
REPEAT
s1:='';
s2:='';
(* Sample auswÀhlen *)
fsel_input(s1,s2,i);
IF (i<>0) AND (s2<>'') THEN BEGIN
len:=LENGTH(s1);
WHILE COPY(s1,len,1)<>'\' DO
DEC(len);
s2:=COPY(s1,1,len)+s2;
(* Laden *)
x:=sam_load(s2);
IF x>=0 THEN BEGIN (* Laden erfolgreich *)
(* ACHTUNG: Bei Verwendung von Write's in der *)
(* niedrigen oder mittleren Auflösung gibt es *)
(* einen Runtimeerror in Pure Pascal (Version *)
(* 13.Aug) in Verbindung mit NVDI 2.1 ! *)
write('Frequenz: (0-3):');
readln(freq);
write('Stereo/Mono (0-1):');
readln(m_s);
(* und abspielen *)
sam_play(x,einmal,freq,m_s);
sam_free(x);
END;
END;
UNTIL (i=0) OR (s2='');
END.
Registeradresse |
Bit-Belegung |
ErklÀrung |
$ff8900 |
**** **** **** **BA A=0, B=0: DMA-Sound ausschalten A=0, B=1: DMA-Sound einschalten A=1, B=1: DMA-Sound einschalten, Sample endlos wiederholen |
|
$ff8920 |
**** **** A*** **CD |
C=0, D=0: Sample-Rate: 6,25 kHz C=0, D=1: Sample-Rate: 12,50 kHz C=1, D=0: Sample-Rate: 25,00 kHz C=1, D=1: Sample-Rate: 50,00 kHz A=0: Stereo A=1: mono |
$ff8902 |
**** **** 00xx xxxx |
Sample-Startadresse, Highbyte |
$ff8904 |
**** **** xxxx xxxx |
Sample-Startadresse, Middlebyte |
$ff8906 |
**** **** xxxx xxxx |
Sample-Startadresse, Lowbyte |
$ff890E |
**** **** 00xx xxxx |
Sample-Endadresse, Highbyte |
$ff8910 |
**** **** xxxx xxxx |
Sample-Endadresse, Middlebyte |
$ff8912 |
**** **** xxxx xxxx |
Sample-Endadresse, Lowbyte |
$ff8908 |
**** **** 00xx xxxx |
Hier findet sich die aktuelle Position |
$ff890A |
**** **** xxxx xxxx |
im Sample, an welcher sich die DMA |
$ff890C |
**** **** xxxx xxxx |
gerade wÀhrend des Abspielens befindet. (High-, Middle- und Lowbyte) |