Freitag, 30. Dezember 2011

Ein gutes Jahr 2012!

Ich wünsche allen Lesern einen guten Rutsch und ein frohes neues Jahr 2012.
In C# ausgedrückt:

Form1.Designer.cs:

namespace WindowsFormsApplication1
{
    partial class Form1
    {
        /// <summary>
       /// Erforderliche Designervariable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
       /// Verwendete Ressourcen bereinigen.
        /// </summary>
        /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Vom Windows Form-Designer generierter Code

        /// <summary>
       /// Erforderliche Methode für die Designerunterstützung.
       /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(12, 12);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.ReadOnly = true;
            this.textBox1.Size = new System.Drawing.Size(374, 328);
            this.textBox1.TabIndex = 0;
            //
            // timer1
            //
            this.timer1.Enabled = true;
            this.timer1.Interval = 300;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            //
            // textBox2
            //
            this.textBox2.Location = new System.Drawing.Point(433, 346);
            this.textBox2.Name = "textBox2";
            this.textBox2.Size = new System.Drawing.Size(12, 20);
            this.textBox2.TabIndex = 1;
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(393, 349);
            this.Controls.Add(this.textBox2);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Grüße vom C# Blog";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Timer timer1;
        private System.Windows.Forms.TextBox textBox2;
    }
}



Form1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        string[] images = new string[22];
        private void init()
        {
            images[0] = NewLine(23) + "".PadLeft(60, ' ') + "|";

            for (int j = 1; j < 14; j++)
            {
                images[j] = NewLine(23 - j) + "".PadLeft(60, ' ') + "|" + NewLine(1);
                images[j] += "".PadLeft(60, ' ') + "|";
            }

            images[14] = NewLine(9) + "".PadLeft(60, ' ') + ".";
            images[15] = NewLine(8) + "".PadLeft(58, ' ') + "....." + NewLine(1) + "".PadLeft(58, ' ') + "....." + NewLine(1) + "".PadLeft(58, ' ') + ".....";

            images[16] = Explode(7, 54, 7);
            images[17] = Explode(9, 52, 5);
            images[18] = Explode(11, 50, 3);
            images[19] = Explode(13, 48, 1);
            images[20] = Explode(7, 54, 7);
            images[21] = "Frohes Neues!";
        }

        private string Explode(int size, int left, int height)
        {
            string result = "";
            result += NewLine(height);
            result += "".PadLeft(left + 2 + (size - 5) + 1, ' ') + "|" + NewLine(1);

            for (int j = 5; j <= size; j++)
            {
                result += "".PadLeft(left + 2 + (size - 5) + 1 - (j - 4) - 1, ' ');
                for (int z = 0; z < 5  + (j - 5) * 2; z++)
                {
                    result += ".";
                }
                result += NewLine(1);
            }

            result += "".PadLeft(left, ' ') + "-";
            for (int j = 0; j <= 5 + (size - 5) * 2; j++)
            {
                result += ".";
            }
            result += "-";
            result += NewLine(1);

            for (int j = size; j >= 5; j--)
            {
                result += "".PadLeft(left + 2 + (size - 5) + 1 - (j - 4) - 1, ' ');
                for (int z = 0; z < 5 + (j - 5) * 2; z++)
                {
                    result += ".";
                }
                result += NewLine(1);
            }

            result += "".PadLeft(left + 2 + (size - 5) + 1, ' ') + "|" + NewLine(1);

            return result;
        }

        private string NewLine(int nr)
        {
            string result = "";
            for (int i = 0; i < nr; i++)
            {
                result += Environment.NewLine;
            }
            return result;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (i < images.Length)
            {
                textBox2.Focus();
                textBox1.Text = images[i];
                i++;
            }
        }

        int i = 0;
        private void Form1_Load(object sender, EventArgs e)
        {
            init();
        }
    }
}

(mein grafisches Vorstellungsvermögen ist nicht sehr gut)

Freitag, 16. Dezember 2011

Danke

Wie aufmerksame wie unaufmerksame Leser sicherlich schon bemerkt haben, erscheinen leider seit längerem keine Posts mehr von mir und auch die Antworten auf eure Kommentare dauern leider sehr lange, da ich ziemlich viel zu tun habe im Moment.

Ich versuche jedoch, wenigstens die Kommentare so gut ich kann zu beantworten und freue mich auch definitiv über jeden Eintrag und jedes Interesse an diesem Blog.

Dafür möchte ich mich hier herzlich bei allen Lesern bedanken, ich hoffe dieser Blog hilft ein wenig und bereitet ein bisschen Freude beim Lesen.

Freitag, 1. Juli 2011

Maus steuern

Auf User Anfragen erscheint heute ein Post zum Thema, wie man mit C# die Maus steuern kann, sie also komplett auf dem ganzen Bildschirm bewegen und z.B. Klicks simulieren kann.
Ich habe schon einen Post zum Simulieren von Tastendrücken geschrieben, welches ganz einfach mit der Funktion SendKeys() umgesetzt werden kann.
Die Maus zu steuern ist nicht mehr ganz so einfach, es gibt (noch) keine eingebaute .Net Funktion, wir müssen auf nicht verwaltete System Funktionen zurückgreifen.
Kernstück ist die P/Invoke Funktion SendInput(). Mit dieser Funktion können verschiedene Kommandos an den ausführenden Computer gesendet werden, zum Beispiel direkte Hardwareeingaben, Tastatur- und eben Mausereignisse (im folgenden Inputs genannt).
Die Funktion erwartet 3 Parameter: Der 1. beschreibt die Anzahl der übergebenen Inputs, der 2. enthält eine Referenz auf die tatsächlichen Inputdaten, der 3. speichert die Größe eines einzelnen Inputs.
Den 2. Parameter muss vom Typ einer Struktur sein, welche mindestens den Typ des Inputs (also Hardware, Tastatur, Maus ...) sowie die Daten des Inputs enthält.
Die Struktur habe ich Input genannt.
Mausinputdaten selber haben mehrere Daten, wie die X - und Y - Koordinate, welche ich in der Struktur MouseInput zusammengefasst habe.

Zuerst möchte ich nun das Bewegen der Maus beschreiben: Wir brauchen also eine Instanz der Struktur Input mit den gewünschten Zielmauskoordinaten.
Als Typ des Inputs setzen wir 0, welches ein Mausereignis symbolisiert.
Die Mausdaten repräsentiert durch eine Instanz von MouseInput lassen wir uns über die Funktion CreateMouseInput() erstellen.
Diese erhält alle benötigten Parameter und setzt diese im zurückgegeben Mausereignis.
Zum Bewegen der Maus sind nur die Werte für X und Y wichtig (Zielkoordinaten) sowie für DwFlags. Dieses Feld kann bestimmte Flags aufnehmen, wie zum Beispiel den Druck eines Mausbuttons.
In diesem Fall übergeben wir aber 2 vorher definierte Konstanten (so haben wir leicht zu merkende Namen anstatt Zahlen), MOUSEEVENTF_ABSOLUTE und MOUSEEVENTF_MOVE, um anzuzeigen, dass wir die Maus bewegen wollen. Erstere sagt aus, dass wir ein Mausereignis nicht nur im aktuellen Fenster herbeiführen wollen, sondern auf dem ganzen Bildschirm, zweite zeigt die gewünschte Bewegung der Maus an.

Das Simulieren eines (Links-)Klicks funktioniert ähnlich.
Wir müssen wieder einen Input mit den entsprechenden Daten erstellen, verwenden diesmal aber ein Array der Länge 2, da wir zuerst das Drücken der linken Maustaste und anschließend das Loslassen dieser simulieren wollen.
Über die Funktion CreateMouseInput() lassen wir uns dafür 2 MouseInputs erstellen und übergeben für den Wert von DwFlags die entsprechenden Konstanten.

Ich hoffe, diese kurze Erläuterung sowie der folgende Code helfen:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;


namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // P/Invoke Funktion u.a. zum Steuern der Maus
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize);

        /// <summary>
       /// Struktur für Mausdaten
        /// </summary>
        struct MouseInput
        {
            public int X; // X - Koordinate
            public int Y; // Y - Koordinate
            public uint MouseData; // Mausdaten, z.B. für Mausrad
            public uint DwFlags; // weitere Mausdaten, z.B. für Mausbuttons
            public uint Time; // Zeit des Events
            public IntPtr DwExtraInfo; // weitere Informationen
        }

        /// <summary>
       /// Oberstruktur für InputDaten der Funktion SendInput
        /// </summary>
        struct Input {
            public int Type; // Typ des Inputs, 0 für Maus  
            public MouseInput Data; // Mausdaten
        }

        // Konstanten für Mausflags
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002; // linken Mausbutton drücken
        const uint MOUSEEVENTF_LEFTUP = 0x0004; // linken Mausbutton loslassen
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000; // ganzen Bildschirm ansprechen, nicht nur aktuelles Fenster
        const uint MOUSEEVENTF_MOVE = 0x0001; // Maus bewegen

        private MouseInput CreateMouseInput(int x, int y, uint data, uint time, uint flag)
        {
            // aus gegebenen Daten Objekt vom Typ MouseInput erstellen, welches dann gesendet werden kann
            MouseInput Result = new MouseInput();
            Result.X = x;
            Result.Y = y;
            Result.MouseData = data;
            Result.Time = time;
            Result.DwFlags = flag;
            return Result;
        }

        private void SimulateMouseClick()
        {
            // Linksklick simulieren: Maustaste drücken und loslassen
            Input[] MouseEvent = new Input[2];
            MouseEvent[0].Type = 0;
            MouseEvent[0].Data = CreateMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTDOWN);

            MouseEvent[1].Type = 0; // INPUT_MOUSE;
            MouseEvent[1].Data = CreateMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTUP);

            SendInput((uint)MouseEvent.Length, MouseEvent, Marshal.SizeOf(MouseEvent[0].GetType()));
        }

        private void SimulateMouseMove(int x, int y) {
            Input[] MouseEvent = new Input[1];
            MouseEvent[0].Type = 0;
            // Maus bewegen: Flags ABSOLUTE (ganzen Bildschirm verfügbar machen) und MOVE (bewegen)
            MouseEvent[0].Data = CreateMouseInput(x, y, 0, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE);
            SendInput((uint)MouseEvent.Length, MouseEvent, Marshal.SizeOf(MouseEvent[0].GetType()));
        }


         private void button1_Click(object sender, EventArgs e)
         {
             // Maus testweise bewegen
             SimulateMouseMove(0, 0);
             // und anschließend klicken
             SimulateMouseClick();
         }
    }
}

Samstag, 14. Mai 2011

Quine Wettbewerb: Finde das kürzeste

Anlässlich des 150. Posts habe ich mir wieder einen kleinen "Wettbewerb" ausgedacht.
Ziel ist es, ein möglichst kurzes Quine zu schreiben, welches eine bestimmte Anweisung ausführt.
Zur Erinnerung: Ein Quine ist ein Programm, welches seinen eigenen Quellcode komplett ausgibt, auf diesem Blog gibt es auch einen Post darüber.
Aufgabe ist es nun, aus folgendem Programm ein Quine zu machen:

class Quine
{
    static void Main(string[] args)
    {
        System.Diagnostics.Process.Start(System.Environment.SystemDirectory + "\\shutdown.exe", "-s -t 120");
    }
}

Der Wettbewerb soll über 3 Wochen laufen, also bis Samstag den 4.5. Wer bis dahin den kürzesten Code gepostet hat, gewinnt ;-)
Ich lege vor.
Das Quine soll natürlich exakt den Programmcode drucken, hierbei sind Zeileneinrückungen aber nicht wichtig.
Ein kleiner Tipp, wie man die Ausgabe leicht überprüfen kann:
Mit Console.SetOut() lässt sich die Ausgabe der Konsole in einen Stream umleiten, so kann z.B. in eine Datei geschrieben werden, also einfach die entsprechenden Befehle in das Programm schreiben (sie zählen nicht zum Quine dazu).
Mit Programmen wie Beyond Compare kann dann die Gleichheit überprüft werden (also definiere, alles was Beyond Compare als gleich ansieht gilt) und mit z.B. Word kann leicht die Länge der Datei gezählt werden.
Mein Quine Code lautet:

class Quine
{
    static void Main(string[] args)
    {
        // Zählt nicht dazu, auskommentieren um Ausgabe umzuleiten: System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\Users\User\Desktop\Neues Textdokument (2).txt");
        // ", System.Console.SetOut(sw);
        System.Diagnostics.Process.Start(System.Environment.SystemDirectory + "\\shutdown.exe", "-s -t 120");
        string s = "class Quine {3} {0} {3} static void Main(string[] args) {3} {0} {3} System.Diagnostics.Process.Start(System.Environment.SystemDirectory + {2}{5}{5}shutdown.exe{2}, {2}-s -t 120{2}); {3} string s = {2}{1}{2}; {3} System.Console.WriteLine(s, System.Convert.ToChar(123), s, System.Convert.ToChar(34), System.Environment.NewLine, System.Convert.ToChar(125), System.Convert.ToChar(92)); {3} {4} {3} {4}";
        System.Console.WriteLine(s, System.Convert.ToChar(123), s, System.Convert.ToChar(34), System.Environment.NewLine, System.Convert.ToChar(125), System.Convert.ToChar(92));
        // ", sw.Close();
    }
}

Die Länge hiervon beträgt 763 Zeichen.

Und jetzt, viel Glück und viel Spaß.

PS: Speichert vorher alle Dokumente bzw. schaut euch an, wie man das Herunterfahren von Windows abbrechen kann (Stichwort: shutdown -a).

Samstag, 7. Mai 2011

Anwendung mit Administratorrechten neu starten (Windows Vista / 7)

In einem älteren Post habe ich gezeigt, wie man mit C# eine Anwendung mit Administratorechten ausstatten und starten kann.
Hierfür wurde jedoch eine eigene Datei, die Anwendungsmanifestdatei, benötigt.
Auf Anfrage eines Lesers möchte ich heute einen kleinen Trick vorstellen, wie man eine Anwendung nur durch reinen Code mit diesen Rechten ausstattet.
Entscheidend ist hierbei die Klasse System.Diagnostics, welche einen beliebigen Process mit bestimmten Argumenten starten kann.
Wenn die Anwendung also normal gestartet wird, wird sie über diese Klasse mit Administratorrechten neugestartet.
Damit das Programm nicht unendlich oft aufgerufen wird, geben wir dem Programm zur Prüfung beim Aufrufen ein neues Befehlszeilenargument mit.
Der folgende Code sollte das Beispiel erläutern:

private void Form1_Load(object sender, EventArgs e)
{
    string[] CommandLineArgs = Environment.GetCommandLineArgs(); // Befehlszeilenargumente auslesen
    if (CommandLineArgs.Length <= 1 || CommandLineArgs[1] != "restarted") // ist das 2. Argument (das 1. ist immer der Dateipfad) != restarted, wurde das Programm noch nicht neugestartet
    {
        ProcessStartInfo ProcessInfo = new ProcessStartInfo(Application.ExecutablePath, "restarted"); // Prozessinformationen festlegen, Anwendungspfad und unser eigenes Argument "restarted"
        ProcessInfo.Verb = "runas"; // Befehl zum Ausführen als Administrator
        Process.Start(ProcessInfo); // Prozess mit den eingestellten Informationen starten
    }
    else
        Environment.Exit(0); // wurde das Argument "restarted" übergeben, wurde das Programm bereits gestartet, um die Endlosrekursion zu verhindern, nicht erneut aufrufen
}

Freitag, 6. Mai 2011

Befehlszeilenargumente auslesen

Programme können unter Windows grundsätzlich mit verschiedenen Argumenten aufgerufen werden, welche Befehlszeilenargumente heißen.
Zum Beispiel können einige Programme direkt mit zu öffnenden Dateien als Argument aufgerufen werden.
In Konsolenanwendungen in C# stehen diese Kommandozeilenargumente direkt als Parameter der Funktion Main() zur Verfügung.
In Windows Forms-Anwendungen stehen diese so nicht bereit, dafür gibt es aber die Funktion Environment.GetCommandLineArgs(), welche die Befehlszeilenargumente als Array vom Typ String zurückgibt.
Folgender Code liest die übergebenen Parameter beim Programmstart aus:

string[] CommandLineArgs = Environment.GetCommandLineArgs();

Wie man sieht, wird als 1. Parameter immer der Pfad und der Name des ausgeführten Programmes übergeben.

Freitag, 1. April 2011

Windows 7 Features: Eine JumpList erstellen

Die JumpList bezeichnet unter Windows 7 das Kontextmenü einer Anwendung in der Taskleiste, welches durch Rechtsklick auf das Applikationssymbol sichtbar wird.
In diesem Menü befinden sich z.B. häufig geöffnete Dateien der Anwendung und mögliche Programmoptionen.
Für das C# Studio sieht meine JumpList beispielsweise so aus:


In C# können wir nun für unsere eigenen Anwendungen JumpLists erstellen und bearbeiten.
Obwohl es im Umfang des .Net Frameworks schon eine JumpList Klasse gibt, brauchen wir für Windows Forms Anwendungen doch das Windows API Code Pack, denn die vorhandene Klasse ist nur für WPF Anwendungen geeignet.
Die Installation des Windows API Code Packs habe ich hier beschrieben, die 2 entstandenen dll - Dateien müssen anschließend noch eingebunden werden (Projekt - Verweis hinzufügen - Durchsuchen). Die benötigten Dateien heißen "Microsoft.WindowsAPICodePack.dll" und "Microsoft.WindowsAPICodePack.Shell.dll", sie befinden sich im Installationsordner des Code Packs unter Shell\bin\Debug.
Die für diesen Post verwendete Klasse JumpList befindet sich schließlich in der Klasse Microsoft.WindowsAPICodePack.Taskbar.
In einer JumpList sind standardmäßig 2 Kategorien vorhanden: "Aufgaben" und "Häufig" (geöffnete Dateien).
Diesen Kategorien können wir durch die Funktionen AddUserTasks() bzw. AddToRecent() Einträge hinzufügen.
Als Einträge verwende ich in diesem Post Objekte der Klasse JumpListLink, welche einen Namen und ein Ziel erhalten, welches ein ausführbares Programm oder eine Datei sein kann.
Die Kategorie "Recent" lässt sich allerdings nicht so einfach befüllen, denn Windows lässt nur Dateien mit einer Endung zu, die für das entsprechende Programm registriert sind.
Mit einem kleinen Trick kann man sich allerdings die Schwierigkeit der Registrierung umgehen, es gibt nämlich die Möglichkeit, in den JumpLists eigene Kategorien anzulegen - legt man dann z.B. die Kategorie "Häufig" an, ist das Ergebnis das gleiche.
Der Beispielcode legt in der JumpList des Programms 2 Einträge unter "Aufgaben" an - einen Eintrag zu Paint und einen zum Windows Taschenrechner.
Für diese Programme werden ebenfalls Icons bereitgestellt, welche als IconReference direkt aus den Originalprogrammen referenziert werden.
Weiterhin legt der Code eine neue Kategorie an und erstellt in dieser einen Link zu einer Datei auf meinem Desktop.
Das anschließende Refresh() ist wichtig, damit die Änderungen übernommen werden.
Die JumpList des Programms ist auch beim nächsten Programmstart - sofern sie nicht gelöscht bzw. geändert wurde - noch in der gleichen Form erhalten!
Das Ergebnis sieht so aus:

Jetzt nun der Code, am Anfang ist ein
using Microsoft.WindowsAPICodePack.Taskbar;
erforderlich:

            JumpList CustomJumpList = JumpList.CreateJumpList(); // neue JumpListe erzeugen

            // Link zu Paint unter Aufgaben hinzufügen
            JumpListLink EntryPaint = new JumpListLink(@"C:\Windows\System32\mspaint.exe", "Paint");
            EntryPaint.IconReference = new Microsoft.WindowsAPICodePack.Shell.IconReference(@"C:\Windows\System32\mspaint.exe", 0);
            CustomJumpList.AddUserTasks(EntryPaint);

            // Link zum Taschenrechner unter Aufgaben hinzufügen
            JumpListLink EntryCalc = new JumpListLink(@"C:\Windows\System32\calc.exe", "Rechner");
            EntryCalc.IconReference = new Microsoft.WindowsAPICodePack.Shell.IconReference(@"C:\Windows\System32\calc.exe", 0);
            CustomJumpList.AddUserTasks(EntryCalc);

            // eigene Kategorie anlegen
            JumpListCustomCategory CustomCategory = new JumpListCustomCategory("Neue Kategorie");
            // in diese Link zu einer Datei anlegen
            CustomCategory.AddJumpListItems(new JumpListLink(@"C:\Users\User\Desktop\C#.txt", "C# Links"));
            CustomJumpList.AddCustomCategories(CustomCategory);

            // Änderungen übernehmen
            CustomJumpList.Refresh();

Mittwoch, 23. März 2011

Spracherkennung Part 2: Command Mode

Wie angekündigt, ist dieser Post eine Fortsetzung des vorigen, es geht um Spracherkennung, insbesondere um den Command Mode.
Wie der Name schon andeutet, wird aufgenommene Sprache damit auf vordefinierte Befehle untersucht.
Man könnte natürlich auch über den im vorigen Post beschriebenen Dictation Mode einen Text aufnehmen und diesen auf bestimmte Begriffe hin untersuchen - der Command Mode bietet dafür allerdings ein paar Vorteile.
Denn einerseits ermöglicht er leicht eine dauerhafte Prüfung, ob Befehle gesagt wurden, und andererseits ist die Erkennung besser, da das Programm nur die Befehlswörter kennt und sich bei der Erkennung nur zwischen diesen entscheiden muss.
Nun direkt zum Code: Die Vorraussetzungen (das Einbinden der Ressource etc.) sind die gleichen wie im vorigen Post.
Zum Erkennen der Befehle benutzen wir jetzt allerdings eine Instanz der Klasse SpeechRecognizer.
Diese erwartet auch eine Grammatik, welche wir aber jetzt manuell mit Wörtern befüllen, die erkannt werden sollen.
Über die Eigenschaft Enabled kann die Spracherkennung aktiviert werden.
Beim Erkennen von Befehlen tritt das Ereignis SpeechRecognized auf, diesem weisen wir eine Funktion zu, welche das Ereignis behandeln soll.
Wird ein SpeechRecognizer initialisiert, öffnet sich unter Windows ein Fenster zur Spracherkennung - hier muss der Benutzer auf den großen Mikrofonknopf drücken, um die Erkennung zu starten.
Das Drücken des Knopfes hat nichts mit der Eigenschaft Enabled zu tun - beide Dinge müssen aktiviert sein.
Einen Weg, die Spracherkennung nur per Code zu starten und das Fenster zu verstecken habe ich nicht gefunden. Mich würde aber interessieren ob das möglich ist, falls jemand eine Idee hat, bitte kommentieren!
Der Code:

        private void StartListening()
        {
            SpeechRecognizer SR = new SpeechRecognizer();
            // die Befehle registrieren
            Choices Commands = new Choices();
            Commands.Add("Stop");
            Commands.Add("Los");
            GrammarBuilder GB = new GrammarBuilder(Commands); // die Befehle mit einem GrammerBuilder laden
            Grammar CommandGrammar = new Grammar(GB); // eine Grammatik über den GrammarBuilder erstellen
            SR.LoadGrammar(CommandGrammar); // die Grammatik laden
            SR.SpeechRecognized += CommandRecognized; // Funktion zur Behandlung des Ereignisses
            SR.Enabled = true;
        }

        private void CommandRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            string Command = e.Result.Text;
            // hier weiterer Verarbeitungscode ...
        }

Dienstag, 22. März 2011

Spracherkennung Part 1: Dictation Mode

Im vorigen Post ging es um die Sprachausgabe mit .Net, dieses Mal widmen wir uns der Umkehrung, nämlich der Spracherkennung.
Diese ist für Computer viel schwieriger als die Ausgabe, das .Net Framework bietet über die Klasse System.Speech.Recognition allerdings wieder fertige Funktionen an, die mit wenig Codeaufwand zur Spracherkennung eingesetzt werden können.
Generell gibt es 2 Modi, in welchen die Spracherkennung betrieben werden kann: In diesem Post geht es um den Dictation Mode, im nächsten um den Command Mode.
Der Dictation Mode eignet sich, wie der Name schon sagt, zum Diktieren von Texten.
Der aufgezeichnete Ton wird wie ein Diktat verstanden und das Programm versucht, die gesprochenen Wörter zu erkennen.
Wie bei der Sprachausgabe muss zuerst ein Verweis auf die Komponente System.Speech eingebunden werden.
Die benötigte Unterklasse heißt nun allerdings Recognition, und so benutzen wir am Anfang folgende using - Direktive:

using System.Speech.Recognition;

Zur Spracherkennung benutzen wir eine Instanz der Klasse SpeechRecognitionEngine.
Diese benötigt eine Grammatik, also eine Art Anweisungssammlung, wie die Sprache interpretiert werden soll.
Als Grammatik übergeben wir der SpeechRecognitionEngine eine Instanz der Klasse DictationGrammar, um anzuzeigen, dass wir den Diktiermodus benutzen wollen.
Das Erkennen von gesprochenen Wörtern geschieht nun mit Aufruf der Funktion Recognize(). Diese setzt die Spracherkennung in Bereitschaftsmodus und beginnt, wenn das Mikrofon Töne vernimmt.
Macht der Sprecher eine bestimmte Zeit Pause (einstellbar), wird die Spracherkennung beendet und das Programm versucht nun, den Ton als Wörter zu interpretieren (eine asynchrone Aufnahme ist auch möglich).
Zum Schluss wird das Diktierergebnis zurückgegeben.
Hier der Code:

            SpeechRecognitionEngine SRE = new SpeechRecognitionEngine();
            SRE.LoadGrammar(new DictationGrammar()); // Diktiergrammatik laden
            SRE.SetInputToDefaultAudioDevice(); // Aufnahmequelle auf Standard setzen

            RecognitionResult Result = SRE.Recognize(); // Ton aufzeichnen und erkennen
            string ResultString = "";
            // alle erkannten Wörter aus dem Ergebnis dem Ergebnisstring hinzufügen
            foreach (RecognizedWordUnit w in Result.Words)
            {
                ResultString += w.Text;
            }

Montag, 21. März 2011

Sprachausgabe mit C#

Die Überschrift hört sich wahrscheinlich ziemlich kompliziert an, allerdings ist in C# die Realisierung einer Sprachausgabe in nur 2 Zeilen möglich!
Denn die schon mit Windows mitgelieferte Sprachsynthese ist auch im .Net Framework nutzbar, die benötigte Komponente ist die Klasse System.Speech.Synthesis. Um sie nutzen zu können, muss sie zuerst eingebunden werden: Projekt - Verweis hinzufügen - (Reiter .Net) System.Speech.
Um den Code zu vereinfachen, binden wir den Namespace mittels using ein:

using System.Speech.Synthesis;

Zur Sprachausgabe benötigen wir eine Instanz der Klasse SpeechSynthesizer, welcher über die Funktion Speak() der auszugebende Text übergeben werden kann.
Natürlich gibt es aber noch viele weitere Eigenschaften, mit denen herumexperimentiert werden darf.
Ein paar in der Übersicht:
- Rate: Wertebereich von -10 bis 10, Geschwindigkeit der Textausgabe
- Volume: Wertebereich 0 - 100, Lautstärke
- Voice: Stimme
Die letzte Eigenschaft ist schreibtgeschützt, ihr kann direkt nichts zugewiesen werden, sie kann allerdings über Funktionen geändert werden.
Nützlich ist die Funktion SelectVoiceByHints(), über die "Hinweise" gegeben werden können, nach denen eine passende Stimme gesucht werden soll.
Solche Kriterien sind zum Beispiel Geschlecht, Alter und sogar Kulturkreis.
So sucht zum Beispiel folgende Anweisung nach einem Stimmtyp, welcher weiblich und erwachsen klingt und legt diese Stimme bei Erfolg als Ausgabestimme fest:

Speaker.SelectVoiceByHints(VoiceGender.Female, VoiceAge.Adult);

Allerdings wird auf vielen Windows Rechnern nur eine Stimme vorinstalliert sein (bei mir z.B. nur "Microsoft Anna").
Dann hat eine Stimmsuche keinerlei Auswirkung.
Die installierten Stimmen kann man über die Funktion Speaker.GetInstalledVoices() auslesen.
Die einzelnen Stimmen liegen als Objekt vom Typ Voice vor, folgendes Beispiel liest alle Stimmen aus und fügt ihren Namen zu der Liste InstalledVoices hinzu:

List<string> InstalledVoices = new List<string>();
foreach (InstalledVoice voice in Speaker.GetInstalledVoices())
{
    InstalledVoices.Add(voice.VoiceInfo.Name);
}

Freitag, 18. März 2011

Speicherauslastung eines Prozesses ermitteln

Der heute Post wird nach den etwas längeren vorhergenden Posts wieder etwas kürzer, ich möchte euch zeigen, wie man mit C# die Speicherauslastung eines Prozesses ermittelt.
Die dafür erforderliche Eigenschaft heißt WorkingSet64 aus der Klasse System.Diagnostics.Process. Sie gibt die Anzahl an Bytes an, die von dem jeweiligen Prozess im Arbeitsspeicher belegt sind - korreliert also in etwa mit der Spalte "Arbeitsspeicher" im Taskmanager.
Um die Speicherbenutzung eines bestimmten Prozesses zu ermitteln, muss dieser zuerst "abgegriffen" werden, zum Beispiel über die Funktion Process.GetProcessesByName() (ein paar Hintergrundinfos zu dieser gibt's in diesem Post).
Die Funktion sucht nach Prozessen mit dem übergebenen Namen und speichert alle diese im Ergebnisarray.
Der folgende Code ermittelt die Speicherauslastung des angegeben Prozesses, in dem er oben geannten Eigenschaft des ersten Eintrags im Array abfragt - falls mehrere Proezsse mit gleichem Namen vorhanden sind, müssen ggf. weitere Maßnahmen ergriffen werden.
Im Beispiel wird die Auslastung des ersten geöffneten Editorprozesses abgefragt:

Process[] Application;
Application = Process.GetProcessesByName("notepad");
long MemorySize = Application[0].WorkingSet64;

Montag, 14. März 2011

Wave Dateien zusammenmischen (überblenden)

In den vorigen beiden Posts wurde das Wave Format auseinander genommen, es wurde gezeigt, wie mit C# Wave Dateien eingelesen und geschrieben werden können.
In diesem Post möchte ich eine "coole" Anwendung zeigen, und zwar, wie man ein Programm erstellt, welches 2 Lieder ineinander übermischt.
Das Zusammenmischen entsteht hier durch Überblenden (Fading) der Lieder, das erste Lied wird gegen Ende hin immer leiser während das zweite Lied immer lauter wird.
Viele wünschen sich wahrscheinlich eine Anwendung, die das selbe mit MP3 - Dateien macht - allerdings ist das MP3 Format um Längen komplizierter als das Wave Format.
Es lassen sich aber beliebige MP3s mit dem in den vorigen Posts erwähnten Programm Audacity in Wave Dateien umwandeln und dann bearbeiten.
Der in diesem Post verwendete Code baut immer noch auf die Klasse WaveFile auf, die in den beiden vorigen Posts angefangen wurde.
Ein Beispielaufruf der benötigten Zeilen zum Mischen 2er Wave Dateien könnte so aussehen:

            WaveFile WF1 = new WaveFile();
            WF1.LoadWave(@"C:\Users\User\Desktop\101-die_atzen_-_disco_pogo-ysp.wav");

            WaveFile WF2 = new WaveFile();
            WF2.LoadWave(@"C:\Users\User\Desktop\StroboPopDieAtzenFeatNena.wav");

            WaveFile.StoreMixWave(@"C:\Users\User\Desktop\mixed.wav", WF1, WF2, 10);

Es werden also zuerst 2 Wave Dateien eingelesen und schließlich wird die statische Funktion StoreMixWave() aufgerufen, welche als Parameter den Pfad zur Ergebnisdatei, die zu mischenden WaveFiles sowie die Überblendezeit erwartet.
Der Funktionscode sieht so aus:

public static void StoreMixWave(string path, WaveFile wf1, WaveFile wf2, int fadeTime)
{
    WaveFile Mixed = MixWave(wf1, wf2, fadeTime); // Ergebnisdatei mischen
    Mixed.StoreWave(path); // Ergebnisdatei auf Festplatte speichern
}

Der Ergebniswavefile wird also in der Funktion MixWave() zusammengemischt, und dieser WaveFile dann mit der bekannten Funktion StoreWave() geschrieben.
Nun betrachten wir direkt einmal den Code der Funktion MixWave():

        private static WaveFile MixWave(WaveFile wf1, WaveFile wf2, int fadeTime)
        {
            int FadeSamples = fadeTime * wf1.ByteRate / wf1.NumChannels; // Anzahl an aus-/ einzublenden Samples
            int FadeBytes = fadeTime * wf1.ByteRate; // Anzahl an aus-/ einzublendenden Bytes

            WaveFile Result = new WaveFile(); // Ergebnis Wave Datei
            Result.FileSize = wf1.FileSize + wf2.DataSize - 2 * FadeBytes; // neue Dateigröße
            Result.Format = "WAVE";

            // Informationen aus dem fmt Chunk übernehmen
            Result.FmtChunkSize = wf1.FmtChunkSize;
            Result.AudioFormat = wf1.AudioFormat;
            Result.NumChannels = wf1.NumChannels;
            Result.SampleRate = wf1.SampleRate;
            Result.ByteRate = wf1.ByteRate;
            Result.BlockAlign = wf1.BlockAlign;
            Result.BitsPerSample = wf1.BitsPerSample;

            Result.DataSize = wf1.DataSize + wf2.DataSize - 2 * FadeBytes; // neue Größe des Data Chunks
            Result.Data = new int[wf1.NumChannels][]; // Anzahl an Kanälen übernehmen
            int NumSamples = Result.DataSize / (Result.NumChannels * ( Result.BitsPerSample / 8)); // Anzahl an Samples ausrechnen, die sich in de Ergebnisdatei ergeben

            // Die Data Arrays für alle Kanäle auf die Anzahl der Samples dimensionieren.
            for (int i = 0; i < Result.Data.Length; i++)
            {
                Result.Data[i] = new int[NumSamples];
            }

            int PosCounter = 0; // Position des aktuellen Samples in der Ergebnisdatei

            // die Samples aus der ersten Wave Datei in das Data Feld der Ergebnisdatei kopieren
            for (int i = 0; i < wf1.Data[0].Length; i++)
            {
                // das aktuelle Sample in allen Kanälen übernehmen
                for (int j = 0; j < wf1.NumChannels; j++)
                {
                    // fällt das aktuelle Sample in die Zeit, die überblendet werden soll, den Amplitudenwert der 1. Datei mit dem Amplitudenwert aus der 2. Datei mischen
                    if (i > wf1.Data[0].Length - FadeSamples)
                       Result.Data[j][PosCounter] = (int)(wf1.Data[j][i] * Factor(i - (wf1.Data[0].Length - FadeSamples), FadeSamples, 0) + wf2.Data[j][i - (wf1.Data[0].Length - FadeSamples)] * Factor(i - (wf1.Data[0].Length - FadeSamples), FadeSamples, 1));
                    else
                       Result.Data[j][PosCounter] = wf1.Data[j][i];
                }
                PosCounter++;
            }

            // die restlichen Samples in die Ergebnisdatei übernehmen
            for (int i = FadeSamples; i < wf2.Data[0].Length; i++)
            {
                for (int j = 0; j < wf1.NumChannels; j++)
                {
                    Result.Data[j][PosCounter] = wf2.Data[j][i];
                }
                PosCounter++;
            }
            return Result;
        }

Am Anfang dieser wird die Anzahl der Samples berechnet, die überblendet werden sollen.
Zur Erinnerung: Eine Wave Datei ist im Groben eine Sammlung von Luftdruckamplituden. Mit einer bestimmten Abtastrate werden in bestimmten Zeitabständen diese Amplituden gemessen und in der Datei gespeichert. Diese einzelnen Werte sind die Samples.
Um 2 Audiodateien übereinander zu legen, kann man z.B. einfach die Amplituden addieren.
Die Anzahl an zu überblenden Samples berechnet sich demnach als Produkt der Überblendzeit in Sekunden und Byterate (Anzahl der Bytes pro Sekunde, die beim Abspielen bearbeitet werden), dividiert durch die Anzahl der Kanäle (da die Byterate die Anzahl an Bytes von allen Kanälen angibt).
Dann werden die Dateien zusammengemischt:
Die ersten beiden Blöcke ("RIFF" und "fmt ") werden einfach aus der ersten übergebenen Wave Datei übernommen - allerdings wird natürlich die Dateigröße angepasst.
Die neue Dateigröße ergibt sich als Summe der alten Dateigrößen, abzüglich 2mal der Anzahl an Bytes, die überblendet werden.
Damit das Ergebnis auch nach etwas klingt, ist hier also Vorraussetzung, dass beide Wave Dateien einen ähnlichen fmt Block haben, sprich die selbe Abtastrate, Anzahl an Kanälen etc. Das ist aber eh der Fall, wenn man die Wave Dateien mit Audacity erstellt.
Das Mischen der Data Chunks gestaltet sich nun als etwas komplizierter.
Zuerst wird die Größe des resultierende Data Blocks berechnet und daraus dann die Anzahl an Samples, die in jedem Kanal gespeichert werden, um das Data Array für alle Kanäle auf diesen Wert zu dimensionieren.
Diese Anzahl berechnet sich laut Spezifikation des Wave Formats laut der Formel (Größe des Data Blocks) / (Anzahl Kanäle * BitsPerSample / 8).
Die Variable PosCounter zählt im folgenden die Position im Data Array der Ergebnisdatei.
Mit 2 Schleifen werden nun alle Samples der ersten Wave Datei durchgegangen und hierbei in jeder Iteration alle Kanäle dieser.
Die entsprechenden Daten aus dem Data Array werden an die Position PosCounter im Data Array der Ergebnisdatei geschrieben.
Hat die Schleife eine Position erreicht, die näher als die Anzahl der zu überblenden Bytes am Ende der ersten Datei liegt, werden nicht einfach die Daten der 1. Datei kopiert, sondern nun mit denen der 2. Datei gemischt.
Damit das 1. Lied langsam ausgeblendet während das 2. langsam eingeblendet wird, bestimmt die Funktion Factor() den Anteil der jeweiligen Lieder an der Gesamtamplitude.
Anschließend werden die noch nicht betrachteten Samples aus der 2. Datei in die Ergebnisdatei übernommen - fertig ist die Mischung!
Schlussendlich noch eine Übersicht über den Code der kompletten Klasse WaveFile, welchen alle 3 Posts zu diesem Thema benutzen:

    public class WaveFile  
    {
        int FileSize; // 1
        string Format; // 2
        int FmtChunkSize; // 3
        int AudioFormat; // 4
        int NumChannels; // 5
        int SampleRate; // 6
        int ByteRate; // 7
        int BlockAlign; // 8
        int BitsPerSample; // 9
        int DataSize; // 10

        int[][] Data; // 11

        #region Einlesen
        public void LoadWave(string path)
        {
            System.IO.FileStream fs = System.IO.File.OpenRead(path); // zu lesende Wave Datei öffnen
            LoadChunk(fs); // RIFF Chunk einlesen
            LoadChunk(fs); // fmt Chunk einlesen
            LoadChunk(fs); // data Chunk einlesen
        }

        private void LoadChunk(System.IO.FileStream fs)
        {
            System.Text.ASCIIEncoding Encoder = new ASCIIEncoding();

            byte[] bChunkID = new byte[4];
            /* Die ersten 4 Bytes einlesen.
            Diese ergeben bei jedem Chunk den jeweiligen Namen. */
            fs.Read(bChunkID, 0, 4);
            string sChunkID = Encoder.GetString(bChunkID); // den Namen aus den Bytes dekodieren

            byte[] ChunkSize = new byte[4];
            /* Die nächsten 4 Bytes ergeben bei jedem Chunk die Größenangabe. */
            fs.Read(ChunkSize, 0, 4);

            if (sChunkID.Equals("RIFF"))
            {
                // beim Riff Chunk ...
                // die Größe in FileSize speichern
                FileSize = System.BitConverter.ToInt32(ChunkSize, 0);
                // das Format einlesen
                byte[] Format = new byte[4];
                fs.Read(Format, 0, 4);
                // ergibt "WAVE" als String
                this.Format = Encoder.GetString(Format);
            }

            if (sChunkID.Equals("fmt "))
            {
                // beim fmt Chunk die Größe in FmtChunkSize speichern
                FmtChunkSize = System.BitConverter.ToInt32(ChunkSize, 0);
                // sowie die anderen Informationen auslesen und speichern
                byte[] AudioFormat = new byte[2];
                fs.Read(AudioFormat, 0, 2);
                this.AudioFormat = System.BitConverter.ToInt16(AudioFormat, 0);
                byte[] NumChannels = new byte[2];
                fs.Read(NumChannels, 0, 2);
                this.NumChannels = System.BitConverter.ToInt16(NumChannels, 0);
                byte[] SampleRate = new byte[4];
                fs.Read(SampleRate, 0, 4);
                this.SampleRate = System.BitConverter.ToInt32(SampleRate, 0);
                byte[] ByteRate = new byte[4];
                fs.Read(ByteRate, 0, 4);
                this.ByteRate = System.BitConverter.ToInt32(ByteRate, 0);
                byte[] BlockAlign = new byte[2];
                fs.Read(BlockAlign, 0, 2);
                this.BlockAlign = System.BitConverter.ToInt16(BlockAlign, 0);
                byte[] BitsPerSample = new byte[2];
                fs.Read(BitsPerSample, 0, 2);
                this.BitsPerSample = System.BitConverter.ToInt16(BitsPerSample, 0);
            }

            if (sChunkID == "data")
            {
                // beim data Chunk die Größenangabe in DataSize speichern
                DataSize = System.BitConverter.ToInt32(ChunkSize, 0);

                // der 1. Index von Data spezifiziert den Audiokanal, der 2. das Sample
                Data = new int[this.NumChannels][];
                // Temporäres Array zum Einlesen der jeweiligen Bytes eines Kanals pro Sample
                byte[] temp = new byte[BlockAlign / NumChannels];
                // für jeden Kanal das Data Array auf die Anzahl der Samples dimensionalisieren
                for (int i = 0; i < this.NumChannels; i++)
                {
                    Data[i] = new int[this.DataSize / (NumChannels * BitsPerSample / 8)];
                }

                // nacheinander alle Samples durchgehen
                for (int i = 0; i < Data[0].Length; i++)
                {
                    // alle Audiokanäle pro Sample durchgehen
                    for (int j = 0; j < NumChannels; j++)
                    {
                        // die jeweils genutze Anzahl an Bytes pro Sample und Kanal einlesen
                        if (fs.Read(temp, 0, BlockAlign / NumChannels) > 0)
                        {   // je nachdem, wie viele Bytes für einen Wert genutzt werden,
                            // die Amplitude als Int16 oder Int32 interpretieren
                            if (BlockAlign / NumChannels == 2)
                                Data[j][i] = System.BitConverter.ToInt16(temp, 0);
                            else
                                Data[j][i] = System.BitConverter.ToInt32(temp, 0);
                        }
                        /* else
                         * andere Werte als 2 oder 4 werden nicht behandelt, hier bei Bedarf ergänzen!
                        */
                    }
                }
            }
        }
        #endregion

        #region Schreiben
        public void StoreWave(string path)
        {
            System.IO.FileStream fs = System.IO.File.OpenWrite(path); // zu schreiben Wave Datei öffnen / erstellen
            StoreChunk(fs, "RIFF"); // RIFF Chunk schreiben
            StoreChunk(fs, "fmt "); // fmt Chunk schreiben
            StoreChunk(fs, "data"); // data Chunk schreiben
        }

        private void StoreChunk(System.IO.FileStream fs, string chunkID)
        {
            System.Text.ASCIIEncoding Decoder = new ASCIIEncoding();
            // den Namen in Bytes konvertieren und schreiben
            fs.Write(Decoder.GetBytes(chunkID), 0, 4);

            if (chunkID == "RIFF")
            {
                // im RIFF Chunk, FileSize als Größe und das Audioformat schreiben
                fs.Write(System.BitConverter.GetBytes(FileSize), 0, 4);
                fs.Write(Decoder.GetBytes(Format), 0, 4);
            }
            if (chunkID == "fmt ")
            {
                // beim fmt Chunk die Größe dieses sowie die weiteren kodierten Informationen schreiben
                fs.Write(System.BitConverter.GetBytes(FmtChunkSize), 0, 4);
                fs.Write(System.BitConverter.GetBytes(AudioFormat), 0, 2);
                fs.Write(System.BitConverter.GetBytes(NumChannels), 0, 2);
                fs.Write(System.BitConverter.GetBytes(SampleRate), 0, 4);
                fs.Write(System.BitConverter.GetBytes(ByteRate), 0, 4);
                fs.Write(System.BitConverter.GetBytes(BlockAlign), 0, 2);
                fs.Write(System.BitConverter.GetBytes(BitsPerSample), 0, 2);
            }
            if (chunkID == "data")
            {
                // beim data Chunk die Größe des Datenblocks als Größenangabe schreiben
                fs.Write(System.BitConverter.GetBytes(DataSize), 0, 4);
                // dann die einzelnen Amplituden, wie beschrieben Sample für Sample mit jeweils allen
                // Audiospuren, schreiben
                for (int i = 0; i < Data[0].Length; i++)
                {
                    for (int j = 0; j < NumChannels; j++)
                    {
                        fs.Write(System.BitConverter.GetBytes(Data[j][i]), 0, BlockAlign / NumChannels);
                    }
                }
            }
        }
        #endregion

        #region Mischen
        private static WaveFile MixWave(WaveFile wf1, WaveFile wf2, int fadeTime)
        {
            int FadeSamples = fadeTime * wf1.ByteRate / wf1.NumChannels; // Anzahl an aus-/ einzublenden Samples
            int FadeBytes = fadeTime * wf1.ByteRate; // Anzahl an aus-/ einzublendenden Bytes

            WaveFile Result = new WaveFile(); // Ergebnis Wave Datei
            Result.FileSize = wf1.FileSize + wf2.DataSize - 2 * FadeBytes; // neue Dateigröße
            Result.Format = "WAVE";

            // Informationen aus dem fmt Chunk übernehmen
            Result.FmtChunkSize = wf1.FmtChunkSize;
            Result.AudioFormat = wf1.AudioFormat;
            Result.NumChannels = wf1.NumChannels;
            Result.SampleRate = wf1.SampleRate;
            Result.ByteRate = wf1.ByteRate;
            Result.BlockAlign = wf1.BlockAlign;
            Result.BitsPerSample = wf1.BitsPerSample;

            Result.DataSize = wf1.DataSize + wf2.DataSize - 2 * FadeBytes; // neue Größe des Data Chunks
            Result.Data = new int[wf1.NumChannels][]; // Anzahl an Kanälen übernehmen
            int NumSamples = Result.DataSize / (Result.NumChannels * ( Result.BitsPerSample / 8)); // Anzahl an Samples ausrechnen, die sich in de Ergebnisdatei ergeben

            // Die Data Arrays für alle Kanäle auf die Anzahl der Samples dimensionieren.
            for (int i = 0; i < Result.Data.Length; i++)
            {
                Result.Data[i] = new int[NumSamples];
            }

            int PosCounter = 0; // Position des aktuellen Samples in der Ergebnisdatei

            // die Samples aus der ersten Wave Datei in das Data Feld der Ergebnisdatei kopieren
            for (int i = 0; i < wf1.Data[0].Length; i++)
            {
                // das aktuelle Sample in allen Kanälen übernehmen
                for (int j = 0; j < wf1.NumChannels; j++)
                {
                    // fällt das aktuelle Sample in die Zeit, die überblendet werden soll, den Amplitudenwert der 1. Datei mit dem Amplitudenwert aus der 2. Datei mischen
                    if (i > wf1.Data[0].Length - FadeSamples)
                       Result.Data[j][PosCounter] = (int)(wf1.Data[j][i] * Factor(i - (wf1.Data[0].Length - FadeSamples), FadeSamples, 0) + wf2.Data[j][i - (wf1.Data[0].Length - FadeSamples)] * Factor(i - (wf1.Data[0].Length - FadeSamples), FadeSamples, 1));
                    else
                       Result.Data[j][PosCounter] = wf1.Data[j][i];
                }
                PosCounter++;
            }

            // die restlichen Samples in die Ergebnisdatei übernehmen
            for (int i = FadeSamples; i < wf2.Data[0].Length; i++)
            {
                for (int j = 0; j < wf1.NumChannels; j++)
                {
                    Result.Data[j][PosCounter] = wf2.Data[j][i];
                }
                PosCounter++;
            }
            return Result;
        }
        
        /// <summary>
       /// Diese Funktion dient zur Berechnung der Gewichtung der Amplituden bei Übermischung.
        /// </summary>
        /// <param name="pos">Position in Datei relativ zum Anfang der Überblendezeit</param>
        /// <param name="max">Ende der Überblendung, relativ zu pos</param>
        /// <param name="song">Kann die Werte 0 (auszublendender Song) oder 1 (einzublender Song) annehmen</param>
        /// <returns></returns>
        private static double Factor(int pos, int max, int song)
        {
            if (song == 0)
                return 1 - Math.Pow((double)pos / (double)max, 2);
            else
                return Math.Pow((double)pos / (double)max, 2);
        }

        public static void StoreMixWave(string path, WaveFile wf1, WaveFile wf2, int fadeTime)
        {
            WaveFile Mixed = MixWave(wf1, wf2, fadeTime); // Ergebnisdatei mischen
            Mixed.StoreWave(path); // Ergebnisdatei auf Festplatte speichern
        }

        #endregion
    }