DSP-Programmierung auf dem Falcon, Teil 4: Tips & Tricks mit Programmbeispiel

Waren die letzten drei Teile eher theoretischer Natur, kommen jetzt vom Badewannenheuler bis zum Wiener Sängerknaben alle auf ihre Kosten. Mit einem Karaoke-Effekt läßt sich das Stimmensignal eines Musikstückes löschen, so daß eine Art Playback entsteht.

Egal ob man nun zu einem Musikstück lieber selbst singen will, oder ob man vielleicht sogenannte „objectionable language“ in Liedern (natürlich aus Jugendschutzgründen) ausblenden will, die digitale Technik, und damit der DSP im Falcon, macht’s möglich. Zukünftig werden wohl Discjockeys, wenn zu fortgeschrittener Stunde mal wieder ein alter Ohrwurm des Kalibers „We don’t need no...“ läuft, nicht mehr die Lautstärke zurückschrauben, sondern auf dem daneben stehenden Falcon einfach den Karaoke-Effekt einschalten und somit das ganze Publikum zum Mitsingen auffordern.

Aus heutiger Sicht mag auch der Rap viel zu früh erschienen sein. Zig Tonnen schwarzes Vinyl hätte man vor der sicheren Vernichtung bewahren können, wäre nicht mehr mit dem Plattenteller, sondern mit der Computermaus gescratcht worden. Wer weiß, was auf digitalem Wege noch alles möglich ist und einmal ganz neue Trends in Bewegung setzt! „Where have all the (old) good times gone?“ Soweit wollen wir hier allerdings nicht gehen. Unser Ziel ist es, aus einem Wirrwarr von Frequenzen die der Gesangsstimme herauszufiltern. Dies läßt im ersten Moment einen tiefgreifenden Algorithmus (evtl, sogar mit Spracherkennung) dahinter vermuten. Und - dieses Eingeständnis muß sein: Wenn man es richtig machen wollte, müßte man wohl auch so verfahren. Zum Glück aber gibt es einen einfacheren und trotzdem „fast“ perfekten Weg.

Das Karaoke-Prinzip

Eine zwingende Voraussetzung ist allerdings die Bearbeitung von Stereo-Aufnahmen. Wer über eine gute Stereo-Anlage verfügt und keine, taube oder extrem tolerante Nachbarn hat, dem ist sicherlich schon aufgefallen, daß der Gesang meistens in der Kanalmitte liegt. Über Kopfhörer hinterläßt das den Eindruck, als ob die oder der Sänger genau zwischen den Gehirnwindungen säße. Die verschiedenen Instrumente hingegen liegen meist seitlich oder wechseln ständig die Position. Außerdem werden selbst für Studioaufnahmen keine Stereo-Mikrophone benutzt, womit halbwegs sichergestellt wäre, daß das Stimmensignal im rechten und linken Kanal identisch ist. Und genau dort setzt unser Karaoke-Effekt (Karaoke: jap. ohne Orchester) an. Dazu filtert man das Signal in der Kanalmitte heraus, welches nicht zwangsläufig die Singstimme sein muß. So erwischt dieser Effekt zum Beispiel bei vielen Jazz-Stücken auch die Instrumentensoli bzw. den Baß und teilweise das Schlagzeug (Bass-Drum).

Bleibt noch zu klären, wie man die Kanalmitte herausfiltert. Eine einfache Formel veranschaulicht unseren Fall. Folgendes sei gegeben: Eine Singstimme S in der Kanalmitte und für den linken und rechten Kanal jeweils ein mehr oder weniger wohlklingendes Hintergrundgeräusch LH und RH. Damit ergeben sich folgende Signale für die beiden Kanäle:

Linker Kanal: L = S + LH
Rechter Kanal: R = S + RH

Zieht man nun den rechten vom linken Kanal ab, kommt man mit „etwas höherer“ Mathematik zu folgendem Ergebnis:

L - R = LH - RH

Damit wäre bewiesen, daß sich auf diese Weise die Stimme in der Kanalmitte herausfiltern läßt. Um einem Überlauf durch die Subtraktion vorzubeugen, sollte man vorher noch den Wertebereich beider Kanäle halbieren.

Den Beweis, daß das Ergebnis LH-RH auch noch nach der Hintergrundmusik der beiden Kanäle klingt, liefert unser Programmbeispiel.

Eine so verblüffend einfache Formel mit einem so beeindruckenden Ergebnis verführt förmlich dazu, einfach einmal andere Formeln zu benutzen. Dennoch wird man schnell feststellen: Man kann zwar viele Algorithmen aus dem immensen Repertoire der Mathematiker auf die Sample-Daten hetzen, die, mathematisch betrachtet, durchaus auch „schöne“ Ergebnisse liefern, ob allerdings die weitaus sensibleren Gehörnerven zum selben Schluß kommen, steht auf einem anderen Blatt.

Auf dem Plattenteller

Kommen wir zum technischen Teil: der Implementierung im DSP. Wie in [1] und [2] beschrieben, versorgt die DMA-Matrix den DSP mit den Sounddaten. Dies bedeutet für den DSP im Maximalfall einen Datendurchsatz von vier Stereokanälen (jeweils DMA-Aufnahme und DMA-Wiedergabe) mit 16-Bit-Samples. Bei maximaler Sample-Frequenz sind das etwa 1,6 MByte pro Sekunde. Allerdings besteht die Aufgabe des DSP nicht nur darin, diese Datenmenge hin und her zu schaufeln, sondern vielmehr in der Berechnung der Samples. Noch wichtiger ist dann die „rechtzeitige“ Berechnung.

Zwar besitzt der DSP einen eigenen Timer, da in unserem Fall aber die DMA-Matrix den Takt diktiert, überläßt man die Synchronisation beim Datenaustausch auch weitgehend dem Multiplexer. Beim Senden und Empfangen der Samples löst dieser jeweils einen Interrupt im DSP aus, der sich wiederum um den verbleibenden Rest kümmert. Dafür stehen im DSP sowohl Sende- als auch Empfangsvektoren bereit. Bevor die beiden Vektoren jedoch „anspringen“, ist noch etwas Konfiguration vonnöten. Zuerst bestimmt man, welche Priorität (Interrupt-Level) ein ausgelöster Interrupt erhält.

Da der DSP neben dem hier angesprochenen SSI-Interface noch weitere Ein-/Ausgabekanäle besitzt, die sich ebenfalls interruptsteuern lassen, bietet eine freie Prioritäten wähl der verschiedenen Schnittstellen enorme Vorteile. In unserem Fall, bei dem nur die SSI-Interrupts zum Einsatz kommen, muß die Priorität einfach nur höher sein als außerhalb des Interrupts.

Als nächstes gilt es, das Datenformat dieser seriellen Schnittstelle festzulegen. Zunächst wäre da die Wortbreite in Bits (bzw. hier die Sample-Breite). Da ATARI seine Entwickler mit der Funktion setsndmode() gesegnet hat, mit der man immerhin zwischen 8-Bit-Mono und 8- und 16-Bit-Stereo-Samples wählen kann, könnte man der Versuchung unterliegen, entsprechende Daten auch am DSP zu erwarten. - Leicht daneben - genauer gesagt: zweimal daneben. Zum ersten erreichen die Samples den DSP immer 16 Bit breit und zum zweiten auch immer in Stereo.

Was nun die Funktion setsndmode() anbelangt, bezieht diese sich ausschließlich auf die DMA-Wiedergabe. Das heißt, daß die Samples zwar in diesen Formaten voriiegen dürfen, dann aber auf das 16-Bit-Stereoformat gebracht werden. Innerhalb der Verbindungsmatrix gibt es dann nur noch dieses Format. Entsprechend gelangen die Daten bei der DMA-Aufnahme nur im 16-Bit-Stereoformat in den Speicher.

Zurück zum DSP: dort erleichtert dieser Umstand natürlich die Arbeit, bleibt doch als variabler Faktor nur noch die Anzahl der Spuren (settrack()), wobei der DSP intern zwischen Spuren und Kanälen (Mono oder Stereo) nicht unterscheidet. Er vergibt an jede Spur und/oder jeden Kanal eine Zeitscheibe (time slots). Zwei Stereospuren haben also vier Slots. Die Zeitscheiben verteilen sich gleichmäßig über einen Sample-Takt, wobei jeweils ein Sende- und ein Empfangs-Interrupt pro Slot eintritt. Welcher der beiden Interrupts zuerst eintritt, ist nicht definiert. Dieser Umstand hat weitreichende Folgen.

Tritt zum Beispiel der erste Sende-Interrupt auf, liegen noch nicht alle Eingangs-Samples des gleichen Taktes vor. Diese benötigt man aber eventuell zur Berechnung des Ausgangssignals. Folglich müssen die Samples eines Taktes zwischengespeichert werden. Da die Berechnung der neuen Ausgangs-Samples auch noch einen Sample-Takt benötigen kann und darf, verlassen die Samples also frühestens zwei Takte später den DSP. Bild 1 veranschaulicht diesen Ablauf.

Im SSI Kontrollegister A (Bit 8-12) setzt man also die Samples pro Takt minus eins und bestimmt so, in wieviele Slots sich der Sample-Takt teilt.

Die beiden Interrupt-Routinen sollten jetzt noch erfahren, in welchem Slot sie denn aufgetreten sind. Bit 2 und 3 des SSI-Statusregisters signalisieren hierzu dem Sende- bzw. dem Empfangs-Interrupt eine Taktsynchronisation. Technisch bedingt ist Bit 2 immer im zweiten Slot des Sendeinterrupts gesetzt, Bit 3 hingegen immer im ersten Slot während des Empfangs, unabhängig von der Anzahl der Slots pro Takt. Wie in Bild 1 ersichtlich, findet die eigentliche Sample-Berechnung dann außerhalb der Interrupts in einer Endlosschleife statt. Diese gilt es nun auch im Gleichtakt zu halten. In unserem Beispiel setzt dazu die Empfangsroutine des ersten Slots ein Bit. Ist dieses Bit gesetzt, setzt die Hauptroutine das Bit zurück und beginnt mit der Berechnung aller Samples eines Taktes. Sollte bis zum nächsten Takt etwas Zeit verbleiben, erledigt die Hauptroutine noch die Kommunikation mit dem Host (hier dem Hauptprozessor).

Bild 1: Dieses Diagramm veranschaulicht den zeitlichen Ablauf vom Einlesen der Samples über deren Berechnung bis zur Sample-Ausgabe.

Im SSI Kontrollregister-B schaltet man die SSI-Interrupts ein und versetzt den DSP in den Netzwerkmodus (Bit 11). Der Netzwerkmodus teilt, wie besprochen, den Sample-Takt in gleich lange Slots.

Über das Port-C-Kontrollregister bestimmt man, welche Signale auf den 9 Leitungen des Port C liegen. An den DSP lassen sich wahlweise ein 9-Bit-paralleler Port oder eine serielle synchrone und eine serielle Kommunikationsschnittstelle anschließen. Wie das Kürzel SSI (Synchronous Serial Interface) verrät, benötigen wir hier die synchrone serielle Schnittstelle zum Multiplexer. Zum Schluß setzt man noch den Interrupt-Level herunter, und das ganze Ding kommt ins Rollen.

Prinzipiell wäre ein Programmaufbau, indem auch die Sample-Berechnung innerhalb der Interrupts läuft, einfacher zu realisieren. Nur ist man dann zeitlich fester gebunden, denn jeder Interrupt muß innerhalb seines Slots abgeschlossen werden, während sich in unserem Beispiel der ganze Sample-Takt nutzen läßt. Wenn dann noch mehrere Spuren zu bearbeiten sind und sich dadurch die Slot-Zeiten verkürzen, muß man früher oder später ohnehin auf dieses Verfahren zurückgreifen.

Das auf den folgenden Seiten abgedruckte Assembler-Listing läßt sich von der Host-Seite so konfigurieren, daß wahlweise eine oder zwei Stereospuren zu einem Stereo-Ausgang gemischt werden. Damit eignet sich das DSP-Programm auch für einen Harddisk-Rekorder mit Ein- und Überblendtechnik. Die Pegel aller vier Kanäle lassen sich vom Host setzen.

Damit neben den Ohren auch noch die Augen auf ihre Kosten kommen, berechnet das DSP-Programm gleich noch die Ausgangspegel der abzuspielenden Samples mit. Über regelmäßige Abfragen der Pegel vom Hauptprozessor stellt dieser die Pegel dann grafisch dar. Stereo - versteht sich - schließlich hat man ja auch zwei Augen.

Um den Quelltext nicht ins Unendliche zu treiben, wurde auf eine logarithmische Ausgabe der Pegel verzichtet.

Es ist angerichtet

Und hier noch ein paar Tips, die je nach Planetenkonstellation dem gestreßten Soundprogrammierer zwischen 5 Minuten und 5 Tagen Zeitersparnis einbringen. Zumeist handelt es sich um die Richtigstellung von zweideutigen Formulierungen in der ATARI-Dokumentation, die oft zu Fehlinterpretationen führen.

So findet man des öfteren falsche Frequenzangaben für die verschiedenen Vorteiler im Zusammenhang mit der Funktion devconnect(). Deshalb sollte man sich ausschließlich an die von ATARI ausgegebene Formel

Sample-Frequenz = ( 25.175 MHz / 256 ) / ( Vorteiler + 1 )

halten (25.175 MHz bei Verwendung des CODEC). Die gültigen Vorteilerwerte müssen zwischen eins und elf (ohne 6, 8 und 10) liegen.

Des weiteren entnimmt man der Beschreibung zur Funktion dsptristate() nur, daß man damit die externen Leitungen von dem Multiplexer abkoppelt. Weitaus bedeutender ist aber der umgekehrte Fall: Ist nämlich keine weitere Hardware am DSP-Ausgang angeschlossen, müssen eben selbige Leitungen an die Matrix angeschlossen werden, sobald der DSP im DMA-Datenpfad eingebunden ist. Zusätzliches Gewicht verleiht diesem Aspekt die Tatsache, daß zumindest das TOS 4.01 vom 2.10.92 bei einem Reset die externen Leitungen von der Matrix abkoppelt. Prinzipiell sollte man nicht ATARIs Beispiel folgen und irgendwelche Grundeinstellungen nach einem Reset erwarten. Das zu obiger TOS-Version mitgelieferte Spiel „Breakout“ gibt nämlich nur Töne von sich, wenn vorher ein anderes Programm im Multiplexer DMAPLAY mit DAC verbunden hat (z.B.: mit „TLKCLOCK“).

Kontrollregister des DSP

Host Control Register HCR

Bit-Nummer Funktion
0 Host Receive Interrupt Enable
1 Host Transmit Interrupt Enable
2 Host Command Interrupt Enable
3 Host Flag 2
4 Host Flag 3

Host Status Register HSR

Bit-Nummer Funktion
0 Host Receive Data Full
1 Host Transmit Data Empty
2 Host Command Pending
3 Host Flag 0
4 Host Flag 1
7 DMA Status

SSI Control Register A M_CRA

Bit-Nummer Funktion
0-7 Prescale Modulus
8-12 Frame Rate Divider (#slots -1)
13-14 Word Length Control
00 - 8 Bit, 01-12 Bit
10-16 Bit, 11-24 Bit
15 Prescaler Range

SSI Control Register B M_CRB

Bit-Nummer Funktion
0 Serial Output Flag 0
1 Serial Output Flag 1
2 Serial Control 0 Direction
3 Serial Control 1 Direction
4 Serial Control 2 Direction
5 Clock Source Direction
8 Frame Sync Length
9 Sync/Async Control
10 Gated Clock Control
11 Mode Select (0 - normal, 1 - network)
12 SSI Transmit Enable
13 SSI | Receive Enable
14 SSI | Transmit Interrupt Enable
15 SSI | Receive Interrupt Enable

SSI Status Register M_SR

Bit-Nummer Funktion
0 Serial Input Flag 0
1 Serial Input Flag 1
2 Transmit Frame Sync
3 Receive Frame Sync
4 Transmitter Underrun Error
5 Receiver Overrun Error
6 Transmit Data Register Empty
7 Receive Data Register Full

Port C Control Register M_PCC

Bit-Nummer Funktion
0 0 - Parallel 0 /1 - SCI Receive
1 0 - Parallel 1/1- SCI Transmit
2 0 - Parallel 2 /1 - SCI Clock
3 0 - Parallel 3 /1 - SSI Control 0
4 0 - Parallel 4 /1 - SSI Control 1
5 0 - Parallel 5 /1 - SSI Control 2
6 0 - Parallel 6 /1 - SSI Clock
7 0 - Parallel 7 /1 - SSI Receive
8 0 - Parallel 8 / 1 - SSI Transmit

Interrupt Priority Register M_IPR

Bit-Nummer Funktion
0-1 IRQA Mode Interrupt Priority Level
2 IRQA Mode Trigger Mode
3-4 IRQB Mode Interrupt Priority Level
5 IRQB Mode Trigger Mode
10-11 Host Interrupt Priority Level
12-13 SSI Interrupt Priority Level
00 - locked, 01 - level 0
10 - level 2, 11 - level 3
14-15 SCI Interrupt Priority Level

Tabelle 1: Über diese Kontrollregister läßt sich der DSP an alle äußeren Bedingungen (vorgegeben durch die Falcon-Hardware) anpassen.

Verschiedene Reset-Verhalten bescheinigen die Programmierer von „MUSiCOM“ auch der Funktion sndstatus(1). Diese soll, je nach TOS-Version, die Matrix an die externen Leitungen ab- oder ankoppeln. Außerdem vermisse ich bei der Beschreibung der Funktion buffoperf), den Warnhinweis (mit einem großen roten Dreieck), daß dies sozusagen der Startschuß für die DMA-Aufname bzw. -Wiedergabe ist, auch wenn der Multiplexer noch nicht verbunden wurde oder noch keine Puffer mit setbuffer() gesetzt wurden. Befinden sich die Pufferzeiger noch irgendwo im RAM-Speicher, wird die Sache tragisch, sobald anstelle der Samples eines leisen Solo-Parts der Multiplexer versucht, den Programmcode eines gerade im Speicher befindlichen Debuggers abzuspielen. Dann legen nämlich die über die Stereo-Anlage angeschlossenen Lautsprecherboxen einen rekordverdächtigen Steptanz auf die (Regal-)Bretter. So geschehen!

Dagegen ist die Tatsache, daß die Status von Dsp_Lock() und locksnd() bei Programmbeendigung und natürlich auch bei Programmabsturz nicht zurückgesetzt werden, schon fast belanglos. Weil man aber bei der relativ neuen Materie „DSP-Programmierung“ die Ursache für ein Fehlverhalten überall dort sucht, wo sie nicht ist, und den naheliegendsten Fall außer Betracht läßt, sollte es hier dennoch erwähnt werden.

Vor der Programmbeendigung sollte mit soundcmd(ADCINPUT,3) der Sound-Chip wieder mit dem AD-Wandler verbunden werden, da sonst der Tastaturklick ausbleibt.

In der Sekundärliteratur zum Falcon findet man des öfteren bei der Beschreibung zur Funktion Dsp_BlkHandshake(), daß dort long-Zeiger zu übergeben sind, was dann zu dem falschen Schluß führt, daß hier das gleiche Datenformat wie bei der Funktion Dsp_BlkUnpacked(), nämlich long-Werte, bearbeitet wird. Die Funktion Dsp_BlkHandshake() erwartet aber, wie auch Dsp_DoBlock(), DSP-Wörter, also Drei-Byte-Werte.

Nun noch eine Richtigstellung in eigener Sache:

Leider hat sich in die Header-Datei der DSP-Bibliothek aus [2] ein Fehler eingeschlichen: Bei dem Zielparameter von devconnect() handelt es sich nicht um einen Index, sondern um einen Bit-Vektor. Folglich erhalten die Makros nachstehende Werte und lassen sich beim Aufruf ODER-verknüpfen:

#define DMAREC 0x01 
#define DSPREC 0x02 
#define EXTOUT 0x04 
#define DAC 0x08

Megadisk

Auf der diesmonatigen Megadisk befinden sich, wie immer, alle Sourcen und die ausführbaren Programme zu diesem Teil des DSP-Kurses. Zusätzlich dazu haben wir auch ein kurzes Stereo-Sample mit auf die Diskette gegeben, mit dem der Karaoke-Effekt sehr gut deutlich wird. Wir möchten dabei der westfälischen Rockgruppe „Cabo“ danken, die uns freundlicherweise die Erlaubnis gegeben hat, das Sample aus einem Ihrer Songs zu entnehmen.

Literatur:

[1] Der Verwandlungskünstler, Teil 1, ST-Computer, 10/93

[2] Der Verwandlungskünstler, Teil 2, ST-Computer, 11/93

[3] Der Verwandlungskünstler, Teil 3, ST-Computer, 12/93

[4] DSP56000/DSP56001 Digital Signal Processor User's Manual, Motorola, 1990

[5] Das Buch zum ATARI Falcon030, Dietmar Hendricks, Alexander Herzlinger, Martin Pittelkow, Data Becker, 1. Auflage 1992


; Karaoke-Effekt DSP-Assembler ; (0)1993 by MAXON-Computer ; Autor: Jürgen Lietzow ;--------- INTERRUPT ------------------------ I_RESET EQU $0000 ; hardware _RESET I_STACK EQU $0002 ; stack error I_SSIRD EQU $0000 ; SSI receive data I_SSTRDE EQU $000E ; SSI rec exception I_SSITD EQU $0010 ; SSI transmit data I_SSITDE EQU $0012 ; SSI tra exception I_HSTRD EQU $0020 ; host receive data I_HSTTD EQU $0022 ; host transmit data I_HSTCM EQU $0024 ; host command ;--------- I/O EQUATES --------------------- M_BCR EQU $FFFE ; Port A Bus Control Reg M_PBC EQU $FFE0 ; Port B Control Register M_PCC EQU $FFE1 ; Port C Control Register HCR EQU $FFE8 ; Host Control Register HSR EQU $FFE9 ; Host Status Register HRX EQU $FFEB ; Host Receive Data Reg HTX EQU $FFEB ; Host Transmit Data Reg M_RX EQU $FFEF ; SSI Receive Data Reg M_TX EQU $FFEF ; SSI Transmit Data Reg M_CRA EQU $FFEC ; SSI Control Register A M_CRB EQU $FFED ; SSI Control Register B M_SR EQU $FFEE ; SSI Status Register M_TSR EQU $FFEE ; SSI Time Slot Register M_IPR EQU $FFFF ; Interrupt Priority Reg M_UU EQU $FFEA VMAIN EQU $0040 ;---- Effects -- only use upper 16 bits ------- tKaraoke EQU 8 tLevel EQU 9 ;---- RESET VECTOR ----------------------------- ORG P:I_RESET JMP VMAIN ;---- STACK ERROR VECTOR ----------------------- ORG P:I_STACK JMP VSTACKERR ;---- SSI RCV INTERRUPT VECTORS ---------------- ORG P:I_SSIRD ; input JSR VLRTEST ORG P:I_SSIRDE ; input error JSR VELRTEST ORG P:I_SSITD ; output JSR VOUTPUT ORG P:I_SSITDE ; short output, error MOVEP X:M_SR,X:M_SR ;--------------------------------------------- ; Start of Programm code ;--------------------------------------------- ORG P:VMAIN JMP DoMain ;--------------------------------------------- ; Clear stack pointer and status ;--------------------------------------------- VSTACKERR ORI #$03,MR ; mask interrupts MOVE #>$00,SP JMP VSTACKERR ;--------------------------------------------- ; Receive interrupt routine + error routine ;--------------------------------------------- VELRTEST MOVEP X:M_SR,X:M_UU ; clear status VLRTEST JCLR #3,X:M_SR,NoFSync MOVEP X:M_RX,X:tDInFirst ; first in temp MOVE #tDataIn+1,R3 BSET #0,X:IsSync ; signal frame sync RTI ; to main loop NoFSync MOVEP X:M_RX,X:(R3)+ RTI ;--------------------------------------------- ; Transmit interrupt routine ;--------------------------------------------- VOUTPUT JSET #2,X:M_SR,OUT_R ; frame sync ? MOVEP X:OutLeft,X:M_TX RTI OUT_R MOVEP X:OutRight,X:M_TX MOVE X:tRight,Y1 ; load next pair MOVE Y1,X:OutRight MOVE X:tLeft,Y1 MOVE Y1,X:OutLeft RTI ;--------------------------------------------- ; Init DSP ;--------------------------------------------- DoMain RESET ORI #$03,MR ; Mask Interrupts MOVE #tDataIn,R3 MOVEP #$1,X:M_PBC ; Port B is Host MOVEP #$3000,X:M_IPR ; SSI RCV INT level 2 MOVEP #$4100,X:M_CRA ; Set SSI 16 Bit ; 2 Frames MOVEP #$F800,X:M_CRB ; Set SSI MOVEP #$0000,X:M_PCC ; Individual reset / ; SSI Port MOVEP #$01F8,X:M_PCC ; Turn on SSI Port. MOVEP X:M_SR,X:M_UU ; Clear Status MOVEP X:M_RX,X:M_SR ; Clear Status ANDI #$FC,MR ; Unmask all interrupts ;--------------------------------------------- ; Start of forever loop ; This is looped once for each input frame sync ;--------------------------------------------- ; Registers R3, and Y1 used by Interrupts ; Do not use them here! MainLoop ; NOTE: we have to reach this point before ; start of second time slot. ORI #$03,MR ; Mask Interrupts MOVE X:Right,X0 ; transmit samples MOVE X0,X:tRight ; from last loop MOVE X:Left,X0 ; to output MOVE X0,X:tLeft ; interrupt handler MOVE #DataLvls,R2 ; weight 2 channel MOVE #tDataIn,R1 ; stereo input MOVE X:(R2)+,X0 ; mix 2 channels to ; one channel MOVE X:(R1)+,X1 MPY X0,X1,A X:(R2)+,X0 ; left 1 MOVE X:(R1)+,X1 MPY X0,XI,B X:(R2)+,X0 ; right MOVE X:(R1)+,X1 MACR X0,X1,A X:(R2)+,X0 ; left 2 MOVE X:(R1)+,X1 MACR X0,X1,B A1,X Left ; right 2 MOVE B1,X;Right MOVE X:tDInFirst,X0 ; we got already MOVE X0,X:tDataIn ; first data BCLR #0,X:IsSync ; free for next sync ANDI #$FC,MR ; Unmask all interrupts ; NOTE: we have to reach this point before ; end of second time slot. ;--------------------------------------------- ; Here you may include more Effects ;--------------------------------------------- JSSET #tKaraoke,X:Effect,Karaoke ;--------------------------------------------- ; Do we send peek levels to Host ? ;--------------------------------------------- JSSET #tLevel,X:Effect,Level ;--------------------------------------------- ; While waiting for next frame sync, we ; check if we get commands from Host ;--------------------------------------------- Wait JCLR #0,X:«HSR,NoHostCommand MOVEP X:«HRX,X0 ; Get Host 24 bit JSET #7,X0,ReadXmem ; read/write ? MOVE #>$7F,A ; lower 7 bits AND X0,A #>$7F,XI CMP X1,A A1,R1 JNE XLocation ; #$7F is SSI CRA MOVE #>$FF00,A AND X0,A MOVEP A1,X:M_CRA JMP NoHostCommand XLocation MOVE #>$FFFF00,A ; mask out AND X0,A ; 7 bit address MOVE A1,X:(R1) ; host value JMP NoHostCommand ReadXmem MOVE #>$7F,A ; lower 7 bits AND X0,A ; is address MOVE A1,R1 XMIT JCLR #1,X:«HSR,XMIT ; wait for host MOVEP X:(R1),X:«HTX ; send x memory NoHostCommand JSET #0,X:IsSync,MainLoop JMP Wait ;--------------------------------------------- ; Karaoke Effect ;--------------------------------------------- Karaoke MOVE X:Left,A MOVE X:Right,X0 SUB X0,A ; left - right MOVE A1,X:Left ; left and right MOVE A1,X:Right ; chanal is same RTS ;--------------------------------------------- ; Calculate Peek Level ;--------------------------------------------- Level MOVE X:Counter,A ; get peek of MOVE #$100,X0 ; #Counter samples SUB X0,A ; Counter is in JNE LCNotZero ; upper 16 bits MOVE X:tPeekRight,A1 ; ready for host MOVE A1,X:PeekRight MOVE X:tPeekLeft,A1 MOVE A1,X:PeekLeft MOVE X:CountMax,A1 MOVE #0,X0 MOVE X0,X:tPeekLeft MOVE X0,X:tPeekRight LCNotZero MOVE A1,X:Counter ; store new Counter MOVE X:tPeekLeft,X0 MOVE X:Left,A CMPM X0,A X:tPeekRight,X0 JLE LNext1 ABS A MOVE A1,X:tPeekLeft LNext1 MOVE X:Right,A CMPM X0,A JLE LNext 2 ABS A MOVE A1,X;tPeekRight LNext2 RTS ;--------------------------------------------- ; Data for Host I/O ;--------------------------------------------- ORG X: $0 DataLvls DC $3FFFO0 DC $3FFF00 DC $3FFF00 DC $3FFF00 Effect DC $000300 ; Bit map ; Karaoke + Levels CountMax DC $020000 ; sample number ; only upper 16 bits PeekLeft DC 0 ; Peek left PeekRight DC 0 ; Peek right ;--------------------------------------------- ; Internal Datas ;--------------------------------------------- IsSync DC 0 ; frame sync indicator Left DC 0 ; current left Right DC 0 ; current right ; the ones by interrupt OutLeft DC 0 ; left output channel OutRight DC 0 ; right output channel tLeft DC 0 ; temp left output tRight DC 0 ; temp right output tPeekLeft DC 0 ; temp left peek tPeekRight DC 0 ; temp right peek Counter DC $000100 ; current counter ; only upper 16 bits tOutLeft DC 0 ; only for transmit tOutRight DC 0 ; interrupt. tDInFirst DC 0 tDataIn DS 8 ; max. 8 time slots END

/* Karaoke-Effekt Rahmenprogramm */ /* (c)1993 by MAXON-Computer /* Autor: Jurgen Lietzow #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <time.h> #ifndef __GNU__ #include <tos.h> #define _read(h,b,l) read(h,b,l) #define fflush(a) #else #include <osbind.h> #include <fcntl.h> #include <unistd.h> #endif #include "dsplib.h" /* füllt char buffer mit 3 Byte Worten */ #define Fill3(p,l,v) \ (p[(l)*3+0]=(char)((v)»16),\ p[(l)*3+1]=(char)((v)»8), \ p[(l)*3+2]=(char)(v) ) /* DSP Kommandos */ #define HOST_SEND 0x000000L #define HOST_RECEIVE 0x000080L /* DSP Speicherplätze */ #define LEVEL_L1 0x000000L #define LEVEL_R1 0x000001L #define LEVEL_L2 0x000002L #define LEVEL_R2 0x000003L #define EFFECT 0x000004L #define SMPL_CNT 0x000005L #define PEEK_LEFT 0x000006L #define PEEK_RIGHT 0x000007L /* CRA ist Pseudo-Speicher */ /* immer 16 Bit samples */ #define SSI_CRA 0x00407FL /* Bit-Maske für Effekte */ #define KARAOKE 0x000100L #define LEVEL_MTR 0x000200L /* SSI CRA Werte (frames per sync) */ #define FRAMES_2 0x000100L #define FRAMES 4 0x000300L void PrintAt( int c, int l, char *t ) { char buf[16] = "\33Y"; buf[2] = ' ' + l; buf[3] = ' ' + c; buf[4] = '\0'; Cconws( buf ); Cconws( t ); } int RunDSP( char *dsp_lod ) { long xmem, ymem, x, y; int nblocks; void *buf; long len; int ability; /* Starte ein DSP-Programm */ ability = Dsp_RequestUniqueAbility(); Dsp_Available( &xmem, &ymem ); nblocks =50; x = 1000; y = 2000; if ( xmem < x || ymem < y ) { Dsp_FlushSubroutines(); Dsp_Available( &xmem, &ymem ); if ( xmem < x || ymem < y ) return ( -1 ); } if ( Dsp_Reserve( x, y ) != 0 ) return ( -1 ); len = 3* (x+y+(3*nblocks)); if ( ( buf = malloc( len ) ) == NULL ) return ( -1 ); len = Dsp_LodToBinary( dsp_lod, buf ); Dsp_ExecProg( buf, len, ability ); return ( ability ); } long effect = KARAOKE|LEVEL_MTR; long left = 0x3fff00L; long right = 0x3fff00L; char host_io[16]; int lleft = 0; int lright = 0; int main( void ) { long curadder, curadc; char *sndbuf; char c; int handle; int prescale; long length; long l; /* Ist überhaupt ein DSP vorhanden, */ /* und ist es ein DSP mit Wortgröße 3 ? */ if ( Dsp_GetWordSize() != 3 ) return ( -1 ); /* sperre sound system und DSP */ if ( locksnd() != 1 ) return ( -1 ); if ( Dsp_Lock() != 0 ) { unlocksnd(); return ( -1 ); } /* resette CODEC wenn Fehler */ if ( sndstatus( 0 ) ) sndstatus( 1 ); /* keine externe Hardware */ dsptristate(ENABLE, ENABLE ); if ( RunDSP( "KARAOKE.LOD" ) == -1 ) { Dsp_Unlock(); unlocksnd(); return ( -1 ); } /* ein Stereo-Kanal (2 frames) */ Fill3 ( host_io, 0, SSI_CRA|FRAMES_2 ); Dsp_BlkHandshake ( host_io, 1, NULL, 0 ); Fill3( host_io, 0, HOST_SEND|EFFECT|KARAOKE|LEVEL_MTR ); Dsp_BlkHandshake( host_io, 1, NULL, 0 ); soundcmd( LTATTEN, 0x80 ); /* Ausgabekanal */ soundcmd( RTATTEN, 0x80 ); soundcmd( LTGAIN, 0x80 ); /* Eingabekanal */ soundcmd( RTGAIN, 0x80 ); /* Addierereingang nur von Matrix */ curadder = soundcmd( ADDERIN, INQUIRE ); soundcmd( ADDERIN, 2 ); /* setze ADC Eingang auf Mikrofon */ curado = soundcmd( ADCINPUT, INQUIRE ); soundcmd( ADCINPUT, 0 ); setmontrack(0);/* Lautsprecher = erste Spur */ printf( "\nEingang von \"CABO.HSNV (M)" " oder über Mikrofon (F)?" ); fflush( stdout ); if ( toupper( (char)Bconin( 2 ) ) == 'M' ) { /* lade Sample-Datei */ prescale = 11; length = 214794L; if ( ( sndbuf = malloc( length ) ) == NULL ) return ( -1 ); if ( (handle = open( "CABO.HSN", O_RDONLY )) <= 0 ) return ( -1 ); /* ohne header */ if ( _read( handle, sndbuf, 128 ) < 128 ) return ( -1 ); if (_read(handle, sndbuf, length) != length) return ( -1 ); close( handle ); Fill3(host,io,0, HOST_SEND|SMPL_CNT|((16000L/40)«8)); Dsp_BlkHandshake ( host_io, 1, NULL, 0 ); setsndmode( STEREO8 ); /* setze DMA Abspielpuffer */ setbuffer( 0, &sndbuf[0], &sndbuf[length] ); settrack( 1, 0 ); /* eine Wiedergabespur */ /* verbinde DMA-Quelle mit DSP-Eingang */ devconnect( DMAPLAY,DSPREC, CLK25M, prescale, NO_SHAKE ); /* verbinde DSP-Ausgang mit DAC */ devconnect( DSPXMIT,DAC, CLK25M, prescale, NO_SHAKE ); buffoper( PLAY_ENABLE | PLAY_REPEAT ); } else { setsndmode{ STEREO16 ); Fill3(host_io,0, HOST_SEND | SMPL_CNT|((50000L/40)«8 ) ); Dsp_BlkHandshake ( host_io, 1, NULL, 0 ); /* verbinde ADC-Eingang mit DSP */ devconnect( ADC,DSPREC, CLK25M, 1,NO_SHAKE ); /* verbinde DSP-Ausgang mit DAC */ devconnect( DSPXMIT,DAC,CLK25M, 1,NO_SHAKE ); } /* warte auf Abbruchbedingung */ PrintAt( 0, 1, "\33f" ); /* Cursor aus */ printf( "\nTaste K für Karaoke an/aus" "\nTaste < für linken Kanal verstärken" "\nTaste > für rechten Kanal verstärken" "\nTaste M für Kanalmitte" "\nTaste Return für Abbruch" ); PrintAt(0,8, "Links :"); PrintAt(0,9, "Rechts :"); fflush( stdout ); c = 1; while ( c ) { if ( effect & LEVEL_MTR ) { Fill3(host_io,0,HOST_RECEIVE|PEEK_LEFT) ; Dsp_BlkHandshake( host_io, 1, host_io, 1 ); host_io[0] »= 1; if ( host_io[0] > lleft ) while ( host_io[0] != lleft ) PrintAt (9 + ++lleft,8,"-"); else while ( host_io[0] != lleft ) PrintAt(9 + lleft--,8," "); Fill3(host_io,0,HOST_RECEIVE|PEEK_RIGHT); Dsp_BlkHandshake ( host_io, 1, host_io, 1 ); host_io[0] »= 1; if ( host_io[0] > lright ) while ( host_io[0] != lright ) PrintAt(9 + ++lright,9,"-"); else while ( host_io[0) != lright ) PrintAt(9 + lright--,9," "); fflush( stdout ); } for ( l = 0; l < 60; l++ ) if ( Bconstat( 2 ) ) switch ( toupper((char) Bconin( 2 )) ) { case '\r': c = 0; break; case 'K' : effect ^= KARAOKE; Fill3 (host_io, 0, HOST_SEND|effect|EFFECT); Dsp_BlkHandshake( host_io, 1, NULL, 0 ); break; case '<' : if ( right - 0x80O0L > 0 ) { left += 0x8000L; right -= 0x8000L; goto send_level; } break; case '>' : if ( left - 0x8000L > 0 ) { left -= 0x8000L; right += 0x8000L; send_level: Fill3(host_io,0,HOST_SEND|left|LEVEL_L1); Fill3(host_io,1,HOST_SEND|right|LEVEL_R1); Dsp_BlkHandshake( host_io, 2,NULL, 0 ); } break; case 'M' : left = right = 0x3fff00L; goto send_level; default : break; } } /* setze die Drsprungsdaten */ buffoper( 0 ); soundcmd( ADDERIN, (int) curadder ); soundcmd( ADCINPUT, (int; curadc ); Dsp_Unlock(); unlocksnd(); return ( 0 ); }

Übersicht DSP-Programmierkurs

Teil 1: Register und Befehlssatz des DSP 56001

Teil 2: I/O-Schnittstellen und I/O-Programmierung des DSP 56001 und des MC68030

Teil 3: Programmier-Tools des DSP 56001 (Assembler, Linker, Debugger)

Teil 4: Tips und Tricks mit Programmbeispiel


Jürgen Lietzow
Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]