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.
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.
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.
Funktion sam_load (Dateiname): Ergebnis.
Parameter:
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:
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:
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.
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) |