Silent Night v .NET

C# .NET Windows Forms Silent Night v .NET

Tak, dneska si uděláme blbinku, určitě máte radost :-). Blíží se vánoce a k vánocům patří koledy. Jedna z nejznámějších je od pana Grubera "Stille Nacht" alias "Tichá noc". Proč si ji tedy nezahrát a zároveň se nenaučit nové možnosti .NET?

silent nightMějme tedy takové zadání, třeba: Naprogramujte "klávesový nástroj" na počítači PC kde vstupem do programu budou stisknuté klávesy. Program podle stisknuté klávesy hraje příslušný tón ve stupnici C dur, dokud uživatel klávesu neuvolní nebo nestiskne jinou klávesu. Tóny budou přehrávané na standardní zvukový výstup (zvuková karta). Program bude na výstupu zobrazovat název tónu který přehrává, i jeho frekvenci. Poslední funčností bude uložit/nahrát skladbu(y) a umět skladby přehrát. Pro zjednodušení v jednom okamžiku hrajeme jeden tón, čili není možné hrát akordy (souznění více tonů).

Snad, se někomu nezježili vlasy na hlavě ze zadání?!?. Zkusme si tedy úlohu raději rozebrat a ujasnit. Dílčí problémy, které je potřeba řešit jsou asi tyto:

  1. Definice tónu, stupnice C dur.
  2. Jak přehrát zvuk s danou frekvencí a danou dobou trvání?
  3. Jak pracovat s klávesy (odchycení stisknuté, ukončení stisknutí)
  4. Jak realizovat odchycení doby trvání stisknuté klávesy.
  5. Jak přehrát zvukový záznam (skladbu).

ad 1.

Pokud si nevzpomeneme něco z hudební výchovy ze základní školy, tak máme dnes internet s obrovskou informační základnou. Tak např. nalezená tato definice tónu: Tónem rozumíme akustické vlnění určené výškou (frekvencí), délkou (dobou trvání), hlasitostí (amplitudou) a barvou (tvarem vlnění). A aniž bych tomu já zas tak rozuměl nalezl jsem tuto stránečku http://www.geocities.com/…my/tune.html kde je spousta wav souborů (samply tónu), takže nemusíme složitě programovat jednotlivé tóny. Máme soubory WAV, co soubor, to jeden tón specifické frekvence. Co se týče stupnice C dur je jednoduchá a obsahuje 7 tónů v neměnném pořadí dle frekvence (C, D, E, F, G, A, H). Tyto tóny tvoří jednu oktávu. Většina nástrojů má však více oktáv stejné stupnice, tzn. že se tóny opakují s vyšší frekvencí. Aby se tóny v oktávách rozlišily, jsou nějak označeny. My je odlišíme tak, že k názvu tónu přidáme čárku (první oktáva) dvě čárky (druhá oktáva)

ad 2.

Sám mám ještě v paměti Borladnovské funkce v Turbo Pascalu Delay() Sound() NoSound() na přehrání tónů, jak se ty časy mění :-). Ale teď máme přímo wav soubory, stačí jenom umět přehrát wav soubor. Jak jsem dlouho hledal tak jsem nenašel přímo třídu v .NET frameworku, která by byla schopná přehrát wav soubor. Zato jsem našel ale jiný způsob, jak do .NET aplikace dostat jakoukoliv jinou (starou) dll knihovnu. Podařilo se mi najít tento způsob přehrání wav souboru.

[DllImport("winmm.dll", SetLastError=true)]
private static extern bool PlaySound(string pszSound, uint hmod, uint fdwSound);

pozor, musíme doplnit name space System.Runtime­.InteropServi­ces;, který nám zaručí import funkcí ze starých externích dll souborů. Pozornost také věnujme výčtu (enum) pojmenovaného "SND". Jsou tam významy bitů, které funkce PlaySound akceptuje. My nastavuje bity (že se jedná o soubor, že požadujeme asynchroní volání, a že se má wav opakovat stále do kola když náhodou dohraje)

ad 3.

v prvé řadě se hlásíme k odběru událostí stisku a uvolnění kláves hlavního formuláře. Musime nastavit vlastnost KeyPreview, což zajistí aktivitu kláves, i když bude mít fokus jiný prvek na formuláři.

this.KeyPreview = true;
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.OnKeyDown);
this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnKeyUp);

int AKT_KEY = 0;

Dále je nutné si vytvořit globální proměnnou, kde si uchováváme kód stisknuté klávesy. Tuto proměnnou potom využíváme v metodách OnKeyDown OnKeyUp, zda se změnila klávesa od posledního stisku. Pokud je nová klávesa, hrajeme nový tón. Pokud klávesa byla upuštěna nehrajeme žádný tón a do AKT_KEY dosadíme hodnotu (0). Vlastní hraní provádí metoda Play

private void Play(bool Hrat, int code)

Metodě předáváme dva parametry. Hrat (True/False) zda ma hrát, nebo nehrát (zastavit dosavadni hraní) a kód klávesy (code). V těle je pak volána zmíněná metoda PlaySound z externího dll souboru. V metodě Play je ještě jedna složitá věcička (jako by toho nebylo málo :-). Volá čistě naší metodu BinSearch(code);

int IDX = BinSearch(code);

Tato metoda dělá jednu jedinou věc: Vrací index do pole "NOTY" vstupuje kód stisknuté klávesy. Proměnná "NOTY" je pole tříd "TON". Za pozornost stojí jak metoda vyhledává index v poli "NOTY". Využívá tzv. binárního vyhledávání. Základem je, že pole je setříděné (máme setříděno právě podle kódu klavesy). Algoritmus je asi takový .

  1. Skočíme do prostředka posloupnosti
  2. Pokud není dále co půlit KONEC -> hodnota nenalezena
  3. Pokud se hodnoty rovnají KONEC (nazenena hodnota) -> vrať index.
  4. Pokud je hledaná hodnota menší než aktuální prvek opakujeme postup (1) v horní části posloupnosti
  5. Pokud je hledaná hodnota větší než aktuální prvek opakujeme postup (1) v dolní části posloupnosti.

ad 4.

Co se týče časování, tak jsem nakonec použil nejjednodušší věc a to rozdíl dvou datumů. Starý datum uchováváme v promenně DT. V proměnné milisec vypočítáme rozdíl datumů v milisekundách. Hodnotu spolu s kódem klávesy ukládáme do globální poměnné. Data

short[] Data = new short[4096];
short Data_IDX = 0;
int milisec = 0;
DateTime DT = DateTime.Now;

milisec = ((int) (DateTime.Now - DT).TotalMilliseconds);
Data[Data_IDX] = (short) AKT_KEY;
Data[Data_IDX+1] = (short) milisec;
Data_IDX += 2;

ad 5.

přehrání nám provede následující kód, kdy procházíme celým pole proměnné Data a hrajeme příslušný tón. Pauzu v cyklu provedeme tak, že program "uspíme" na daný počet milisekund pro každou notu. Všimněme si že aplikace v tuto chvíli "zamrzne" nereaguje myš ani stisk kláves. Přehrávání by se mohlo řešit v samostatném vlákně... Pozor musíme uvést Namespace "using System.Threading;" pro práci s vlákny.

short code;
short time;
for (int i=0; i<Data_IDX; i+=2)
{
      code = Data[i];
      time = Data[i+1];

      Play(true, code);
      Thread.Sleep(time);

}
Play(false, 0);

Jeden snight.cs soubor zkompilujeme takto:

csc /t:winexe /r:System.dll /r:System.Xml.dll /r:System.Data.dll
     /r:System.Drawing.dll /r:system.windows.forms.dll snight.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;

namespace SilentNight
{

                /// <summary>
                /// Summary description for Form4.
                /// </summary>
        public class Form4 : System.Windows.Forms.Form
        {
                private System.Windows.Forms.Label label2;
                private System.Windows.Forms.Label INFO;

                short[] Data = new short[4096];
                short Data_IDX = 0;
                int milisec = 0;
                DateTime DT = DateTime.Now;

                private System.Windows.Forms.PictureBox Osnova;
                private System.Windows.Forms.Button KONEC;
                private System.Windows.Forms.PictureBox Nota;
                private System.Windows.Forms.Button bNew;
                private System.Windows.Forms.ComboBox SKLADBY;
                private System.Windows.Forms.Button bLoad;
                private System.Windows.Forms.Button bSave;

                struct TON
                {
                        public byte id;
                        public string noteName;
                        public int keyCode;
                        public string fileName;
                        public int Hz;

                                                // Konstruktor stuktury
                        public TON(byte ID, string NoteName, int KeyCode, string FileName, int _Hz)
                        {
                                id = ID;
                                noteName = NoteName;
                                keyCode = KeyCode;
                                fileName = FileName;
                                Hz = _Hz;
                        }
                }

                TON [] NOTY = {
                                  new TON ( 3, "E"", 69, "e4.wav", 330 ),
                                  new TON ( 8, "C""", 73, "c5.wav", 523 ),
                                  new TON ( 9, "D""", 79, "d5.wav", 587 ),
                                  new TON ( 10, "E""", 80, "e5.wav", 659 ),
                                  new TON ( 1, "C"", 81, "c4.wav", 262 ),
                                  new TON ( 4, "F"", 82, "f4.wav", 346 ),
                                  new TON ( 5, "G"", 84, "g4.wav", 392 ),
                                  new TON ( 7, "H"", 85, "b4.wav", 494 ),
                                  new TON ( 2, "D"", 87, "d4.wav", 294 ),
                                  new TON ( 6, "A"", 89, "a4.wav", 440 ),
                                  new TON ( 6, "A"", 90, "a4.wav", 440 ),
                                  new TON ( 11, "F""", 219, "f5.wav", 698 ),
                                  new TON ( 13, "A""", 220, "a5.wav", 880 ),
                                  new TON ( 12, "G""", 221, "g5.wav", 784 )
                               };


                public enum SND
                {
                        SND_SYNC         = 0x0000  ,/* play synchronously (default) */
                        SND_ASYNC        = 0x0001 , /* play asynchronously */
                        SND_NODEFAULT    = 0x0002 , /* silence (!default) if sound not found */
                        SND_MEMORY       = 0x0004 , /* pszSound points to a memory file */
                        SND_LOOP         = 0x0008 , /* loop the sound until next sndPlaySound */
                        SND_NOSTOP       = 0x0010 , /* don"t stop any currently playing sound */
                        SND_NOWAIT       = 0x00002000, /* don"t wait if the driver is busy */
                        SND_ALIAS        = 0x00010000 ,/* name is a registry alias */
                        SND_ALIAS_ID     = 0x00110000, /* alias is a pre d ID */
                        SND_FILENAME     = 0x00020000, /* name is file name */
                        SND_RESOURCE     = 0x00040004, /* name is resource name or atom */
                        SND_PURGE        = 0x0040,  /* purge non-static events for task */
                        SND_APPLICATION  = 0x0080 /* look for application specific association */
                }

                [DllImport("winmm.dll", SetLastError=true)]
                private static extern bool PlaySound(string pszSound, uint hmod, uint fdwSound);

                int AKT_KEY = 0;

                public Form4()
                {
                                                //
                                                // Required for Windows Form Designer support
                                                //
                        InitializeComponent();
                                                //
                                                // TODO: Add any constructor code after InitializeComponent call
                                                //
                }
                                #region Windows Form Designer generated code
                                /// <summary>
                                /// Required method for Designer support - do not modify
                                /// the contents of this method with the code editor.
                                /// </summary>
                private void InitializeComponent()
                {
                        System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form4));
                        this.label2 = new System.Windows.Forms.Label();
                        this.Osnova = new System.Windows.Forms.PictureBox();
                        this.KONEC = new System.Windows.Forms.Button();
                        this.INFO = new System.Windows.Forms.Label();
                        this.Nota = new System.Windows.Forms.PictureBox();
                        this.SKLADBY = new System.Windows.Forms.ComboBox();
                        this.bLoad = new System.Windows.Forms.Button();
                        this.bSave = new System.Windows.Forms.Button();
                        this.bNew = new System.Windows.Forms.Button();
                        this.SuspendLayout();
                                                //
                                                // label2
                                                //
                        this.label2.Location = new System.Drawing.Point(168, 88);
                        this.label2.Name = "label2";
                        this.label2.Size = new System.Drawing.Size(96, 8);
                        this.label2.TabIndex = 1;
                                                //
                                                // Osnova
                                                //
                        this.Osnova.Image = Image.FromFile("img/osnova.gif"@);
                        this.Osnova.Location = new System.Drawing.Point(8, 8);
                        this.Osnova.Name = "Osnova";
                        this.Osnova.Size = new System.Drawing.Size(160, 104);
                        this.Osnova.TabIndex = 5;
                        this.Osnova.TabStop = false;
                                                //
                                                // KONEC
                                                //
                        this.KONEC.Location = new System.Drawing.Point(224, 120);
                        this.KONEC.Name = "KONEC";
                        this.KONEC.TabIndex = 6;
                        this.KONEC.Text = "KONEC";
                        this.KONEC.Click += new System.EventHandler(this.KONEC_Click);
                                                //
                                                // INFO
                                                //
                        this.INFO.Location = new System.Drawing.Point(168, 8);
                        this.INFO.Name = "INFO";
                        this.INFO.Size = new System.Drawing.Size(128, 72);
                        this.INFO.TabIndex = 8;
                                                //
                                                // Nota
                                                //
                        this.Nota.Image = Image.FromFile("img/nota.gif"@);
                        this.Nota.Location = new System.Drawing.Point(88, 80);
                        this.Nota.Name = "Nota";
                        this.Nota.Size = new System.Drawing.Size(11, 7);
                        this.Nota.TabIndex = 9;
                        this.Nota.TabStop = false;
                        this.Nota.Visible = false;
                                                //
                                                // SKLADBY
                                                //
                        this.SKLADBY.DisplayMember = "1";
                        this.SKLADBY.Items.AddRange(new object[] {
                                                                      "SKLADBA 0",
                                                                      "SKLADBA 1",
                                                                      "SKLADBA 2",
                                                                      "SKLADBA 3",
                                                                      "SKLADBA 4",
                                                                      "SKLADBA 5",
                                                                      "SKLADBA 6",
                                                                      "SKLADBA 7",
                                                                      "SKLADBA 8"});
                        this.SKLADBY.Location = new System.Drawing.Point(8, 120);
                        this.SKLADBY.Name = "SKLADBY";
                        this.SKLADBY.Size = new System.Drawing.Size(80, 21);
                        this.SKLADBY.TabIndex = 10;
                        this.SKLADBY.Text = "comboBox1";
                                                //
                                                // bLoad
                                                //
                        this.bLoad.Location = new System.Drawing.Point(96, 120);
                        this.bLoad.Name = "bLoad";
                        this.bLoad.Size = new System.Drawing.Size(35, 23);
                        this.bLoad.TabIndex = 11;
                        this.bLoad.Text = "Hrej";
                        this.bLoad.Click += new System.EventHandler(this.bLoad_Click);
                                                //
                                                // bSave
                                                //
                        this.bSave.Location = new System.Drawing.Point(136, 120);
                        this.bSave.Name = "bSave";
                        this.bSave.Size = new System.Drawing.Size(35, 23);
                        this.bSave.TabIndex = 12;
                        this.bSave.Text = "Ulož";
                        this.bSave.Click += new System.EventHandler(this.bSave_Click);
                                                //
                                                // bNew
                                                //
                        this.bNew.Location = new System.Drawing.Point(176, 120);
                        this.bNew.Name = "bNew";
                        this.bNew.Size = new System.Drawing.Size(40, 23);
                        this.bNew.TabIndex = 13;
                        this.bNew.Text = "Nová";
                        this.bNew.Click += new System.EventHandler(this.bNew_Click);
                                                //
                                                // Form4
                                                //
                        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
                        this.ClientSize = new System.Drawing.Size(312, 149);
                        this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                    this.bNew,
                                                    this.bSave,
                                                    this.bLoad,
                                                    this.SKLADBY,
                                                    this.Nota,
                                                    this.INFO,
                                                    this.KONEC,
                                                    this.Osnova,
                                                    this.label2});
                        this.KeyPreview = true;
                        this.Name = "Form4";
                        this.Text = "Silent Night .NET";
                        this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.OnKeyDown);
                        this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnKeyUp);
                        this.ResumeLayout(false);

                }
                                #endregion
                                /// <summary>
                                /// The main entry point for the application.
                                /// </summary>
                [STAThread]
                static void Main()
                {
                        Application.Run(new Form4());
                }
                private void OnKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
                {

                        int code = e.KeyValue;

                                                // neji jiz jednou stisknuta ta sama klavesa ?
                        if (code != AKT_KEY)
                        {

                                milisec = ((int) (DateTime.Now - DT).TotalMilliseconds);
                                Data[Data_IDX] = (short) AKT_KEY;
                                Data[Data_IDX+1] = (short) milisec;
                                Data_IDX += 2;

                                                                // dosad kod zmackle klavesy
                                AKT_KEY = code;

                                DT = DateTime.Now;

                                                                // hraj ton
                                Play(true, code);

                        }
                        else
                        {
                                this.label2.Text += "|";
                        }
                }

                private void OnKeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
                {
                        int code = e.KeyValue;
                        if (AKT_KEY == code)
                        {

                                milisec = ((int) (DateTime.Now - DT).TotalMilliseconds);
                                Data[Data_IDX] = (short) AKT_KEY;
                                Data[Data_IDX+1] = (short) milisec;
                                Data_IDX += 2;

                                Play(false, code);
                                AKT_KEY = 0;
                                DT = DateTime.Now;

                                this.label2.Text = "";

                        }
                        else
                        {
                                this.label2.Text = "";
                        }
                }


                private void Play(bool Hrat, int code)
                {
                        if (Hrat)
                        {
                                                                // ziskej index do pole nami definovanych tonu
                                int IDX = BinSearch(code);

                                                                // nalezen ton odpovidaji kodu klavesy ?
                                if (IDX != -1)
                                {
                                        TON ton = (TON) NOTY[IDX];
                                        string FILE = "tony" + ton.fileName;@

                                        INFO.Text = "TÓN: " + ton.noteName + "
Pořadí: " + ton.id + "
Frekvence: " + ton.Hz + " Hz";
                                        Nota.Location = new System.Drawing.Point(85, 71 - ton.id * 4);
                                        Nota.Visible = true;

                                        PlaySound(FILE, 0, (int) (SND.SND_ASYNC | SND.SND_FILENAME | SND.SND_LOOP)).ToString();

                                }
                                else
                                {
                                        INFO.Text = "tón nedefinován" + code;
                                        Nota.Visible = false;
                                }

                        }
                        else
                        {
                                PlaySound(null, 0, (int) (SND.SND_ASYNC | SND.SND_FILENAME | SND.SND_LOOP)).ToString();
                                Nota.Visible = false;
                        }
                }


                private int BinSearch(int find)
                {
                                                // algoritmus binarniho vyhledavani

                        int s;
                        int a, b;

                        int code;

                                                // nejnizsi index pole-1
                        a = -1;
                                                // nejvyssi index pole+1
                        b = NOTY.Length;

                        while (a != b)
                        {
                                                                // v "s" bude stred dosavadniho intervalu
                                s = (b - a) / 2 + a;
                                code = ((TON) NOTY[s]).keyCode;

                                                                // porovnavame prostredni prvek s hledanou hodnotou
                                if (code == find)
                                {
                                        return s;
                                }

                                                                // dale se rozhodujeme kterou z "delenych pulek" se vydame v dalsich krocich
                                if (code > find)
                                {
                                        b = s;
                                }
                                else
                                {
                                        a = s;
                                }

                                                                // nezbytna podminka k ukonceni (pokud by zustali indexy blizko sebe napr 4 a 5 celociselnym delenim by jsme nenasli prostredek pole)
                                if (a+1 == b) a++;

                        }

                                                // prvek nenalezen
                        return -1;
                }

                private void KONEC_Click(object sender, System.EventArgs e)
                {
                        Play(false, 0);
                        Application.Exit();
                }


                private void bNew_Click(object sender, System.EventArgs e)
                {
                        Data_IDX = 0;
                        AKT_KEY = 0;
                        DT = DateTime.Now;
                }

                private void bLoad_Click(object sender, System.EventArgs e)
                {


                        if (SKLADBY.SelectedIndex != -1)
                        {
                                string F = SKLADBY.SelectedIndex.ToString();

                                FileStream fs = File.Open("music"+ F +".sav", FileMode.Open);
                                Data_IDX = (short) (fs.Length / 2);
                                for (int i=0; i<Data_IDX; i++)
                                {
                                        Data[i] = (short) (fs.ReadByte() | (fs.ReadByte() << 8));
                                }
                                fs.Close();
                        }

                        short code;
                        short time;
                        for (int i=0; i<Data_IDX; i+=2)
                        {
                                code = Data[i];
                                time = Data[i+1];

                                Play(true, code);
                                Thread.Sleep(time);

                        }
                        Play(false, 0);
                }


                private void bSave_Click(object sender, System.EventArgs e)
                {
                        string F = SKLADBY.SelectedIndex.ToString();

                        FileStream fs = File.Create("music"+ F +".sav");
                        for (int i=0; i<Data_IDX; i++)
                        {
                                fs.WriteByte((byte) (Data[i] & 0xff));
                                fs.WriteByte((byte) (Data[i] >> 8));
                        }
                        fs.Close();
                }


        }
}

Tak dneska to bylo obtížné, ale snažil jsem se, popsat to co nejsrozumitelněji. Určitě je někde v programu chyba :-), takže to neberte za hotovou aplikaci ale spíše jako nakopnutí a pokračování. Využití programu je poněkud sporadické snad jenom pro začátečníky (v hudbě) nebo pro toho kdo nevydrží nehrát ani minutu a na cestě vlakem může místo pozorování přírody vytáhnou notesa a cvičit skladby. Přece jenom klasické klávesy mají více kláves než klávesnice počítače, více barev tonů (tony flétny, kytary, trumpety, ...) takže náhrada asi nehrozí, třebas se ale program někomu z Vás podaří dotáhnou dál...


 

Stáhnout

Staženo 472x (58.24 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal Michael Stavěla
Avatar
Jméno: Michael Stavěla, místo nejčastějšího pobytu: Morava, vzdělání technicko-ekonomické, záliby: rekreační sport (floorball, cyklistika, plavání), programování, šifry, příroda – poznávání nových míst, hlavně ne rutina – ať se něco děje, čím víc zm...

Jak se ti líbí článek?
Celkem (3 hlasů) :
4.333334.333334.333334.33333 4.33333


 


Miniatura
Předchozí článek
8 dam Windows .NET aplikace
Miniatura
Všechny články v sekci
Okenní aplikace v C#
Miniatura
Následující článek
ConsoleExtra

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!