#!/usr/bin/perl


# ------------------------------------------------------------------------------------------------
#
# Copyright © 2011, Intel Corporation
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted
# provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this list of conditions
#   and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, this list of
#   conditions and the following disclaimer in the documentation and/or other materials provided
#   with the distribution.
# * Neither the name of Intel Corporation nor the names of its contributors may be used to endorse
#   or promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# ------------------------------------------------------------------------------------------------
#
# Project     : Intel® Leibniz Challenge 2011
# File name   : WAV_Morsedecode_Lösung.pl
# Author      : mbarsnic
# Date        : 2011-04-12
# Revision    : 6.00


use warnings;
use strict;
use Fcntl; # für Modus (O_RDONLY) bei sysopen()
# für Zwischenablage in Temporärdatei, wird aber potentiell am Ende gelöscht, schlecht zum Debuggen
# use File::Temp; 

# Wenn die Tonfolge als WAV aufgenommen oder zu WAV gewandelt wird,
# bitte drauf achten, dass ein unkomprimiertes Format ("PCM") gewählt
# wird. Es darf ein oder zwei Kanäle haben ("mono" bzw. "stereo"), wir
# werten es in beiden Fällen als ein Gesamtsignal aus. Die "Abtastwerte"
# dürfen 8 oder 16 Bits groß sein, 16 sind aber definitiv zu empfohlen,
# aus Gründen der Genauigkeit.
# Ein übliches Format ist "CD-Qualität": Zwei Kanäle, 16 Bits pro
# Abtastwert, 44100 Abtastwerte pro Sekunde.

# Auf die Daten könnte mit dem Perl-Modul "Audio::Wav" recht einfach
# zugegriffen werden. Wer sich die Einbindung dieses Perl-Moduls zutraut,
# soll es gerne verwenden. Wir machen es hier ohne.
# Siehe dazu
# http://search.cpan.org/dist/Audio-Wav/

# Variablen, die als globale auch in den Unterfunktionen verwendet werden
my $dateiname;
# ein Array für alle Werte kann speicherintensiv sein:
# my @abtastwerte;
my $abtastwerte_dateiname = "abtastwerte.txt";
my ($format_string, $format_groesse, $immer, $kanaele, $abtastrate,
    $bytes_pro_sekunde, $bytes_pro_abtastung, $bits_pro_abtastung);
my ($daten_string, $daten_laenge);
my $datenbreite;
my $datenformat;

my @anaus; # Liste der Ton-an- und Ton-aus-Zeiten mit Typ und Länge (Hash)
my @toene; # Liste von lang/kurz und Art der Unterbrechungen
my @morsebuchstaben; # Liste der dekodierten Buchstaben und Wort-Lücken
my $morsetext = ""; # das Ergebnis als zusammenhängender Text

my %buchstabe =
(
  '.-'          => 'A',
  '-...'        => 'B',
  '-.-.'        => 'C',
  #####################
  # hier fehlen jede Menge Zeichen, von D bis Y, Ziffern, Sonderzeichen,
  # zu ergänzen in Aufgabenteil 2.4.1.
  #####################
  '-..'         => 'D',
  '.'           => 'E',
  '..-.'        => 'F',
  '--.'         => 'G',
  '....'        => 'H',
  '..'          => 'I',
  '.---'        => 'J',
  '-.-'         => 'K',
  '.-..'        => 'L',
  '--'          => 'M',
  '-.'          => 'N',
  '---'         => 'O',
  '.--.'        => 'P',
  '--.-'        => 'Q',
  '.-.'         => 'R',
  '...'         => 'S',
  '-'           => 'T',
  '..-'         => 'U',
  '...-'        => 'V',
  '.--'         => 'W',
  '-..-'        => 'X',
  '-.--'        => 'Y',
  '--..'        => 'Z',
  '-----'       => '0',
  '.----'       => '1',
  '..---'       => '2',
  '...--'       => '3',
  '....-'       => '4',
  '.....'       => '5',
  '-....'       => '6',
  '--...'       => '7',
  '---..'       => '8',
  '----.'       => '9',
  '.-.-.-'      => '.',
  '--..--'      => ',',
  '..--..'      => '?',
  '.----.'      => '\'',
  '-.-.--'      => '!',
  '-..-.'       => '/',
  '-.--.'       => '(',
  '-.--.-'      => ')',
  '---...'      => ':',
  '-.-.-.'      => ';',
  '-...-'       => '=',
  '.-.-.'       => '+',
  '-....-'      => '-',
  '..--.-'      => '_',
  '.-..-.'      => '"',
  '...-..-'     => '$',
  '.--.-.'      => '@',
  # sog. "prosigns", http://en.wikipedia.org/wiki/Prosigns_for_Morse_code
  '-.-.-'       => '(Beginn)',
  '.-.-.'       => "(Ende)\n",
  '......'      => '(Fehler)',
  '.......'     => '(Fehler)',
  '........'    => '(Fehler)',
  # Sonderfall für unser Leerzeichen (existiert leider nicht explizit im
  # Morsecode-Alphabet)
  "Wortpause"   => ' ',
);

###################
# Unterfunktionen #
###################

sub formatermittlung()
{
  # Als erstes wird der Header eingelesen. Dieser enthält wichtige
  # Informationen darüber, wie die Daten formatiert sind.

  # die 44 Header-Bytes, im allgemeinen Fall:
  my $riff_header;
  sysread WAVDATEI, $riff_header, 12;
  my $format_header;
  sysread WAVDATEI, $format_header, 24;
  my $daten_header;
  sysread WAVDATEI, $daten_header, 8;

  # RIFF-Header:
  # ASCII-String "RIFF", Länge des Inhalts als 32-Bit-little-endian-Zahl, ASCII-String "WAVE"
  my ($riff_string, $riff_laenge, $riff_typ) = unpack "A4VA4", $riff_header;

  # erstmal prüfen, ob das Format laut Header passt
  if ($riff_string ne "RIFF")
  {
    die "RIFF-Header ist nicht \"RIFF\", sondern \"$riff_string\"";
  }
  if ($riff_typ ne "WAVE")
  {
    die "RIFF-Typ ist nicht \"WAVE\", sondern \"$riff_typ\"";
  }
  # print "RIFF-Länge: $riff_laenge\n";

  # Format-Header:
  ($format_string, $format_groesse, $immer, $kanaele, $abtastrate, $bytes_pro_sekunde,
      $bytes_pro_abtastung, $bits_pro_abtastung) = unpack "A4VvvVVvv", $format_header;
  # rein interessehalber:
  print "Dateiname      : \"$dateiname\"\n";
  print "Format-Header  : \"$format_string\"\n";
  print "Format-Größe   : $format_groesse Bytes\n"; # Zahl der nachfolgenden Bytes in diesem Header
  print "Immer          : $immer\n"; # keinen Schimmer, wofür das gut ist :-/
  print "Kanäle         : $kanaele\n";
  print "Abtastrate     : $abtastrate 1/s\n";
  print "Bytes/s        : $bytes_pro_sekunde\n";
  print "Bytes/Abtastung: $bytes_pro_abtastung\n";
  print "Bits/Abtastung : $bits_pro_abtastung\n";

  # prüfen, ob wir das Format sinnvoll verarbeiten können
  if (($kanaele != 1) && ($kanaele != 2))
  {
    die "Nur ein oder zwei Kanäle sinnvoll, $kanaele gefunden";
  }
  if (($bits_pro_abtastung != 8) && ($bits_pro_abtastung != 16))
  {
    # ehrlich gessgt könnten wir wohl auch 12-Bit-Abtastwerte verarbeiten, aber es muss nicht sein
    die "Nur 8 oder 16 bits pro Abtastung sinnvoll, $bits_pro_abtastung gefunden";
  }

  # Daten-Header:
  ($daten_string, $daten_laenge) = unpack "A4V", $daten_header;
  # rein interessehalber:
  print "Daten-Header   : \"$daten_string\"\n";
  print "Daten-Länge    : $daten_laenge Bytes\n";

  # Jetzt sollten anschließend $daten_laenge Bytes Daten vorliegen.
  # Die Bytes sind folgendermaßen angeordnet:
  # [Kanal 1, 1. Abtastung], [Kanal 2, 1. Abtastung],
  # [Kanal 1, 2. Abtastung], [Kanal 2, 2. Abtastung],
  # ...

  # Das Format für die Funktion unpack() für spätere Verwendung festlegen
  if ($bits_pro_abtastung == 8)
  {
    $datenbreite = "c"; # 8 Bits, vorzeichenbehaftet
  }
  else
  {
    $datenbreite = "s"; # 16 Bits, vorzeichenbehaftet
  }
  if ($kanaele == 1)
  {
    $datenformat = "$datenbreite";
  }
  else
  {
    # zwei Abtastwerte der Art $datenbreite zusammengefügt
    $datenformat = "$datenbreite$datenbreite";
  }
}

sub audiodatenaquise()
{
  #############################
  # Hier lesen wir eine gesamte Abtastung (alle Bytes aller Kanäle, also 1 bis 4)
  # in eine Variable ein, und das solange, bis keine Bytes mehr in der Datei sind.
  #############################

  my $datenhaufen; # ein "Haufen" Bytes je Abtastwert
  my $abtastnummer = 0;

  open(ABTASTWERTE, ">", $abtastwerte_dateiname) or die "Konnte \"$abtastwerte_dateiname\" nicht zum Schreiben öffnen";

  while ($bytes_pro_abtastung == sysread(WAVDATEI, $datenhaufen, $bytes_pro_abtastung))
  {
    my $abtastwert;
    if ($kanaele == 2)
    { # 2 Kanäle, "stereo", zu einem Wert zusammenfügen (Mittelwert)
      my ($links, $rechts) = unpack $datenformat, $datenhaufen;
      $abtastwert = int(($links + $rechts) / 2);
      # print "Links $links, rechts $rechts\n";
    }
    else
    {
      $abtastwert = unpack $datenformat, $datenhaufen;
    }
    # print "Abtastwert: $abtastwert ($abtastnummer)\n";
    # in ein Array:
    # push @abtastwerte, $abtastwert;
    # in eine Datei:
    print ABTASTWERTE $abtastwert . "\n";
    $abtastnummer++;
    # gelegentlich Fortschritt anzeigen
    if (($abtastnummer % 100) == 0)
    {
      printf "\rAbtastung Nummer %9d ...", $abtastnummer;
    }
  }
    print "\n";

  close ABTASTWERTE;

  # Zusamenfassung der Audiodatenaquise:
  print "Abtastwerte    : $abtastnummer gelesen\n";
  # Gegenkontrolle gegen Header-Angaben:
  if (($abtastnummer * $bytes_pro_abtastung) != $daten_laenge)
  {
    print "WARNUNG: Zahl der gelesenen Abtastwerte $abtastnummer und Datenlänge $daten_laenge Bytes passen nicht zusammen!\n";
  }
}


sub anausdetektion()
{
  #############################
  # Jetzt gehen wir durch die Abtastwerte, die einen modulierten Ton darstellen,
  # und versuchen, die Zeitpunkte von "Ton an" und "Ton aus" zu detektieren.
  #############################

  # einen sinnvollen Schwellwert definieren
  # Annahme 1: die Aufnahme ist nicht zu laut, nicht zu leise, kaum verrauscht
  # Achtung: bei 16-Bit-Auflösung ist der Wertebereich -32768 bis 32768
  #          bei  8-Bit-Auflösung ist der Wertebereich -128 bis 127
  # 5 oder 10 % des Maximalwerts erschien sinnvoll, aber 2% tun es oft viel besser
  my $schwellwert = int(0.02 * 2**($bits_pro_abtastung-1));
  print "Schwellwert: festgelegt als $schwellwert (von " . 2**($bits_pro_abtastung-1) . ")\n";
  # Annahme 2: die Frequenz ist ca. 1 kHz
  # bei 16..48kHz Abtastung hieße das Stille müsste 48 Werte lang bestehen
  my $min_aus = int($abtastrate / 1000);
  my $min_an = int($min_aus / 10);
  # Die Morse-Hardware generiert ca.:
  # schnell:
  #   lang 150 ms, kurz 50 ms, Lücke 50 ms, Buchstaben-Pause 150 ms, Wort-Pause 350
  # langsam:
  #   lang 600 ms, kurz 200 ms, Lücke 200 ms, Buchstaben-Pause 600 ms, Wort-Pause 1400

  my $zaehler = 0;
  my $an_zaehler = 0;
  my $aus_zaehler = 0;
  my $ton_zustand = "aus"; # "an", "aus", "geht_an", "geht_aus"
  my %zeit;
  $zeit{'aus'} = 0; # zu Anfang erstmal aus

  open(ABTASTWERTE, "<", $abtastwerte_dateiname);

  while (<ABTASTWERTE>)
  {
    my $wert = $_;
  #  print "DEBUG: Zeit $zaehler, Zustand $ton_zustand\n";

    # Idee: Ton erkennen (über Schwellwert)
    # Wenn genügend Abtastwerte in Folge über dem Schwellwert erkannt
    # werden. ist es definitiv an.
    # Wenn genügend Abtastwerte in Folge unter dem Schwellwert erkannt
    # werden, ist es Stille.

    # Zustandmaschine um $ton_zustand
    if ($ton_zustand eq 'an')
    {
      if (abs($wert) < $schwellwert) # unterhalb der Schwelle
      {
        $ton_zustand = 'geht_aus';
        # print "DEBUG: Zeit $zaehler, Zustand $ton_zustand\n";
        $zeit{'geht_aus'} = $zaehler;
        $aus_zaehler = 1;
      }
    }
    elsif ($ton_zustand eq 'aus')
    {
      if (abs($wert) >= $schwellwert) # oberhalb der Schwelle
      {
        $ton_zustand = 'geht_an';
        # print "DEBUG: Zeit $zaehler, Zustand $ton_zustand\n";
        $zeit{'geht_an'} = $zaehler;
        $an_zaehler = 1;
      }
    }
    elsif ($ton_zustand eq 'geht_an')
    {
      if (abs($wert) >= abs($schwellwert)) # oberhalb der Schwelle
      {
        if ($an_zaehler < $min_an)
        {
          # verbleibt im gleichen Zustand
          $an_zaehler++;
        }
        else
        {
          # lange genug Amplitude gesehen, jetzt ist es halt "an"
          $ton_zustand = 'an';
          # print "DEBUG: Zeit $zaehler, Zustand $ton_zustand\n";
          $zeit{'an'} = $zeit{'geht_an'};
          my $dauer = $zeit{'an'} - $zeit{'aus'};
          push @anaus, { 'anaus' => 'aus', 'dauer' => $dauer };
          # print "DEBUG: aus für $dauer\n";
        }
      }
      else
      {
        # ist nun doch nicht angegangen, also zurück zu 'aus'
        $ton_zustand = 'aus';
        # print "DEBUG: Zeit $zaehler, Zustand $ton_zustand\n";
      }
    }
    elsif ($ton_zustand eq 'geht_aus')
    {
      if (abs($wert) < abs($schwellwert)) # unterhalb der Schwelle
      {
        if ($aus_zaehler < $min_aus)
        {
          # verbleibt im gleichen Zustand
          $aus_zaehler++;
        }
        else
        {
          # lange genug keine Amplitude gesehen, jetzt ist es halt "aus"
          $ton_zustand = 'aus';
          $zeit{'aus'} = $zeit{'geht_aus'};
          my $dauer = $zeit{'aus'} - $zeit{'an'};
          push @anaus, { 'anaus' => 'an', 'dauer' => $dauer };
          # print "DEBUG: an für $dauer\n";
        }
      }
      else
      {
        # ist nun doch nicht ausgegangen, also zurück zu 'an'
        $ton_zustand = 'an';
        # print "DEBUG: Zeit $zaehler, Zustand $ton_zustand\n";
      }
    }
    else
    {
      die "Ungültiger Ton-Zustand \"$ton_zustand\"";
    }
    $zaehler++;
  }
}


sub anauszulangkurz()
{
  #############################
  # Jetzt nehmen wir die An-/Aus-Zeiten des Piep-Tons und
  # ermittlen daraus lang und kurz (für Ton und für Pause)
  #############################
  # Es gibt nicht soviele Daten im @anaus, wir können ruhig mehrmals
  # drüberiterieren

  # erstmal die Verteilung der An-Zeiten
  # damit können wir vllt. langsamen vs. schnellen Modus erkennen
  my $min_anzeit = 2**31;
  my $max_anzeit = 0;
  my $min_auszeit = 2**31;
  my $max_auszeit = 0;
  my $erstes_aus = 1; # das erste wollen wir ignorieren
  foreach my $anauswert (@anaus)
  {
    my $dauer = ${$anauswert}{'dauer'};
    if (${$anauswert}{'anaus'} eq 'an')
    {
      if ($dauer < $min_anzeit)
      {
        $min_anzeit = $dauer;
      }
      if ($dauer > $max_anzeit)
      {
        $max_anzeit = $dauer;
      }
    }
    else
    {
      if ($erstes_aus == 1)
      {
        $erstes_aus = 0;
      }
      else
      {
        if ($dauer < $min_auszeit)
        {
          $min_auszeit = $dauer;
        }
        if ($dauer > $max_auszeit)
        {
          $max_auszeit = $dauer;
        }
      }
    }
  }
  print "Minimale An-Dauer : $min_anzeit (" . $min_anzeit / $abtastrate * 1000 . " ms)\n";
  print "Maximale An-Dauer : $max_anzeit (" . $max_anzeit / $abtastrate * 1000 . " ms)\n";
  print "Minimale Aus-Dauer: $min_auszeit (" . $min_auszeit / $abtastrate * 1000 . " ms)\n";
  print "Maximale Aus-Dauer: $max_auszeit (" . $max_auszeit / $abtastrate * 1000 . " ms)\n";

  my @an_schwellen;
  my @aus_schwellen;


  if (($max_anzeit / $abtastrate > 0.3) && ($max_auszeit / $abtastrate > 0.7)
      &&
      ($min_anzeit / $abtastrate > 0.1) && ($min_auszeit / $abtastrate > 0.1))
  {
    # langsam
    print "Langsamer Morse-Modus detektiert\n";
    @an_schwellen  = (50, 400);       # für 200, 600 ms
    @aus_schwellen = (50, 400, 1000); # für 200, 600, 1400 ms
  }
  elsif (($max_anzeit / $abtastrate < 0.3) && ($max_auszeit / $abtastrate < 0.7)
        &&
       ($min_anzeit / $abtastrate < 0.1) && ($min_auszeit / $abtastrate < 0.1))
  {
    # schnell
    print "Schneller Morse-Modus detektiert\n";
    @an_schwellen  = (20, 100);      # für 50, 150 ms
    @aus_schwellen = (20, 100, 250); # für 50, 150, 350 ms
  }
  else
  {
    # mehrdeutig - aber sind wir zu streng?
    warn "kein bestimmter Morse-Modus detektiert\n";
    # nehmen wir der Form halber einfach mal den schnellen Modus an
    @an_schwellen  = (20, 100);      # für 50, 150 ms
    @aus_schwellen = (20, 100, 250); # für 50, 150, 350 ms
  }

  # anhand dieser Erkenntnis können wir jetzt die einzelnen An-Aus-Phasen einordnen
  # an (@an_schwellen):
  # t < A: oh, sehr kurz (nicht gesondert auswerten)
  # A < t < B: kurz
  # B < t: lang
  # aus (@aus_schwellen):
  # t < C: oh, sehr kurz (nicht gesondert auswerten)
  # C < t < D: kurz (Tonabstand)
  # D < t < E: mittel (Buchstabenabstand)
  # E < t < F: lang (Wortpause)
  my $voriges_an = 'an'; # weil wir erstmal ein 'aus' erwarten

  foreach my $anauswert (@anaus)
  {
    my $an = ${$anauswert}{'anaus'};

    # diesen Fehler verhindern wir eigentlich bei anausdetektion():
    if ($an eq $voriges_an)
    {
      die "Nach 'an' sollte 'aus' kommen, die letzten zwei Werte waren '$voriges_an' und '$an'";
    }

    my $dauer = ${$anauswert}{'dauer'} / $abtastrate * 1000; # ms
    # print "DEBUG: Dauer $dauer\n";
    if ($an eq 'an')
    {
      if ($dauer < $an_schwellen[1])
      {
        # kurzer Ton
        push @toene, 'kurz';
      }
      else
      {
        # langer Ton
        push @toene, 'lang';
      }
    }
    else
    {
      if ($dauer < $aus_schwellen[1])
      {
        # kurze Lücke
      }
      elsif (($aus_schwellen[1] <= $dauer) && ($dauer < $aus_schwellen[2]))
      {
        # mittlere Lücke
        push @toene, 'buchstabenpause';
      }
      else
      {
        # Wortlücke
        push @toene, 'wortpause';
      }
    }
    $voriges_an = $an;
  }
  # print "Töne: " . join(", ", @toene) . "\n";
}


sub langkurzzumorsebuchstaben()
{
  #############################
  # Jetzt nehmen wir die Tonfolge und ermitteln Morse-Buchstaben
  #############################
  my $tonstring = "";
  # print "DEBUG: \@toene = " . join (",", @toene) . "\n";
  foreach my $ton (@toene)
  {
    if ($ton eq 'lang')
    {
      $tonstring .= "-";
    }
    elsif ($ton eq 'kurz')
    {
      $tonstring .= ".";
    }
    elsif ($ton eq 'buchstabenpause')
    {
      if ($tonstring ne "")
      {
        push @morsebuchstaben, $tonstring;
        $tonstring = ""; # zurück setzen
      }
    }
    elsif ($ton eq 'wortpause')
    {
      if ($tonstring ne "")
      {
        push @morsebuchstaben, $tonstring;
        push @morsebuchstaben, "Wortpause"; # gesondert markieren
        $tonstring = ""; # zurück setzen
      }
    }
    else
    {
      die "Unbekannter Ton \"$ton\" in \@toene";
    }
  }
  # verbliebene Töne ohne abschließende Pause?
  if ($tonstring ne "")
  {
    push @morsebuchstaben, $tonstring;
  }
}


sub morsebuchstabenzutext()
{
  #############################
  # Hier soll das Array @morsebuchstaben Zeichen für Zeichen zu einem
  # String $morsetext zusammengesetzt werden.
  # Aufgabenteil 2.4.3
  #############################
  # print "DEBUG: \@morsebuchstaben = " . join (", ", @morsebuchstaben) . "\n";
  foreach my $morse (@morsebuchstaben)
  {
    $morsetext .= $buchstabe{$morse} || "";
  }
  print "Ergebnis als Text:\n$morsetext\n";
}


sub textzuzahlenreihen()
{
  # Gemäß Datenlogger-Beschreibung umzusetzen
  # Hier sollte $morsetext zerlegt werden, damit statt Text aus Buchstaben
  # tatsächlich Zahlenreihen herauskommen.
  my $morsetextzeile = "";
  my @morsezeilen;
  my $zahldermesswerte;

  # nochmal zeilenweise zusammensetzen
  foreach my $morse (@morsebuchstaben)
  {
    my $zeichen = $buchstabe{$morse};
    # "Beginn" wollen wir nicht sehen
    if (!defined $zeichen)
    {
      warn "Kein sinnvolles Zeichen zu \"$morse\" gefunden";
    }
    elsif ($zeichen =~ /Beginn/)
    {
      $zeichen = '';
    }
    elsif ($zeichen =~ /Ende/)
    {
      # ablegen im Array
      push @morsezeilen, $morsetextzeile;
      $morsetextzeile = "";
    }
    else
    {
      $morsetextzeile .= $buchstabe{$morse};
    }
  }
  # print "DEBUG: \@morsezeilen:\n" . join("\n", @morsezeilen) . "\n";

  if (scalar @morsezeilen < 1)
  {
    die "Keine Morsezeilen gefunden";
  }
  # die erste Zeile gesondert behandeln
  my $zeile = shift @morsezeilen;
  if ($zeile !~ m/ILC/)
  {
    warn "Keine korrekte Kopfzeile gefunden";
  }
  if ($zeile !~ m/\s(\d+)\s+ENTRIES/)
  {
    warn "Keine Messwertanzahl (\"ENTRIES\") in Kopfzeile gefunden";
  }
  else
  {
    $zahldermesswerte = $1;
  }

  print "Zahl der gesendeten Messwerte: $zahldermesswerte\n";

  my @messwerte;

  foreach my $zeile (@morsezeilen)
  {
    my @woerter = split / /, $zeile;
    my $wortanzahl = scalar @woerter;
    # weniger als zwei Wörter: ergibt keinen Sinn
    if ($wortanzahl < 2)
    {
      warn "Merkwürdige Zeile: \"$zeile\"";
    }
    # weniger Messwerte als (verbleibend) erwartet
    if ($wortanzahl < $zahldermesswerte + 1)
    {
      warn "Nicht genügend Messwerte in Zeile: \"$zeile\", " .
           $wortanzahl -1 . " gefunden, " . $zahldermesswerte . " erwartet";
    }

    my $wort = shift @woerter;
    # Zeilenzahl steht am Anfang der gesendeten Ergebniszeile
    if ($wort =~ /\D/)
    {
      warn "Keine korrekte Zeilenzahl am Anfang der Zeile gefunden: \"$wort\"";
    }
    my $zeilenzahl = scalar $wort;

    foreach $wort (@woerter)
    {
      # wenn das Wort nicht nur aus Ziffern besteht, ist irgendwas faul
      if ($wort =~ /\D/)
      {
        warn "Keinen korrekten Messwert erkannt: \"$wort\"";
      }
      # diese Messwertzeichenkette als Zahl interpretieren
      push @messwerte, scalar $wort;
      # Zahl der verbleibenden Messwerte runtersetzen ("mitzählen")
      $zahldermesswerte--;
    }
  } # foreach my $zeile

  # irgendwie geartete Ausgabe:
  my $zaehler = 1;
  foreach my $messwert (@messwerte)
  {
    print "Messwert Nr. $zaehler:\t$messwert\n";
    $zaehler++;
  }
}




########
# MAIN #
########
# Haupt-Programmteil

$| = 1; # während der Entwicklung die Ausgaben nicht puffern

if (scalar @ARGV ne 1)
{
  die "Ein Argument (Dateiname) benötigt";
}

$dateiname = $ARGV[0];

# klassisch:
# open WAVDATEI, "<", $dateiname or die "Konnte WAV-Datei \"$dateiname\" nicht zum Lesen öffnen: $!";
# wir machen es stattdessen systemnah, byteweise:
sysopen WAVDATEI, $dateiname, O_RDONLY or die "Konnte WAV-Datei \"$dateiname\" nicht zum Lesen öffnen: $!";

formatermittlung();
print "\n";
audiodatenaquise();
print "\n";
anausdetektion();
print "\n";
anauszulangkurz();
print "\n";
langkurzzumorsebuchstaben();
print "\n";
morsebuchstabenzutext();
print "\n";
textzuzahlenreihen();

# Ende
close ABTASTWERTE;
close WAVDATEI;

