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.
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.
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).
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.
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“).
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 |
14 | SSI |
15 | SSI |
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
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 );
}
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