Silent Night v .NET
V predchádzajúcom cvičení, Cvičenie 2 - Dátové typy, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
Tak, dnes si urobíme blbosti, určite máte radosť . Blíži sa vianoce a k Vianociam patrí koledy. Jedna z najznámejších je od pána Grubera "Stille Nacht" alias "Tichá noc". Prečo si ju teda nezahrať a zároveň sa nenaučit nové možnosti .NET?
Majme teda také zadanie, treba: Naprogramujte "klávesový nástroj" na
počítači PC kde vstupom do programu budú stlačené klávesy. Program podľa
stlačené klávesy hrá príslušný tón v stupnici C dur, kým používateľ
kláves neuvoľní alebo nestlačí inú kláves. Tóny budú prehrávané na
štandardný zvukový výstup (zvuková karta). Program bude na výstupe
zobrazovať názov tónu ktorý prehráva, aj jeho frekvenciu. Posledné
funčnosti bude uložiť / nahrať skladbu (y) a vedieť skladby prehrať. Pre
zjednodušenie v jednom okamihu hráme jeden tón, čiže nie je možné hrať
akordy (súznenie viac tónov).
Snáď, sa niekomu nezježili vlasy na hlave zo zadania?!?. Skúsme si teda úlohu radšej rozobrať a ujasniť. Čiastkové problémy, ktoré je potrebné riešiť sú asi tieto:
- Definícia tónu, stupnice C dur.
- Ako prehrať zvuk s danou frekvenciou a danú dobou trvania?
- Ako pracovať s klávesy (odchytenie stlačené, ukončenie stlačenie)
- Ako realizovať odchytenie doby trvania stlačené klávesy.
- Ako prehrať zvukový záznam (skladbu).
ad 1.
Ak si nespomenieme niečo z hudobnej výchovy zo základnej školy, tak máme dnes internet s obrovskou informačný základňou. Tak napr. Nájdená táto definícia tónu: Tónom rozumieme akustické vlnenie určené výškou (frekvenciou), dĺžkou (dobou trvania), hlasitosťou (amplitúdou) a farbou (tvarom vlnenie). A bez toho aby som tomu ja zas tak rozumel našiel som túto Stránecká http://www.geocities.com/...my/tune.html kde je veľa wav súborov (sample tónu), takže nemusíme zložito programovať jednotlivé tóny. Máme súbory WAV, čo súbor, to jeden tón špecifické frekvencie. Čo sa týka stupnice C dur je jednoduchá a obsahuje 7 tónov v nemennom poradí podľa frekvencie (C, D, E, F, G, A, H). Tieto tóny tvoria jednu oktávu. Väčšina nástrojov má však viac oktáv rovnaké stupnice, tzn. že sa tóny opakujú s vyššou frekvenciou. Aby sa tóny v oktávach rozpoznať, sú nejako označené. My ich odlíšime tak, že k názvu tónu pridáme čiarku (prvé oktáva) dve čiarky (druhá oktáva)
ad 2.
Sám mám ešte v pamäti Borladnovské funkcie v Turbo Pascal Delay () Sound () nosound () na prehranie tónov, ako sa tie časy menia . Ale teraz máme priamo wav súbory, stačí len vedieť prehrať wav súbor. Ako som dlho hľadal tak som nenašiel priamo triedu v .NET frameworku, ktorá by bola schopná prehrať wav súbor. Zato som našiel ale iný spôsob, ako do .NET aplikácie dostať akúkoľvek inú (starú) dll knižnicu. Podarilo sa mi nájsť tento spôsob prehranie wav súboru.
[DllImport("winmm.dll", SetLastError=true)] private static extern bool PlaySound(string pszSound, uint hmod, uint fdwSound);
pozor, musíme doplniť name space System.Runtime.InteropServices ;, ktorý nám zaručí import funkcií zo starých externých dll súborov. Pozornosť tiež venujme zoznamu (enum) pomenovaného "SND". Sú tam významy bitov, ktoré funkcie PlaySound akceptuje. My nastavuje bity (že sa jedná o súbor, že požadujeme asynchroní volanie, a že sa má wav opakovať stále do kola keď náhodou dohrá)
ad 3.
v prvom rade sa hlásime k odberu udalostí stlačenia a uvoľnenia klávesov hlavného formulára. Musime nastaviť vlastnosť KeyPreview, čo zaistí aktivitu klávesov, aj keď bude mať fokus iný prvok na formulári.
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;
Ďalej je nutné si vytvoriť globálnu premennú, kde si uchovávame kód stlačené klávesy. Túto premennú potom využívame v metódach OnKeyDown OnKeyUp, či sa zmenila klávesa od posledného stlačenia. Ak je nová klávesa, hráme nový tón. Ak klávesa bola spadla nehráme žiadny tón a do AKT_KEY dosadíme hodnotu (0). Vlastné hranie vykonáva metóda Play
private void Play(bool Hrat, int code)
Metóde odovzdávame dva parametre. Hrat (True / False) či ma hrať, alebo nehrať (zastaviť doterajšej hranie) a kód klávesy (code). V tele je potom volaná spomínaná metóda PlaySound z externého dll súboru. V metóde Play je ešte jedna zložitá vecička (ako by toho nebolo málo . Volá čisto našej metódu BinSearch (code);
int IDX = BinSearch(code);
Táto metóda robí jednu jedinú vec: Vracia index do poľa "NOTY" nadobúda kód stlačené klávesy. Premenná "NOTY" je pole tried "TON". Za pozornosť stojí ako metóda vyhľadáva index v poli "NOTY". Využíva tzv. Binárneho vyhľadávania. Základom je, že pole je zotriedené (máme zotriedené práve podľa kódu klavesy). Algoritmus je asi taký.
- Skočíme do prostredku postupnosti
- Pokiaľ nie je ďalej čo poliť KONIEC -> hodnota nenájdená
- Ak sa hodnoty rovnajú KONIEC (nazenena hodnota) -> vráť index.
- Ak je hľadaná hodnota menšia ako aktuálna prvok opakujeme postup (1) v hornej časti postupnosti
- Ak je hľadaná hodnota väčšia ako aktuálny prvok opakujeme postup (1) v dolnej časti postupnosti.
ad 4.
Čo sa týka časovanie, tak som nakoniec použil najjednoduchšia vec a to rozdiel dvoch dátumov. Starý dátum uchovávame v premennej DT. V premennej MILISEC vypočítame rozdiel dátumov v milisekundách. Hodnotu spolu s kódom klávesy ukladáme do globálnej poměnné. dáta
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.
prehranie nám vykoná nasledujúci kód, kedy prechádzame celým poľa premenné Dáta a hráme príslušný tón. Pauzu v cykle vykonáme tak, že program "uspí" na daný počet milisekúnd pre každú notu. Všimnime si že aplikácia v túto chvíľu "zamrzne" nereaguje myš ani stisk klávesov. Prehrávanie by sa mohlo riešiť v samostatnom vlákne ... Pozor musíme uviesť Namespace "using System.Threading;" pre prácu s vláknami.
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 súbor skompilujeme 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 dnes to bolo ťažké, ale snažil som sa, popísať to čo najzrozumiteľnejšie. Určite je niekde v programe chyba , Takže to neberte za hotovú aplikáciu ale skôr ako nakopnutie a pokračovanie. Využitie programu je trochu sporadické snáď len pre začiatočníkov (v hudbe) alebo pre toho kto nevydrží nehrať ani minútu a na ceste vlakom môže namiesto pozorovanie prírody vytiahnu Notes a cvičiť skladby. Predsa len klasické klávesy majú viac klávesov ako klávesnica počítača, viac farieb tónov (tony flauty, gitary, trumpety, ...) takže náhrada asi nehrozí, hoci sa ale program niekomu z Vás podarí dotiahnu ďalej ...
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 504x (58.24 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#