Eine der interessantesten und vielseitigsten Einsatzmöglichkeiten von Computersystemen ist die Erfassung und Verarbeitung von analogen Meßdaten. Zu diesen zählt man digitale Filter, Speicheroszilloskope sowie die Erfassung von Tönen aller Art (Sound-Sampling genannt). Die gestiegenen Anforderungen bei der Meßgenauigkeit vieler physikalischer Vorgänge sind ohne die Meßerfassung durch einen Rechner nicht zu befriedigen. Aber auch die moderne Unterhaltungselektronik profitiert von solcher Technik. Die schon sehr verbreiteten CD-Player verdanken ihre hohe Qualitätsstufe der Digitaltechnik.
Allen diesen Anwendungsmöglichkeiten liegt das gleiche Prinzip zugrunde: die Umsetzung von elektrischen Spannungen als “Rohmaterial” in digitale Daten mittels mathematischer Berechnungen. Aber kein Rechner kann Analogsignale verarbeiten. Aus diesem Grund wird ein analoges Signal (stetiger Spannungsverlauf) an den Eingang eines Umsetzers, des sogenannten A/D-Wandlers geführt, der dann an seinem Ausgang dem Rechner ein digitales Signal in Form einer Zahlenkombination zur Verfügung stellt.
Das hier beschriebene “Sound Sampling” ermöglicht es, Sprache oder sogar ganze Musikstücke mit einem ATARI-ST zu digitalisieren, zu bearbeiten und über den Monitorlautsprecher wieder auszugeben. Selbstverständlich darf man von diesem Sound Sampler nicht HiFi-Qualität erwarten, aber um z.B. einen Programm-Vorspann noch mit Musik zu versehen, reicht sie aus.
Für den interessierten Anfänger, der noch nicht weiß, was ein Sound Sampler ist, oder was der Begriff ‘digitalisieren’ bedeutet, sollen diese jetzt erklärt werden. Was ein Sound-Sampler tut, ist schnell gesagt. Er wandelt die Signale, die ein handelsüblicher Kassettenrekorder, ein Radio oder ein ähnliches Gerät über den Kopfhörerausgang, den Lautsprecheranschluß oderein Mikrofon ausgeben (siehe Bild 1), in für den Computer verständliche Signale um. Da aber ein Computer nichts anderes versteht als Zahlen, muß ein solches Signal in Zahlen umgewandelt werden. Diese Aufgabe übernimmt ein sogenannter Analog-Digital-Wandler. Im Prinzip besteht ein Sound-Sampler aus nichts anderem als einem A/D-Um-setzer. Analog nennt man das Signal, das z.B. ein Kassettenrekorder ausgibt; digital das Signal, das vom Computer verstanden werden kann - Zahlen. Digitalisiert man also etwas, so wird ein Signal in ein für den Computer verständliches verwandelt.
Um ein Analogsignal (z. B. einen stetigen Spannungsverlauf) in ein digitales umzuwandeln, muß es in bestimmten Abständen abgetastet und dem Spannungswert zur Zeit der Abtastung ein Zahlenwert zugeordnet werden. Diesen Vorgang nennt man Quantisierung. Der zeitliche Abstand zwischen zwei Meßpunkten ( Umwandlungen) wird Abstastfrequenz genannt. Je höher sie ist, umso kürzer sind die immer gleichen Abstände, und desto besser ist die Wiedergabequalität vom Computer. Bild 2 soll dies verdeutlichen. Der schraffierte Teil zeigt die Ungenauigkeit, die bei höherer Abtastfrequenz immer kleiner wird. Die Erfassung eines Analogsignales bzw. seine Verarbeitung in einen digitalen Wert nimmt eine gewisse Zeit in Anspruch. Ändert sich während dieser Zeit das analoge Eingangssignal, so ist mit einem Fehler bei der Umsetzung zu rechnen. Abhilfe würde ein sogenannter “Sample and Hold” am Eingang des A/ D-Wandlers schaffen. Durch diesen Sample and Hold” wird die Eingangsspannung (Probe) für die Dauer der Umwandlung gespeichert, so daß sich das Eingangssignal ändern kann, ohne daß dies einen Einfluß auf die Analog/Digital-Wandlung hat. Ein solcher “Sample and Hold” ist bei dem hier vorgestellten Projekt nicht vorgesehen, da einerseits ein solcher Wandler sehr teuer ist, anderseits für unsere Anwendung ein “normaler” 8-Bit-Wandler vollkommen ausreicht. Ein weiterer wichtiger Faktor für die Qualität einer Digitalisierung ist die Auflösung, mit der das analoge Signal abgetastet (zerlegt) werden kann. Bei unserem 8-Bit-Wandler kann das Eingangssignal in 28 = 256 Schritte (Auflösungsstufen) unterteilt werden.
Beispiel: Ein Signal mit einer Amplitude von 1 Voltss kann mit einem 8-Bit-Wandler in 256 Spannungsstufen unterteilt werden, d.h. man hat eine Auflösung von 1/256 gleich 3,91 mV (Millivolt).
Da nun die meisten Computer von der Hardware her nicht in der Lage sind den Digitalisiervorgang durchzuführen, genügt es nicht, einfach die entsprechende Software einzuladen, sondern man muß sich auch einer entsprechenden Schnittstelle zur Außenwelt bedienen, die diese Arbeit erledigt. Ein solches Interface, einen “Sound Sampler” stellt die hier gezeigte Schaltung dar.
Das Herz des Samplers ist ein etwa 35,-DM teurer A/D-Wandler mit der Bezeichnung ZN427, der problemlos erhältlich ist und auch ohne eine allzu aufwendige Zusatzschaltung auskommt. Der ZN427 ist ein bekannter, leistungsfähiger und schneller 8-Bit-Wandler, der zudem relativ preisgünstig ist. Die maximale Eingangsspannung beträgt 5 Volt, Hier nun die Beschreibung der Anschluß-Pins des ZN427:
Pin 1 Busy: wird während des Wandlungsvorganges auf Low gesetzt, damit der Rechner die Daten als ungültig erkennen kann.
Pin 2 Output Enable: bei Low gehen die Datenausgänge in den hochohmigen Zustand,
Pin 3 Clock-Eingang: regelt den internen Wandlungsvorgang, wobei pro Wert 9 Taktimpulse benötigt werden (8+1).
Pin 4 WR: Die Wandlung wird gestartet, wenn der Rechner diesen Eingang auf Low zieht.
Pin 5 R.ext.: hier liegt die negative Versorgungsspannung an.
Pin 6 U in: Eingang für die analogen Signale.
Pin 7 REFin: bestimmt als Referenzspannungseingang den Wandlungsbereich.
Pin 8 Refout: Ausgang für Referenzspannung (wird mit REFin verbunden).
Pin 9 GND: Masseeingang.
Pin 10 Vcc: Eingang für Spannungsversorgung von +5 Volt, die aus dem Joystickport abgenommen werden,
Pin 11-18 Ausgänge: Von denen aus die digitalen Daten an den Computer geschickt werden.
Die übrigen Bauteile der Schaltung dienen als Oszillatorstufe und der Erzeugung der negativen Versorgungsspannung. Da deren Zusammensetzung relativ einfach gehalten ist, erübrigt sich eine ausführliche Beschreibung.
Die gesamte Schaltung des A/D-Wandlers findet auf einer Platine von etwa 10x5 cm Platz, die nur einseitig beschichtet sein muß. Wer sich das Aufzeichnen des Layouts ersparen möchte, kann auch eine Loehrasterplatine verwenden. Die gedruckte Schaltung sieht jedoch besser aus, ist übersichtlicher und dadurch leichter zu bestücken,
Beim Einlöten der Bauteile dürften eigentlich keine Probleme entstehen, lediglich die beiden ICs sollten gesockelt werden.
Ist die Platine bestückt, verbindet man die Datenausgänge und die Strobe-Leitung sowie die Masse durch ein Flachbandkabel mit den entsprechenden Pins eines 25-poligen D-Sub-Steckers, der am Druckerport des ATARI-ST angeschlossen wird. Da dieser Port keine Versorgungsspannung liefert, greift man diese am Joystickport Pin Nr.7 ab. Hierbei ergibt sich allerdings das Problem der Steckverbindung, da die Serienstecker nicht in den Port passen, und bei Joystickkabeln der Pin 7 nicht belegt ist. Ich habe daher von einem Serienstecker die Blechummantelung abgenommen und die Kunststoffteile wieder zusammengeklebt. An Pin 7 liegen +5V an. Ein Stück Flachbandkabel, das blind an einige Pins angelötet wird, dient der Zugentlastung.
Zum Schluß muß noch der Stecker für den Analogeingang angelötet werden. Da es hier verschiedene Typen und Größen gibt, muß man sich dabei nach dem individuellen Ausgabegerät (Kassettenrekorder, Stereoanlage) richten. Jedenfalls muß der Stecker in einen der Ausgänge passen. Ist nun das Gerät fertig zusammengebaut, so läßt es sich folgendermaßen testen:
Man schließt Versorgungsspannung und Analogeingang an und mißt mit einem Multimeter die Spannungswerte am Sockel des ZN427, dabei dürfen noch keine ICs eingesteckt sein.
Pin 2: 5V
Pin 5: -3V
Pin 6: 2,5V
Pin 9: 0V
Pin 10: 5V
An Pin 3 sollte das Oszilloskop eine Rechteckschwingung zeigen. Sind die Werte in Ordnung, steckt man die beiden Chips in die Sockel (auf die Kerbung achten) und schließt noch den D-Sub-Stecker am Druckerport an (im ausgeschalteten Zustand des Rechners), lädt die Software und ab geht die Post.
Da der Monitorlautsprecher des ATARI-ST hohe Töne nicht übertragen kann, erweist sich eine Abtastfrequenz von 20kHz als die beste Lösung, zumal damit bei reservierten 680 KByte stolze 35 Sekunden gesampelt werden können.
Der Aufwand der bis hierhin getrieben wurde, ermöglicht es noch nicht, eine digitalisierte Stimme aus dem Lautsprecher des SM 124 hören zu können. Dafür benötigt man noch etwas Software, die die gesamten Informationen (Daten) richtig aufbereitet und wiedergibt. Das hier aufgelistete Assembler-Programm ist die minimale Version eines Sound-Samplers, um Geräusche, Stimmen oder auch Musik aufzunehmen bzw. wiedergeben zu können.
Dank des ausführlich dokumentierten Source Codes ist es wohl kaum nötig, die Arbeitsweise des Programms selbst zu erläutern. Auch das Auswahlmenü erklärt sich eigentlich von selbst. Dennoch soll auf dieses genauer eingegangen werden.
Mit F1 wird der Menüpunkt ‘Hören’ aufgerufen. Hier hat man die Möglichkeit, sich das zu sampelnde Stück schon einmal vorher anzuhören, ohne es gleich in den Speicher zu übernehmen. So lassen sich der beste Anfang und das beste Ende für das Stück leichter finden oder der Klang bei verschiedenen Frequenzen testen. Durch einen Druck auf die Space-Taste kehrt man wieder zum Auswahlmenü zurück.
Mit der F2-Taste kann man ein Stück aufnehmen. Zunächst wird das Stück lediglich gespielt, aber noch nicht in den Speicher übernommen. Durch einen Druck auf die Space-Taste beginnt der eigentliche Aufnahmevorgang. Auf diese Weise läßt sich der Anfang des Stückes sehr genau treffen. Dann wird solange aufgenommen, bis entweder der Speicher voll ist oder die Escape-Taste gedrückt wird. Das Stück wird dann im unteren Teil des Bildschirms als Graph dargestellt.
Stückliste
IC1: ZN427
IC2: 14584
R1 390Q
R2 22K
R3 33K
R4 82K
R5 100K
Dl 1N4148
CI 100pF
C2, C3: 10pF
1 D-Sub-Stecker (25-polig)
1 Chinchstecker (Kopfhörer)
1 Joystickbuchse (für ST)
1 IC-Sockel 14 1 IC-Sockel 18
Mit F3 kann ein File von Diskette in den Speicher gelesen werden, um es dann abzuspielen. Es können übrigens auch Files geladen werden, die keine digitalisierte Musik enthalten. Es ist zwar relativ sinnlos, aber bestimmt interessant, wie sich das eine oder andere Programm ‘anhört’.
Durch F4 können die im Speicher befindlichen Daten auf Diskette abgespeichert werden. Sollte auf einer Diskette weniger Platz sein, als für die Daten benötigt werden, wird das File einfach um den entsprechenden Teil gekürzt und dann auf Diskette abgespeichert. Ein paar Bytes werden auf der Diskette immer noch freigelassen.
Durch einen Druck auf F5 kann das Sample abgespielt werden. Es wird so lange wiederholt, bis die Space-Taste gedrückt wird.
Mit F6 kann das Stück rückwärts gespielt werden. Auch hier wird so lange wiederholt, bis die Space Taste gedrückt wird.
Mit F7 läßt sich die Aufnahme- bzw. Hörfrequenz verändern. Diese kann in dem Bereich von 3 bis 22 liegen. Nach dem Programmstart ist sie auf 20 gesetzt. Bei 3 ist die Wiedergabequalität am schlechtesten, bei 22 am besten. Dieser Wert beeinflußt die Menüpunkte ‘Hören’ und ‘Aufnehmen'.
Mit der Funktionstaste F8 läßt sich die Abspielfrequenz verändern. Sie kann in einem Bereich von 3 bis 30 liegen. Dieser Wert beeinflußt die Abspielgeschwindigkeit der Menüpunkte Vorwärts- bzw. Rückwärtsspielen. 30 ist am schnellsten und 3 am langsamsten.
Über F9 läßt sich eine Diskette formatieren. Es gibt die Möglichkeit, entweder eine einseitige oder eine zweiseitige Diskette mit 10 Sektoren zu formatieren.
Mit F10 wird das Programm nach einer Sicherheitsabfrage verlassen.
Da das Programm sehr modular und Übersichtlich aufgebaut ist, dürfte es geübten Programmierern keine größere Schwierigkeiten bereiten, z.B. eine Funktion zur Definition von Blöcken oder andere, eigene Funktionen einzubauen.
; -----------------------------------
; ST Sound Sampler 1.0
; written 1988 by Martin Backschat, Bergstr.16, 8071 Hepberg
; mit IDEAL von OMIKRON ( ' z' zum Assemblieren und 'z,w sample.tos' zum Linken und Sichern)
; -----------------------------------
start:
move.l 4(sp),a0 ; Basepage-Zeiger von Stack holen
move.l $c(a0),d0 ; Länge des benötigten Programmspeicher
add.l $14(a0),d0 ; aus Prg.-,Daten- und uninit. Datenteillänge
add.l $1c(a0),d0 ; berechnen
add.l #$100,d0 ; + 256 Bytes Basepage
move.l d0,-(sp) ; als Argument auf Stack
pea (a0) ; Basis des Programmes
pea $004a0000 ; SETBLOCK
trap #1
lea 12 (sp),sp
;
move.w # 4,-(sp)
trap #$e ; Auflösung abfragen (0,1 Farbe/2 Mono)
addq.l #2,sp
subq.w #1,d0
bpl.s nolowrez ; wenn niedrige Auflösung in mittlere schalten
moveq.l #-1,d0
move.w #1,-(sp)
move.l d0,-(sp) ; Bildschirmlage unverändert lassen
move.l d0,-(sp)
move.w #5,-()sp)
trap #$e Auflösung umschalten
lea 12(sp),sp
moveq.l #0,d0
nolowrez:
move.w d0,rez ; 0 wenn Farbe 640x200, 1 wenn Mono 640x400
.dc.w $a000 ; Grafikroutinen initialisieren
move.l a0,lineavar ; Zeiger auf Grafikvariablen merken
move.w #1,24(a0) ; Alle Grafiken in Rot bzw. schwarz bei Mono
moveq.l #-1,d0
move.w d0,34(a0) Linienmuster ist durchzogene Linie
move.w d0,32(a0)
clr.w 36(a0) ; Schreibmodus a.Ersetzen stellen
;
clr.l -(sp)
move.w #$20,-(sp)
trap #1 ; i. den Supervisormodus wechseln
addq.l #6, sp
move.l d0, oldsp alten Stackzeiger merken
moveq.l #-1,d0
move.l d0,-(sp)
move.w #$48,-(sp)
trap #1 ; Anzahl d.freien Bytes abfragen
addq.1 # 6, sp
sub.l #5*1024,d0 ; 5 KB dem System übriglassen
move.l d0,freemem ; Anzahl der verfügbaren Bytes merken
lea memtext(pc),a0 ; u.d.Benutzer i.Text einblenden
move.l #$ffff,d7
move.l d0,d1
lsr.l #5,d1
lsr.l #5,d1 ; /1024 daraus ergeben sich Anzahl KB
move.l #1000,d2 ; 4 stellige Zahl ausgeben
moveq.l #3,d3
mainl:
divu d2,d1
moveq.l #'0',d4 ; in ASCII-Zeichen umwandeln
add.b d1,d4
move.b d4,(a0)+ ; in Text schreiben
swap d1 ; Divisionsrest als neue Zahl verwenden
and.l d7,d1 ; obere Langworthälfte löschen (für divu)
divu #10,d2 ; auch den Divisor anpassen
dbf d3,mainl ; solange, bis Zahl komplett ausgegeben
move.l d0,-(sp)
move.w #$48,-(sp)
trap #1 ; Verfügbaren Speicher reserv.
addq.l #6,sp
move.l d0,memadr ; Adresse d.Speicherblocks merken
;
clr.b $484 ; Tastenwiederholung u.-klick abschalten
pea siditab(pc) ; Sounddatentabeile
move.w #$20,-(sp)
trap #$e ; Soundchip auf Digitalmusik vorbereiten
addq.l #6,sp
;
lea inittext (pc.) ,a0
bsr print ; Bildschirm löschen
;
menuloop:
lea menutext (pc),a0
bsr print ; Programmenü ausgeben
bsr showgraf ; Graphen des Samples anzeigen
menukey:
bsr getkey ; Taste holen
swap d0 ; Funktionstasten nur über Scancodes erreichbar
moveq.l #$3b, d1
cmp.w d1,d0 ; Taste zwischen F1 und F10?
bmi.s menukey
;
sub.w d1,d0 ; ansonsten aus Tastencode Offset formen
lsl.w #2,d0
lea cmdtab (pc),a0 ; Tabelle mit den Kommandoadressen
move.l 0(a0,d0),a0 ; Adresse d.gewünschten Kommandos holen
jsr (a0) ; anspringen
;
bra. s menuloop ; wenn Kommando ausgeführt, dann wieder ins Menü
; Gibt den Text, auf den A0 zeigt, aus
print:
pea (a0)
move.w #9, -(sp)
trap #1 ; PRINT TEXT
addq.l #6,sp
rts
; Holt eine Taste (unteres D0.W ist ASCII, oberes DO ist Scancode)
getkey:
move.w #7, -(sp)
trap #1
addq.l #2,sp
rts
; Gibt eine Nachricht (Zeiger in A0) an den Anwender in Zeile 16 aus
printmsg:
pea (a0) ; Zeiger merken
lea msg1text(pc),a0
bsr print ; erstmal positionieren und Invers an
move.l (sp)+,a0
bsr print ; jetzt Nachricht ausgeben
lea msg2text(pc),a0
bra print ; und Invers ausschalten
; Löscht die Zeile 16, in der eine Nachricht steht
clrmsg:
lea clrmtext(pc),a0
bra print
; Berechnet in D0.L die ASCII-Zahleneingabe (die in 'in ptext' abgelegt ist)
calctext:
lea inptext(pc),a6 ; Hier steht die Zahleneingabe
moveq.l #0,d0 ; Startwert 0
calcloop:
moveq.l #0,d1
move.b (a6)+,d1 ; erstes Zeichen holen
cmp.w #'0',d1 ; ist Zeichen keine ASCII-Zahl, dann
bmi.s calcend ; dies als Textende betrachten und
cmp.w #'9'+1,d1 ; stoppen
bpl.s calcend
sub.b #'0',d1 ; aus ASCII '0-9' die Zahl 0-9 berechnen
mulu #10,d0 ; Zahl um eine 10erpotenz größer
add.w d1,d0 ; Zahl zu Gesamtzahl addieren
bra.s calcloop
calcend:
rts
; holt Eingabe von Benutzer; zuvor Nachricht ausgeben, wobei das erste Byte
; des Textzeigers die maximale Eingabelänge angibt
gettext:
moveq.l #0,d7
move.l d7,d6
move.b (a0)+,d7 ; Anzahl der erlaubten Eingaben
pea (a0) ; Textzeiger merken
lea get1text(pc),a0
bsr print ; erstmal positionieren
move.l (sp)+,a0
bsr print ; Nachricht ausgeben
lea inptext (pc),a6 ; ab hier wird Eingabe gespeichert
keyinpl:
bsr getkey ; Taste holen
cmp.b #$d,d0 ; wurde RETURN gedrückt?
beq.s endgett ; ja, dann abbrechen
cmp.b #$8,d0 ; wurde BACKSPACE gedrückt?
beq.s delgett ; ja, dann letztes Zeichen löschen
cmp.b d6,d7 ; ist erlaubte Textlänge überschritten?
beq.s keyinpl ; ja, dann Taste einfach ignorieren
move.b d0, (a6)+ ; ansonsten speichern
addq.l #1,d6 ; Zeichenzähler erhöhen
move.w d0,-(sp)
move.w #2,-(sp)
trap #1 ; u.Zeichen auf Bildschirm ausgeben
addq.1 #4,sp
bra.s keyinpl ; nächstes Zeichen einlesen
delgett: ; Einsprung für BACKSPACE-Taste
tst.b d6 ; schon alle Zeichen gelöscht?
beq.s keyinpl ; ja, dann ignorieren
subq.l #1,a6 ; ansonsten Zeichen a.Speicher löschen
subq.l #1,d6 ; Zeichenzähler vermindern
lea deltext(pc),a0
bsr print ; u.Zeichen auf Bildschirm löschen
bra.s keyinpl
endgett: ; Einsprung für RETURN-Taste
clr.b (a6) ; Eingabe mit $0-Kode beenden
lea get2text(pc),a0 ; blink.Cursor ausschalten
bsr print
bra clrmsg ; Nachrichtenzeile löschen
; Gibt Fehlernachricht in A0 aus und wartet auf eine Taste
error:
bsr printmsg ; Nachricht in A0 ausgeben
bsr getkey ; auf Taste warten
bra clrmsg ; Nachricht wieder löschen
; Liest digitale Daten vom Printerport ein. Hierbei
; müssen Parameter wie folgt übergeben werden:
; D0.b = Scankode d.Taste, bei deren Druck frühzeitig abgebrochen werden soll
; D1.w = Frq. in Hz (z.B. 22405 oder 8192); muss im Bereich 3000-22500 liegen
; D2.b = Flag um zwischen Anhören (D2=0) und Aufnahme (D2=1) zu unterscheiden
; A1 = Speicheradresse ab der die Daten beim Aufnehmen abgelegt werden sollen
; A2 = bis zu dieser Adresse dürfen Daten abgelegt werden; wird diese Adresse
; erreicht, wird abgebrochen
input:
move.l a1,startadr ; Startadresse für später merken
bsr sndinit ; Timer für Frq.syncronisation init.
;
move.b #$e, (a5) ; ersten Strobe HIGH senden
move.b (a5),d7 ; damit wird Digitizer gestartet
or.b #$20,d7 ; Strobesignal in SID-Register 14
move.b d7,2(a5)
move.b #7, (a5) ; Port B des Soundchips
move.b (a5),d7 ; auf Eingabe schalten
and.b #$7f,d7
move.b d7,2(a5)
;
inploop:
moveq.l #0,d6
move.b #$f, (a5) ; Daten liegen an SID-Register 15 an
move.b (a5),d6 ; Daten auslesen
;
move.b #$e, (a5)
move.b (a5),d7
and.b #$df,d7 ; Strobe LOW ausgeben
move.b d7,2(a5)
move.b (a5),d7
or.b #$20,d7 ; Strobe HIGH ausgeben
move.b d7,2(a5) ; um Digitizer zu <takten>
;
tst.b d2 ; Ausnehmen oder nur anhören?
beq.s norec
move.b d6, (a1)+ ; über A1 speichern
cmp.l a1,a2 ; Endadresse schon erreicht?
beq.s inpend ; ja, dann abbrechen
norec:
and.b #%11111100, d6 ; nur Bits 2-7 verwenden
move.w d6,d7 ; x3 nehmen um als Offset in
add.w d6,d7 ; Lautsprechertabelle zu dienen
add.w d6, d7
movem.l 0(a3,d7),d5-d7 ; Lautsprecherdaten für alle 3 Kanäle
movem.l d5-d7,(a5) ; holen und in SID-Register schreiben
;
cmp.b 2(a4),d0 ; Abbruchtaste gedrückt?
beq.s inpend ; ja, dann abbrechen
inpsync:
btst #4,$d(a6) ; solange warten, bis TIMER D
beq.s inpsync ; runtergezählt hat (Syncronisation!)
bclr #4,$d(a6) ; TIMER D zurücksetzen
bra inploop ; und nächste Daten einiesen
inpend: ; Einsprung für Abbrechen
tst.b d2 ; wurde aufgenommen?
beq. s inpend2
sub.l startadr,a1 ; ja, dann die Länge des aufgenommenen
move.l a1,smpllen ; Bereiches festhalten
inpend2:
bra sndinit2 ; TIMER D wieder zurücksetzen
; Spielt digitale Daten im Speicher ab. Parameter werden wie folgt übergeben:
; D0.b = Scankode der Taste, bei deren Druck spielen abgebrochen werden soll
; D1.w = Frq. in Hz (z.B. 22405 oder 8192); muss im Bereich 3000-30000 liegen
; D2.b = Flag zur Unterscheidung ob vorwärts (D2=0)
; oder rückwärts (D2=1) gespielt werden soll
; D3. w = Anzahl der Wiederholungen
; A1 = Speicheradresse ab der d.digitalen Daten liegen
; A2 = Endadresse der digitalen Daten
play:
subq.w #1,d3 ; Anzahl Wiederholungen f.DBF-Schleife
move.l a1,startadr ; Start- und Endadresse für
move.l a2,endadr ; Wiederholung merken
bsr sndinit ; TIMER D für Syncronisation init.
moveq.l #0,d4
playloop:
tst.b d2 ; vorwärts oder rückwärts spielen?
bne. s playb
move.b (a1)+,d4 ; vorwärts, über Startadresse A1
cmp.l a1,a2 ; Daten lesen; Endadresse erreicht?
bne. s playfl ; nein...
dbf d3,playfla ; wenn ja, Wiederholungszähler
bra.s playend ; vermindern und bei -1 abbrechen
playfla:
move.l startadr,a1 ; Startadresse A1 wieder zurücksetzen
playfl:
bra. s playl
playb: ; Einsprung fürs Rückwärtsspielen
move.b -(a2),d4 ; über Endadresse A2 Daten lesen
cmp.l a1,a2 ; Startadresse schon erreicht?
bne. s playl ; nein...ok
dbf d3,playla ; wenn ja, dann Wiederholungszähler
bra.s playend ; vermindern und bei -1 abbrechen
playla:
move.l endadr,a2 ; Endadresse A2 wieder zurücksetzen
playl:
and.b #%11111100, d4 ; nur Bits 2-7 verwenden
move.w d4,d7 ; x3 um als Offset in
add.w d4,d7 ; Lautsprechertabelle zu dienen
add.w d4, d7
movem.l 0(a3,d7),d5-d7 ; Lautsprecherdaten holen und
movem.l d5-d7,(a5) ; in Lautsprecher d.SID schreiben
cmp.b 2(a4),d0 ; Abbruchtaste gedrückt?
beq.s playend ; ja, dann abbrechen
playsync:
btst #4,$d(a6) ; warten, bis TIMERD runtergezählt
beq.s playsync ; hat (Syncronisation!)
bclr #4,$d(a6) ; TIMER D zurücksetzen
bra playloop ; und weitermachen
playend: ; Einsprung fürs Abbrechen
bra sndinit2 ; TIMER D zurücksetzen
; Initialisiert TIMER D und bereitet System auf Abspielen/Aufnehmen vor
sndinit:
move.w #$2700, sr ; ab jetzt kein IRQ mehr!
lea voldat(pc),a3 ; Lautsprechertabellenbasis in A3
move.w #$fc00,a4 ; Tastatur-ACIA-Basis in A4
move.w #$8800,a5 ; SID-Basis in A5
move.w #$fa00,a6 ; MFP-Basis in A6
;
move.l #2457600,d7 ; aus Hz-Zahl in D1.w Timerdaten
divu d1,d7 ; berechnen mit Formel
lsr.w #2,d7 ; TI-DATA x 4 = TAKT / HZ
move.b d7,$25(a6) ; Timerstartwert
move.b $ld(a6),d7 ; Vorteiler des TIMERs D
and.b #$f0,d7 ; ist 4
or.b #$01,d7
move.b d7,$ld(a6)
bset #4,9(a6) ; TIMER D muss IRQ signalisieren
rts ; können
; TIMER D zurücksetzen und System wieder normalisieren
sndinit2:
move.b $1d(a6),d7
and.b #$f0,d7 ; TIMER D stoppen
move.b d7,$1d(a6)
bclr #4,9(a6)
move.w #$2300,sr ; IRQs wieder erlauben
rts
; Beendet nach Nachfrage das Programm
quit:
lea quittext(pc),a0
bsr printmsg ; Nachfragetext ausgeben
quitkey:
bsr getkey ; Taste holen
cmp.b #'j',d0 ; wenn j für JA gedrückt, dann
beq.s surequit ; abbrechen
cmp.b #'J',d0 ; wenn J für JA gedrückt, dann
beq.s surequit ; abbrechen, ansonsten wieder ins
bra clrmsg ; Menü zurückkehren
surequit:
addq.l #4,sp ; Rücksprungadr d.JSR vom SP entfernen
move.b #7,$484 ; Tastenwiederholung u.Klick einsch.
move.l oldsp,-(sp)
move.w #$20,-(sp)
trap #1 ; wieder in den USER-Modus zurück
addq.1 #6, sp
move.l memadr,-(sp) ; Basis d.reservierten Speichers
move.w #$49,-(sp)
trap #1 ; belegten Speicher wieder freigeben
addq.l #6,sp
clr.w -(sp)
trap #1 ; Programm beenden
; Digital Sound vom Druckerport anhören
listen:
lea heartext(pc),a0
listenl:
bsr printmsg ; Nachricht ausgeben
moveq.l #$39,d0 ; Abbruchtaste ist SPACE
move.w recfrq,d1 ; z.Anhören Aufnahmefrq. verwenden
moveq.l #0,d2 ; Flag auf Anhören setzen
bsr input ; Anhören
bra clrmsg ; Nachricht löschen u.i.Menü zurück
; Digital Sound vom Druckerport aufnehmen
record:
lea recltext(pc),a0
bsr listenl ; erstmal Anhören und bei SPACE
lea rec2text(pc),a0 ; Aufnahme starten
bsr printmsg
moveq.l #$01,d0 ; bei Aufnahme ist ESC die Abbruchtaste
move.w recfrq,d1 ; Aufnahmefrq.
moveq.l #1,d2 ; Flag für Aufnehmen
move.l memadr,a1 ; Startadresse, ab der aufgenommen wird
move.l a1,a2
add.l freemem,a2 ; Endadresse d.Speicherblocks
bsr input ; Aufnehmen
bra clrmsg ; Nachricht löschen und ins Menü
; Abspielfrq. neu setzen (Eingabe des Anwenders in KHz)
setplay:
lea setptext(pc),a0 ; Eingabetext
move.l #playfrq,frqptr ; Zeiger a d.Abspielfrq ( . w)
moveq.l #30,d0 ; maximale Frq. ist 30 KHz
bra.s setlabl
setrec:
lea setrtext(pc),a0 ; Eingabetext
move 1 #recfrq,frqptr ; Zeiger a d.Anfnamhefrq.(.w)
moveq.l #22,d0 ; maximale Frq. ist 22 KHz
setlabl:
move w d0, -(sp) max . Frq. merken
bsr gettext ; erstmal Benutzereingabe holen
bsr calctext ; Eingabe i.Zahl in D0.W umwandeln
move.w (sp)+,d1 max. Frq. zurückholen
cmp w #3,d0 ; min.Frq. 3 KHz unterschritten
blt. s seterr ; oder maximale Frq. 22/30 KHz
cmp.w d1,d0 ; überschritten, dann
bhi.s seterr ; Fehlermeldung ausgeben
mulu #1000,d0 ; aus KHz Hz machen (x1000)
move.l frqptr,a0 ; Zeiger auf Frequenz (.w)
move.w d0,(a0) ; Hz-Zahl speichern
rts ; und zurück ins Menü
seterr:
lea setetext(pc),a0 ; Fehlermeldung ausgeben
bra error
; digitalen Sound im Speicher rückwärtsspielen
playback:
moveq.l #1,d2 ; Flag für rückwärts
bra.s playsnd ; digitalen Sound im Speicher abspielen
playforw:
moveq.l #0,d2 ; Flag für vorwärts
playsnd:
move.w d2,-(sp) ; Flag merken
lea playtext(pc),a0 ; Nachricht ausgeben
bsr printmsg
moveq.l #$39,d0 ; Abbruchtaste ist SPACE
move.w playfrq,d1 ; Abspielfrquenz holen
move.w (sp)+,d2 ; Flag vor/rückwärts
moveq.l #-1,d3 ; Anzahl d.Wiederhol, unendlich
move.l memadr,a1 ; Startadresse des Sounds
move.l a1,a2
add.l smpllen,a2 ; Endadresse des Sound-Speicherblocks
bsr play ; und abspielen
bra clrmsg ; Nachricht löschen u.i.Menü zurück
; setzt das Laufwerk, wenn bei Filenameneingabe Laufwerksangabe vorkommt (A:...)
setdrv:
cmp.b #':',inptext+1 ; kommt Laufwerksangabe vor?
bne.s setdrve ; nein, dann zurück
moveq.l #0,d0
move.b inptext,d0 ; Laufwerksnummer (A,B,C...) holen
and.b #%ll,d0 ; für Funktions in Nummern (0,1,2)
subq.w #1,d0 ; umformen
move.w d0,-(sp)
move.w #$e,-(sp)
trap #1 ; SET DRIVE
addq.l #4,sp
setdrve:
rts
; Lädt eine Sample-Datei von Disk
load:
lea loadtext(pc) ,a0
bsr gettext ; Dateinamen holen
bsr setdrv ; ggf. Laufwerk setzen
clr.w -(sp)
pea inptext(pc)
move.w #$3d, -(sp)
trap #1 ; Datei öffnen
addq.l #8,sp
tst.l d0 ; Fehler beim Öffnen?
bmi.s loaderr ; ja, dann Datei nicht gefunden..
move.w d0,-(sp)
move.l memadr, -(sp) ; Startadresse
move.l freemem,-(sp) ; Länge des Speicherblocks
move.w d0,-(sp)
move.w #$3f,-(sp)
trap #1 ; Datei einiesen
lea 12(sp),sp
move.l d0,smpllen ; Anzahl der eingelesene Bytes
move w #$3e,(sp) ; a1s Sample-Länge übernehmen
trap #1 ; Datei wieder schliessen
addq.l #4,sp
rts
loaderr:
lea loadetxt(pc) ,a0 ; <File not found>-Fehlermeldung
bra error ; ausgeben
; Speichert aktuelles Sample auf Disk
save:
lea savetext(pc),a0
bsr gettext ; Dateiname holen
bsr setdrv ; ggf. Laufwerk setzen
clr.w -(sp)
pea diskinfo(pc)
move.w #$36,-(sp)
trap #1 ; GET DISKINFO
addq.l #8,sp
move.l smpllen,d7 ; Länge des Samples
move.l diskinfo,d0 ; freie Clusters auf Disk
subq.l #2,d0
mulu #1024,d0 ; -> freie Bytes auf Disk
cmp.l d7,d0 ; zu wenig freie Bytes auf Disk?
bmi.s saveerr ; ja, dann Sample einfach kürzen
saveerrl:
clr.w -(sp) pea inptext(pc)
move.w #$3c,-(sp)
trap #1 ; Datei auf Disk eröffnen
addq.l #8,sp
move.w d0,-(sp)
move.l memadr,-(sp) ; Startadresse des Samples
move.l d7,-(sp) ; (angepasste) Länge des Samples
move.w d0,-(sp)
move.w #$40,-(sp)
trap #1 ; Sample auf Diskette speichern
lea 12(sp),sp
move.w #$3e,-(sp)
trap #1 ; Datei schliessen
addq.l #4,sp
bra clrmsg ; und zurück ins Menü
saveerr: ; Einsprung, wenn Diskkapazität zu gering
exg d0,d7 ; Samplelänge einfach anpassen
lea saveetxt(pc),a0 ; Benutzermitteilung ü.Anpassung
bsr printmsg ; geben
bra saveerrl ; und speichern...
; formatiert eine Diskette 1- oder 2-seitig mit 80 Tracks/10 Sektoren
format:
lea formtext(pc),a0
bsr gettext ; Anzahl d.Seiten vom Benutzer holen bsr calctext ; in Zahl umwandeln (1 oder 2)
move.l d0, d7
subq.l #1,d7 ; 1/2 in 0/1 formen
bmi formend ; bei unzulässiger Eingabe abbrechen
cmp.w #2,d7
bpl formend
;
lea formtpuf(pc),a6 ; Datenpuffer fürs Formatieren
moveq.l #79,d6 ; beginnen mit Track 80
nexttr: ; Label für Trackzähler
move.w d7, d5
nextside: ; Label für Seitenzähler
clr.w -(sp)
move.l #$87654321,-(sp)
move.w #1,—(sp)
move.w d5,-(sp)
move.w d6,-(sp)
move.w #10,-(sp)
clr.w -(sp)
clr.l -(sp)
pea (a6)
move.w #10,-(sp)
trap #$e ; Track D6/Seite D5 formatieren
lea 26(sp),sp
dbf d5,nextside ; Seitenzähler
dbf d6,nexttr ; Trackzähler
;
bsr clrpuf ; Puffer löschen
clr.w -(sp)
moveq.l #2,d0
add.w d7,d0
move.w d0,-(sp)
move.l #$fffffff,-(sp)
pea (a6)
move w #18,-(sp)
trap #$e ; Bootsektor initialisieren
lea 14 (sp),sp
;
move.w #800,d0 ; Anzahl Sektoren auf Disk
addq 1 #1,d7
mulu d7,d0 ; wenn 2-seitig, dann x2-> 1600 Skt.
move.b d0,$13(a6) ; Anzahlen Sektoren im 8086 Format
lsr.w #8,d0 ; abspeichern (LOW-HIGH)
move.b d0,$14(a6)
move.b #10,$18(a6) ; Anzahl Sektoren/Track
moveq.l #1,d0 ; Puffer n.Sektor 1/Track Osichern
bsr writesec
bsr clrpuf ; Puffer löschen
move.l #$f7ffff00, (a6) ; FAT initialisieren
moveq.l #7,d0 ; Puffer nach Sektor 7 und 2
bsr writesec
moveq.l #2,d0
bra writesec
formend:
rts
; schreibt Sektor D0 auf Disk (Track 0/Seite 1)
writesec:
move.w #1,-(sp) ; nur einen Sektor schreiben
clr.w -(sp)
clr.w -(sp)
move.w d0,-(sp) ; Sektornummer
clr.w -(sp)
clr.l -(sp)
pea (a6)
move.w # 9, -(sp)
trap #$e ; Sektor schreiben
lea 20(sp),sp
rts
; Puffer für Track- und Sektorendaten löschen
clrpuf:
move.l a6,a0
move.w #512/4,d0
clrpuf1:
clr.l (a0)+ ; 512 Bytes löschen
dbf d0, clrpuf1
rts
; Zeigt den Grafen des Samples im Speicher an. Dieser wird in den Bildschirmzeilen 18-25 dargestellt.
; Die Pixelzeilen sind auflösungsabhängig,
showgraf:
lea clrgrtxt(pc),a0
bsr print ; Zeilen 18-25 löschen
move.l lineavar,a6 ; Basis der Grafikvariablen
lea 38(a6),a6 ; Basis der XI,Y1/X2,Y2-Koordinaten
move.l memadr,a5 ; Startadresse in A5
move.l #640,d3 ; Breite einer Pixelzeile
move.l smpllen,d7 ; Länge des Samples
cmp.l d3,d7 ; wenn Sample kürzer als 64 0 Bytes
blt showend ; dann abbrechen
divu d3,d7 ; ansonsten Offset berechnen aus
moveq.l #0,d6 ; Formel OFFS = LEN/640
move.w d6, (a6) ; Startkoordinaten X1 = 0
move.w #18*16+56,d0 ; Y1 = Mittl.Zeile des Bereichs
tst.w rez ; Zeile 18-25
bne.s highl ; da auflösungsabhängig, b.mittlerer
lsr.w #1,d0 ; Auflösung Pixelzeile einfach
highl: ; halbieren
move.w d0,2(a6) ; Y1 speichern
drgraf: ; Einsprung für Spaltenzähler
moveq.l #0,d5
move.b (a5),d5 ; Samplebyte auslesen
lea 0(a5,d7),a5 ; m.Offset z.nächsten Samplebyte
lsr.b #2,d5 ; nur Bit 2-7 verwenden
move.b d5,d4 ; Samplebyte liegt im Wertbereich 0-63
lsr.b #2,d4 ; wir haben aber 112 Pixelzeilen zur
add.b d4,d5 ; Verfügung, darum x1.75 nehmen
add.b d4,d5 ; (1 + 1/4 + 1/4 + 1/4)
add.b d4,d5
add.w #18*16,d5 ; Absolute Pixelzeile berechnen
tst.w rez
bne.s highrez ; wenn mittlere Auflösung, dann
lsr.w #1,d5 ; einfach Höhe halbieren
highrez:
move.w d6,4(a6) ; X2-Koordinate
move w d5,6(a6) ; gerade berechnete Y2-Koordinate
dc.w $a003 ; DRAW LINE
move.w d6,(a6) ; End- werden Startkoordinaten
move.w d5.2(a6)
addq.1 #1,d6 ; X Koordinate erhöhen
cmp.w d3,d6 ; schon Endspalte 640 erreicht?
blt drgraf ; nein, dann weitermachen
showend:
rts
; ----------------
; Datenteil
; ----------------
.data
inittext:
.dc.b $1b,"E",$1b,"f", 0
menutext:
.dc.b $1b,"Y",32+16,32+0,$1b,"d",$1b,"H"
.dc.b " ",$1b,"p"," ST Sound Sampler - "
memtext: .dc.b "0000 KB frei ", $1b,"q",13,10,13,10
.dc.b " Software by M.Backschat -Hardware by F.Weiss",13,10
.dc.b 13,10,13,10
.dc.b " F1.....Hören", 13, 10
.dc.b " F2.....Aufnehmen", 13,10
.dc.b " F3.....Sample laden", 13,10
.dc.b " F4.....Sample sichern", 13, 10
.dc.b " F5.....Vorwärts spielen",13,10
.dc.b " F6.....Rückwärts spielen", 13, 10
.dc.b " F7.....Aufnahme/Hörfrq. einstellen",13,10
.dc.b " F8.....Abspielfrq. einstellen",13, 10
.dc.b " F9.....Disk formatieren", 13,10
.dc.b " F10....Ende!", 13,10,0
clrgrtxt:
.dc.b $1b,"Y",32+18,32+0,$1b,"J",0
get1text:
.dc.b $1b,"Y",32+16,32+0,$1b,"e",0
get2text:
.dc.b $1b,"f", 0
msg1text:
.dc.b $1b,"Y",32+16,32+0, $1b, "p",0
msg2text:
.dc.b $1b,"q" ,0
clrmtext:
.dc.b $1b,"Y",32+16,32+0,$1b,"1",0
deltext:
.dc.b $1b,"D"," $1b,"D",0
quittext:
.dc.b " Programm verlassen (j/n)? ",0
heartext:
.dc.b " SPACE zum Abbrechen ", 0
recltext:
.dc.b " SPACE zum Aufnahmestart ",0
rec2text:
.dc.b " ESC zum frühzeitigen Abbruch ",0
setrtext:
.dc.b 2
.dc.b " Neue Aufnahme/Hörfrq. in KHz (3-22): ",0
setptext:
.dc.b 2
.dc.b " Neue Abspielfrq. in KHz (3-30) : ",0
setetext:
.dc.b " Frq. war nicht im 3-22/30 KHz-Bereich! ",0
playtext:
.dc.b " SPACE zum Abbrechen (ansonsten Wiederholung)", 0
savetext:
.dc.b 40
.dc.b " Sample sichern unter: ",0
saveetxt:
.dc.b " Zu geringe Diskkapazität - Sample wird gekürzt! ", 0
loadtext:
.dc.b 40
.dc.b " Lade Sample mit Namen: ",0
loadetxt:
.dc.b " Kann Sample nicht finden' ",0
formtext:
.dc.b 1
.dc.b " Anzahl Seiten (1,2,RETURN ist Abbruch): ",0
.even
; Kommandoadressen, die über F1-F10 aufrufbar sind
cmdtab:
.dc.l listen, record, load,save,playforw
.dc.1 playback, setrec, setplay,format,quit
; Lautsprecherdatentabelle
voldat_
.dc.l $08000000,$09000000,$0a000000
.dc.l $08000000,$09000000,$0a000200
.dc.1 $08000000,$09000000,$0a000300
.dc.l $08000200,$09000200,$0a000200
.dc.l $08000500,$09000000,$0a000000
.dc.l $08000500,$09000200,$0a000000
.dc.l $08000600,$09000100,$0a000000
.dc.l $08000600,$09000200,$0a000100
.dc.l $08000700,$09000100,$0a000000
.dc.l $08000700,$09000200,$0a000000
.dc.l $08000700,$09000300,$0a000100
.dc.l $08000800,$09000000,$0a000000
.dc.l $08000800,$09000200,$0a000000
.dc.l $08000800,$09000300,$0a000100
.dc.l $08000800,$09000400,$0a000100
.dc.l $08000900,$09000000,$0a000000
.dc.l $08000900,$09000200,$0a000000
.dc.l $08000900,$09000300,$0a000100
.dc.l $08000900,$09000400,$0a000100
.dc.l $08000900,$09000500,$0a000000
.dc.l $08000900,$09000500,$0a000200
.dc.l $08000900,$09000600,$0a000000
.dc.l $08000900,$09000600,$0a000200
.dc.l $08000a00,$09000200,$0a000000
.dc.l $08000a00,$09000200,$0a000200
.dc.l $08000a00,$09000400,$0a000100
.dc.l $08000a00,$09000500,$0a000000
.dc.l $08000a00,$09000500,$0a000200
.dc.l $08000a00,$09000600,$0a000100
.dc.l $08000a00,$09000600,$0a000300
.dc.l $08000b00,$09000100,$0a000000
.dc.l $08000b00,$09000200,$0a000100
.dc.l $08000b00,$09000300,$0a000100
.dc.l $08000b00,$09000400,$0a000100
.dc.l $08000b00,$09000500,$0a000100
.dc.l $08000b00,$09000600,$0a000000
.dc.l $08000b00,$09000600,$0a000200
.dc.l $08000b00,$09000700,$0a000000
.dc.l $08000b00,$09000700,$0a000100
.dc.l $08000b00,$09000700,$0a000300
.dc.l $08000b00,$09000700,$0a000400
.dc.l $08000b00,$09000800,$0a000100
.dc.l $08000b00,$09000800,$0a000300
.dc.l $08000b00,$09000800,$0a000400
.dc.l $08000b00,$09000800,$0a000500
.dc.l $08000b00,$09000800,$0a000500
.dc.l $08000c00,$09000200,$0a000000
.dc.l $08000c00,$09000200,$0a000200
.dc.l $08000c00,$09000400,$0a000100
.dc.l $08000c00,$09000500,$0a000000
.dc.l $08000c00,$09000500,$0a000300
.dc.l $08000c00,$09000600,$0a000000
.dc.l $08000c00,$09000600,$0a000200
.dc.l $08000c00,$09000700,$0a000000
.dc.l $08000c00,$09000700,$0a000300
.dc.l $08000c00,$09000700,$0a000400
.dc.l $08000c00,$09000800,$0a000000
.dc.l $08000c00,$09000800,$0a000300
.dc.l $08000c00,$09000800,$0a000400
.dc.l $08000c00,$09000800,$0a000500
.dc.l $08000c00,$09000900,$0a000000
.dc.l $08000c00,$09000900,$0a000300
.dc.l $08000c00,$09000900,$0a000400
.dc.l $08000c00,$09000900,$0a000500
aiditab:
.dc.b 0,$ff,1,$ff,2,$ff,3,$ff,4,$ff,5,$ff,6,0,7,$3f
.dc.b 8,0,9,0,10,0,$ff,0
recfrq:
.dc.w 20000
playfrq:
.dc.w 20000
smpllen:
.dc.l 1
;----------------------
; Datanteil uninitialisiert
.bss
rez:
.ds.w 1
linaavar:
.ds.l 1
oldsp:
.ds.l 1
inptext:
.ds.b 80
frqptr:
.ds.l 1
memadr:
.ds.l 1
freemem:
.ds.l 1
startadr:
.ds.l 1
endadr:
.ds.l 1
diskinfo:
.ds.l 4
formtpuf:
.ds.b 9*1024
Das Assemblerlisting des Sound-Samplers