← ST-Computer 08 / 1988

Soundsampler im Selbstbau: Sample mir's noch einmal Sam

Projekt

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.

Bild 1: So sieht ein Analog~Signal aus

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.

Quantisierung & Abtastung

Bild 2: Quantisierung & Abtastung

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.

Die Hardware

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.

Der Aufbau

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.

Bild 3 Der A D Wandler ZN427

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.

Bild 4: Der Schaltplan des Sound-Samplers

Die Software

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

Bild 5: Der Layout- und der Bestückungsplan (Maßstab 1:1)

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

Martin Backschat