Verkehrt herum abgespielte Songtexte blicken auf eine lange Tradition zurück. Eine Perl-Applikation steuert mit einer einfachen Curses-GUI die Aufnahme durch ein Mikrofon und spielt das Ergebnis rückwärts ab.
Neulich lauschte ich im Internet-Radio dem Song ``Work It'' von Missie Elliot und stellte fest, dass ich Teile des Refrains trotz ordentlicher Englischkenntnisse seit seinem Erscheinen im Jahre 2002 noch nie recht verstanden hatte. Wikipedia [2] löste das Rätsel, denn die feiste Hip-Hopperin hatte einfach Gesangssequenzen rückwärts eingespielt!
Dieses sogenannte ``Backmasking'' [3] geht bis auf die Beatles zurück, und auch in meiner Jugendzeit war es ein beliebter Zeitvertreib. Denn damals gab es noch keine Killerspiele, und als das pädagogisch wertvolle Holzspielzeug seine Spannung verlor, blieb uns nur, von gutgläubigen Erwachsenen aus der Apotheke geholte Chemikalien zu lautstarken Sprengsätzen zu vermengen. Außerdem hatte es uns ein Cassettenrekorder angetan, den man durch gezielte Manipulation des Antriebs dazu brachte, aufgenommene Bänder verkehrt herum abzuspielen. Man sprach ``Redro Kernettesack!'' aufs Band und aus dem Lautsprecher kam in einem unvorstellbarem Akzent, irgendwo zwischen osteuropäisch und außerirdisch: ``Kkkassettnrekorrdeeer!'' Stundenlanger Spaß für die ganze Familie!
Das Skript in Listing flipit
produziert nach dem Aufruf von der
Kommandozeile die Fußzeile in Abbildung 1 und lädt dazu ein, die
Taste ``R'' (für Record) zu drücken, um eine ins Mikrofon gesprochene
Nachricht aufzunehmen. Während der Aufnahme erscheint das Menü in
Abbildung 2, das darauf hinweist, dass ``S'' (für Stopp) die Aufnahme
beendet und ``P'' (für Play) die aufgenommene .ogg-Datei
verkehrtherum abspielt. Während der Wiedergabe erscheint der Text in Abbildung
3, der sofort wieder dem Menü in Abbildung 1 Platz macht,
falls keine Sounddaten mehr vorliegen.
Abbildung 1: Am Programmstart: Die Taste [r] startet eine Aufnahme. |
Abbildung 2: Während der Aufnahme: [s] beendet die Aufnahme, [p] spielt sie gleich auch noch rückwärts ab. |
Abbildung 3: Mit dem Tool Sox kehrt flipit die Aufnahme um und spielt so das Original rückwärts ab. |
Die minimalistische graphische Oberfläche erzeugt das Modul Curses::UI::POE, das die Grafikroutinen der Curses-Library mit der Multitasking-Umgebung POE verbandelt. Denn während das Skript langwierige Vorgänge wie die Aufnahme einer Sounddatei, deren Umkehrung oder das Abspielen steuert, soll die GUI blitzschnell auf User-Eingaben reagieren. Erfahrene Leser dieser Rubrik wissen, dass die kooperative Multitasking-Umgebung POE dafür die notwendigen Voraussetzungen mitbringt. Sie verfolgt einen asynchronen Ansatz, der Anfängern oft Kopfzerbrechen bereitet. Hat man den Bogen aber erst einmal raus, lassen sich mit ihr schnell robuste Applikationen bauen.
001 #!/usr/local/bin/perl -w 002 use strict; 003 use POE; 004 use POE::Wheel::Run; 005 use Curses::UI::POE; 006 use File::Temp qw(tempfile); 007 use Sysadm::Install qw(:all); 008 use POSIX; 009 010 our $HEAP; 011 012 my $CUI = Curses::UI::POE->new( 013 -color_support => 1, 014 inline_states => { 015 _start => sub { 016 $HEAP = $_[HEAP]; 017 }, 018 play_ended => \&footer_update, 019 }); 020 021 my $WIN = $CUI->add("win_id", "Window"); 022 023 my $FOOT = $WIN->add(qw( bottom Label 024 -y -1 -paddingspaces 1 025 -fg white -bg blue)); 026 027 footer_update(); 028 029 $CUI->set_binding(sub { exit 0; }, "q"); 030 $CUI->set_binding( \&play_flipped, "p"); 031 $CUI->set_binding( \&record, "r" ); 032 $CUI->set_binding( \&record_stop, "s" ); 033 034 $CUI->mainloop; 035 036 ########################################### 037 sub record { 038 ########################################### 039 if(defined $HEAP->{recorder}->{wheel}) { 040 return; # Still recording 041 } 042 043 my($fh, $tempfile) = tempfile( 044 SUFFIX => ".ogg", UNLINK => 1); 045 046 my $wheel = 047 POE::Wheel::Run->new( 048 Program => "rec", 049 ProgramArgs => [$tempfile], 050 StderrEvent => 'ignore', 051 ); 052 053 $HEAP->{recorder} = { 054 wheel => $wheel, 055 file => $tempfile, 056 }; 057 058 $FOOT->text("Recording ... " . 059 "([s] to stop, [p] to play)"); 060 $FOOT->draw(); 061 } 062 063 ########################################### 064 sub record_stop { 065 ########################################### 066 my $wheel = $HEAP->{recorder}->{wheel}; 067 068 return if !defined $wheel; 069 070 $wheel->kill(SIGTERM); 071 delete $HEAP->{recorder}->{wheel}; 072 footer_update(); 073 } 074 075 ########################################### 076 sub footer_update { 077 ########################################### 078 my $text = "[r] to record"; 079 080 if(defined $HEAP->{recorder}->{file}) { 081 $text .= ", [p] to play"; 082 } 083 084 $text .= ", [q] to quit"; 085 086 $FOOT->text($text); 087 $FOOT->draw(); 088 } 089 090 ########################################### 091 sub play_flipped { 092 ########################################### 093 if(defined $HEAP->{recorder}->{wheel}) { 094 # Active recording? Stop it. 095 record_stop( $HEAP ); 096 } 097 098 $FOOT->text("Playing ..."); 099 $FOOT->draw(); 100 101 my $recorded = $HEAP->{recorder}->{file}; 102 103 return if ! defined $recorded; 104 105 my $wheel = 106 POE::Wheel::Run->new( 107 Program => \&sox_play, 108 ProgramArgs => [$recorded], 109 StderrEvent => 'ignore', 110 CloseEvent => 'play_ended', 111 ); 112 113 $HEAP->{players}->{$wheel->ID} = $wheel; 114 } 115 116 ########################################### 117 sub sox_play { 118 ########################################### 119 my($recording) = @_; 120 121 my($fh, $tmpfile) = 122 tempfile(SUFFIX => ".ogg"); 123 124 tap "sox", $recording, 125 $tmpfile, "reverse"; 126 tap "play", $tmpfile; 127 128 unlink $tmpfile; 129 }
Der Konstruktoraufruf in Zeile 13 schaltet mit color_support
Terminalfarben ein und bestimmt zwei POE-typische Zustände. Den ersten,
_start
, springt POE sofort nach dem Start des POE-Kernels an,
praktisch als Mutter aller Zustände des Zustandsautomaten. Zeile 16
nutzt ihn nur dazu, eine Referenz auf die ebenfalls POE-typische Datenstruktur
eines Session-Heaps in der globalen
Variablen $HEAP abzulegen, damit der als Variablenspeicher genutzte
Hash auch außerhalb der POE-Welt zugänglich ist. Den zweiten
Zustand, play_ended
, springt POE an, falls ein aufgenommener
Sound erfolgreich rückwärts abgespielt wurde. Für diesen Fall
definiert Zeile 18 den Handler footer_update()
, der den Text
auf dem Fußbalken dem Status des Apparats anpasst.
Die GUI setzt sich aus einem Hauptfenster $WIN und einer Fußzeile $TOP
zusammen.
Das Hauptfenster ruft Zeile 21
durch die Methode add()
des Moduls Curses::UI::POE ins Leben.
Letzteres stellt lediglich zu
Curses::UI, genauer gesagt, Curses::UI::Container durch. Der
erste Parameter ist die für das Window gewünschte ID (im Skript
auf "win_id"
gesetzt), und der zweite Parameter, "Window"
,
bestimmt die Klasse des neu erzeugten Widgets.
Das zweite Widget, die Fußzeile mit den Instruktionen für gerade
erlaubte User-Aktionen, ensteht in Zeile 23. GUI-typisch erzeugt
das Parent-Widget $WIN durch einen Aufruf der Methode add()
das
in ihm liegende Fußzeilenwidget. Mit -fg white
und -bg blue
legt es Weiße Schrift (Vordergrund) auf blauem Hintergrund fest.
Der erste Parameter "bottom"
ist die gewünschte ID des neu
erzeugten Fensters, der zweite, ``Label'', die Widget-Klasse. Der
Wert -1
für den Parameter ``-y'' gibt an, dass das Label ganz unten
am Fensterrand zu platzieren ist. Die Option -paddingspaces
dehnt
das Label bis an die horizontalen Grenzen des einschließenden
Fensters aus.
Das Label
verfügt über eine text()-Methode, die den auf
der Fußzeile zu sehenden Text löscht und setzt. Die in Zeile 27
aufgerufene Funktion footer_update()
frischt die neu definierte
und vorerst leere Fußzeile erstmals mit dem im Startzustand möglichen
User-Kommandos auf.
Die Zeilen 29 bi 32 definieren, was passiert, wenn der User die Tasten
"q"
(Quit), "p"
(Play), "r"
(Record) und "s"
(Stop Recording) drückt.
Bei ``q'' ruft flipit
die Funktion exit()
auf, die das Programm abbricht.
Die GUI fällt dabei sauber in sich zusammen.
Die zugewiesenen
Handlerfunktionen play_flipped
(Audiodatei verkehrt herum
abspielen), record()
(Aufnahme starten) und record_stop()
(Aufnahme anhalten) definiert das Skript weiter unten. Der Einfachheit
halber greifen sie alle auf den globalen $HEAP zu, und, indirekt über die
Funktion footer_update(), auf die ebenfalls globalen Widget-Variablen.
GUI-typisch springt das Programm dann in Zeile 34 in die
Haupteventschleife, aus der es nie mehr austritt, und solange
Usereingaben
verarbeitet, bis jemand ``q'' drückt und damit
den Teppich unter der GUI wegzieht.
Drückt der User ``r'', springt die GUI die Funktion record()
ab Zeile 37
an. Diese prüft zunächst, ob bereits eine Aufnahme läuft, und bricht
in diesem Fall mit return
ab, ignoriert also den Tastendruck.
Falls nicht, legt die Funktion tempfile()
aus dem Modul File::Temp
eine temporäre Datei mit der Endung ``.ogg'' an, die Dank der
gesetzten UNLINK
-Option von Perls automatischer Abrißbirne
bei Programmende wegefegt wird. Der Suffix ist wichtig, denn das
nachfolgend aufgerufene Tool sox
schließt daraus auf das in
der Audiodatei zu verwendende Kodierungsverfahren. Externe Programme
startet in POE ein Objekt der Klasse POE::Wheel::Run, das die GUI
ruckellos weiterlaufen lässt und bei Bedarf sogar Aktionen auslöst,
wenn das losgelassene Programmkind sich verabschiedet. Das Wheel
in Zeile 47 ignoriert allerdings die Events, die bei Ausgaben
nach STDERR auftreten (StderrEvent), und nur der User kann
den Lauf des Rekorders beenden. Das aufgerufene Programm rec
liegt dem
sox
-Paket bereits bei (genau wie die später genutzte Utility play
)
und nimmt nur den Namen der zu erstellenden Audiodatei entgegen.
Es liest Audiodaten über ein eingebautes Laptop-Mikrofon oder ein
extern an die Soundkarte angestöpseltes.
Zu beachten ist, dass POE::Wheel::Run das aufzurufende Programm
und dessen Parameterliste von einander getrennt in den Parametern
Program
und ProgramArgs
erwartet.
Der Code ab Zeile 46 hält die GUI übrigens ganz und gar nicht auf,
alle notwendigen Aktionen geschehen im Hintergrund. Damit das Wheel
nicht nach dem Verlassen der Funktion record
seine letzte Referenz
verliert und Perls Garbage-Collector zum Opfer fällt, speichert
Zeile 53 eine Referenz darauf unter dem Schlüssel recorder
im
POE-spezifischen $HEAP ab. Weiter sichert es dort den Namen der
temporären Datei, damit die Abspielfunktion später darauf zugreifen kann.
Am Ende frischt record()
noch die Fußzeile auf, um dem User
mitzuteilen, dass er mit ``s'' stoppen und mit ``p'' stoppen und den
Abspielvorgang einleiten kann.
Bei ``s'' kommt die Funktion record_stop
an die Reihe, und prüft zunächst,
ob überhaupt ein Wheel läuft. Falls nicht, hat ein wohl ein
Übermütiger ``s'' gedrückt, ohne dass überhaupt eine Aufnahme lief.
Zeile 70 schießt das laufende ``rec''-Programm dann mit dem über
das POSIX-Modul hereingeholte SIGTERM
-Signal ab, und im Anschluß
streicht Zeile 71 die Referenz des soeben (hoffentlich) ruckartig
beendeten Wheels aus dem $HEAP.
Die Funktion footer_update()
frischt mit der text()-Methode
die Fußzeile auf und schickt ein draw()
hinterher, damit die
GUI sich auch veranlasst fühlt, das Widget neu auf den Bildschirm
zu zeichnen. Der Abspiel-Handler play_flipped
stoppt zunächst
etwaige laufende Aufnahmen und ruft dann in Zeile 105 das
Abspiel-Wheel auf. Dieses definiert einen CloseEvent
, den in Zeile
18 definierten POE-Zustand play_ended
anspringt, sobald die
vom Wheel aufgerufene
Funktion sox_play()
(ab Zeile 117) zurückkehrt. Auch hier vertrödelt
POE keine Zeit, sondern führt sox_play()
asynchron aus und
kommuniziert mit ihren Ausgabe-, Fehler- und Ende-Events.
Damit auch dieses Wheel nicht in sich zusammenfällt, wenn das Skript
den Scope der Funktion
play_flipped
verlässt (was wegen des asynchronen Aufrufs noch passiert,
bevor das Wheel die externe Funktion überhaupt startet), speichert
Zeile 113 eine Referenz darauf im $HEAP ab.
Jedes Wheel hat innerhalb einer POE-Session eine eindeutige ID und
da play_flipped()
die Referenz unter der ID abspeichert, könnte der
User auch mehrere Abspielvorgänge quasi parallel starten. Wer dies
ausprobieren möchte, darf gerne mal drei, vier Mal kurz hintereinander
die Taste ``p'' drücken.
Die Funktion
sox_play
legt eine weitere temporäre, zunächst leere .ogg-Datei an
und übergibt sie dem mit der Funktion tap
aus dem CPAN-Modul
Sysadm::Install aufgerufene Kommando der Utility "sox"
:
sox input.ogg output.ogg reverse
Die nachgestellte Option reverse
weist sox
an, die Eingabedatei
nicht einfach in die Ausgabedatei zu überführen, sondern sie
dabei auch noch zeitverkehrt umzudrehen. Die Ergebnisdatei schnappt
sich dann die Sox-Utility play
in Zeile 126 und spielt sie über
die Soundkarte des verwendeten PCs ab. Die umgedrehte Datei löscht
Zeile 128 anschließend, denn ein erneuter Aufruf von sox_play
legt eine neue an.
Damit rec
das richtige Mikrofon anspricht, um die eintrudelnden
Audiodaten einzulesen, war es bei dem im Testlabor verwendeten
Netbook notwendig, die
Audio-Utility alsamixer
aufzurufen. Die Startseite zeigt
die Playback
-Parameter an, die sich auf die Datenausgabe beziehen
(Abbildung 4). Die Funktionstaste F4 schaltet in den Capture-Modus um,
der die Mikrofoneinstellungen kontrolliert (Abbildung 5). Kommt ein
eingestöpseltes externes Mikrofon zum Einsatz, muss der Eintrag
in der Rubrik Input So
auf ``Mic'' stehen. Reicht das qualitativ
ausreichende eingebaute Netbook-Mikro, wird mit den Cursortasten
im alsamixer ``Front Mic'' eingestellt. Der Regler ``Digital'' bestimmt die
Empfindlichkeit. Mit der ``Esc''-Taste verlässt der User den
alsamixer wieder, die eingestellten Änderungen bleiben permanent.
Abbildung 4: Playback-Modus nach dem Start des alsamixer. |
Abbildung 5: Die Taste [F4] schaltet in den Capture-Modus um. Die linke "Input So" muss auf "Front Mic" stehen, um das eingebaute Mikrofon des Netbooks zu aktivieren. Der Regler "Digital" bestimmt die Empfindlichkeit. |
Die Module POE, POE::Wheel::Run, Curses::UI::POE und Sysadm::Install liegen, samt ihrer Abhängigkeiten, teilweise schon neueren Linux-Distributionen bei oder lassen sich über eine CPAN-Shell installieren. Als besonderen Leserservice bedient auf [4] der Perlmeister in einem Lehrvideo höchstpersönlich das Skript auf einem kleinen Netbook und versucht sich daran, das Wort ``Linux Magazine'' auf Rückwärts-Englisch aufzunehmen, um es dann von flipit richtig herum abspielen zu lassen. Ein Riesenspaß für Groß und Klein!
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2010/03/Perl
``Work It'', Song von ``Missy Elliott'', http://en.wikipedia.org/wiki/Work_It_(Missy_Elliott_song)
``Backmasking'', die Technik, Songsequenzen rückwärts abzuspielen.
Michael Schilli demonstriert Aufnahmen mit dem Skript flipit
:
http://www.youtube.com/watch?v=LdSTIa2Tx4o
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |