← ST-Computer 03 / 1993

Soundsamples fĂŒr ATARI STE- und TT-Computer - Einladen & Abspielen

Programmierpraxis

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)
Marco Feikert