79
SVEUČILIŠTE U ZAGREBU FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA DIPLOMSKI RAD br. 1780 Korištenje zvuka u Java aplikacijama Nenad Pećanac

Nenad Pećanac - Diplomski rad - Ruđer Bošković Institute · Web viewDenon, Mitsubishi i Soundstream koji su koristili PCM tehnologiju zapisivanja. Digitalni zapisi spremali su

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Nenad Pećanac - Diplomski rad

SVEUČILIŠTE U ZAGREBU

FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA

DIPLOMSKI RAD br. 1780

Korištenje zvuka u Java aplikacijama

Nenad Pećanac

Zagreb, prosinac 2008.

Sadržaj

11.Uvod

32.Opis korištenih tehnologija

32.1.Programski jezik Java

32.1.1.Razvoj jezika i njegova namjena

42.1.2.Java danas

42.1.3.Zvučna podrška

52.2.Java Sound API

52.2.1.Koncept, namjena i performanse

62.2.2.Funkcionalnost

72.2.3.Digitalni zapis zvuka

82.2.4.Format zvučnog zapisa

82.2.5.Format zvučne datoteke

92.2.6.Zvučna konfiguracija

112.2.7.MIDI protokol

132.2.8.Dizajn i programska struktura MIDI modula

162.2.9.Korištenje MIDI modula

202.3.Swing

202.3.1.Razvoj i namjena

202.3.2.Arhitektura

253.Opis aplikacije

253.1.Koncept, namjena i dizajn

263.2.Glavni modul

263.2.1.Glavni izbornik

303.2.2.Preglednik instrumenata

323.3.Glavni sekvencer

343.4.Sporedni sekvencer

373.5.Kontrolni modul

384.Programska izvedba aplikacije

384.1.Pregled i organizacija koda

384.1.1.Klase grafičkog sučelja

404.1.2.Zvučne klase

404.1.3.Pomoćne klase

414.2.Implementacija zvučnog sustava

414.2.1.Klasa SynthesizerWrapper

434.2.2.Klasa SequencerWrapper

464.3.Implementacija grafičkog sučelja

474.3.1.Izvedba glavnog modula

504.3.2.Izvedba osnovnog sekvencerskog modula

554.3.3.Izvedba glavnog sekvencerskog modula

604.3.4.Izvedba sporednog sekvencerskog modula

644.3.5.Izvedba kontrolnog modula

665.Literatura

676.Zaključak

1. Uvod

Digitalno zapisivanje, reprodukcija i obrada zvuka u današnje vrijeme čine osnovnu studijsku tehnologiju za rukovanje zvučnim zapisima. Napretkom računalne tehnologije, u 90-tim godinama 20. stoljeća digitalna obrada zvuka se ubrzano razvila, te preuzela većinski udio u zastupljenosti na svjetskom tržištu nad analognim tehnologijama.

Prvi praktični uređaj za snimanje i reprodukciju zvučnog zapisa izumio i patentirao je Thomas Edison 1878. godine. [1] Bio je to mehanički uređaj nazvan „fonografski cilindar“. Radio je na principu igle koja je bila pričvršćena na membranu, čije je vibracije prenosila na voštani cilindar. Uređaj je brzo usavršen i rasprostranjen širom svijeta. Ovim izumom čovjeku je prvi put dana mogućnost da uhvati i spremi zvuk, te ga reproducira po želji. Snimanjem zvuka, distribucijom i prodajom zvučnih zapisa započeta je nova, brzo rastuća grana industrije. Do 1910. godine snimljeno je i rasprodano nekoliko milijuna naslova. Uređaj je uskoro zamijenjen gramofonom, kojeg je izumio Emile Berliner 1887. godine [2], a industrija se nastavila sve brže razvijati. Mehanički proces snimanja zvuka ubrzo je zamijenjen električnim.

Analogno snimanje zvuka započeli su 1920-tih godina inženjeri američke tvrtke Bell Telephone Laboratories usavršavanjem uređaja nazvanog mikrofon, kojeg je također izumio Emile Berliner 1876. godine. Tvrtka je izum otkupila za potrebe telefonske industrije. Analogni proces snimanja zvuka upotpunjen je izumom magnetofona njemačke tvrtke AEG 1935. godine. 1948. godine američki izumitelj Les Paul izumio je višekanalni magnetofon [3] koji je postao glavni alat za studijsko snimanje i montažu zvuka sve do 90-tih godina 20. stoljeća, kada je zamijenjen računalima. Ovim izumom postalo je moguće napredno manipuliranje slijedom zvučnih zapisa, spajanje zvučnih dionica i kreiranje zvučnih sekvenci.

Digitalno zapisivanje zvuka postalo je moguće tek 60-tih godina dvadesetog stoljeća. Proces pretvorbe analognog zvučnog signala u digitalni oblik pod nazivom PCM (eng. Pulse-Code Modulation) prvi je istražio i patentirao britanski znanstvenik Alec Reeves još 1937. godine. Nakon objave njegovog rada američka tvrtka Bell Telephone Laboratories počela je raditi na implementaciji procesa. 1957. godine njihov inženjer Max Mathews razvio je sustav za snimanje zvuka uz pomoć računala. Od 1970. godine na tržištu su se pojavili višekanalni digitalni snimači zvuka tvrtki Denon, Mitsubishi i Soundstream koji su koristili PCM tehnologiju zapisivanja. Digitalni zapisi spremali su se i dalje na magnetske trake, a najpoznatiji tip takvog medija bile su DAT (eng. Digital Audio Tape) trake, često korištene u studijske svrhe.

70-tih godina 20. stoljeća na tržištu su se također počeli pojavljivati elektronički instrumenti, kao što su sintesajzeri i sekvenceri. 80-tih godina takvi uređaji činili su standardnu opremu glazbenih studija. Uskoro, u zvučna studija su počela ulaziti i računala, koja su se spajala sa klavijaturama i ostalim elektroničkim komponentama korištenjem MIDI tehnologije (eng. Musical Instrument Digital Interface). [4] Ova tehnologija predstavljena je 1983. godine i uvela je u upotrebu industrijski standardiziran protokol za povezivanje, kontrolu i sinkroniziranje elektroničkih uređaja koji se održao do danas. Protokol je definiran na način da uređaji među sobom izmjenjuju MIDI poruke, koje sadrže instrukcije za izvođenje operacija koje uređaji trebaju obaviti.

Početkom 90-tih godina za zapisivanje zvuka su se počeli koristiti računalni tvrdi diskovi. Zvuk se u početku spremao u sirovim AIFF ili WAV formatima, a kasnije su uvedeni komprimirani formati kao što su MP3, WMA i OGG. Nakon što su računalni tvrdi diskovi dostigli dovoljne veličine, a procesori dovoljne brzine, otvorila se mogućnost za efikasno spremanje i rukovanje sa velikim zvučnim zapisima. Ubrzo, računala su postala osnovni alat svakog glazbenog studija. Današnji profesionalni programski alati kao što su Cubase, Protools, Logic, Acid, Nuendo ili Sonar korisniku nude funkcionalnosti cijelog glazbenog studija u jednoj aplikaciji, koja se uz prikladnu zvučnu podršku može koristiti na prosječnom korisničkom osobnom računalu. Takvi programi rezultat su dugogodišnjeg razvoja i napisani su u programskim jezicima C i C++. Od nedavno, napretkom i optimizacijom programskog jezika Java, uz korištenje Java Sound API biblioteke, postalo je moguće razvijati slične prenosive aplikacije prihvatljivih performansi.

2. Opis korištenih tehnologija2.1. Programski jezik Java

2.1.1. Razvoj jezika i njegova namjena

Programski jezik Java osmišljen je u tvrtci Sun Microsystems početkom 90-tih godina prošlog stoljeća. Razvoj jezika počeo je 1990. godine kao Stealth project (eng. nevidljivi, prikriveni projekt), poslije nazvan Green project (eng. zeleni projekt), a vodili su ga Bill Joy, Patrick Naughton, Mike Sheridan i James Gosling. U početku, Java je bila poznata pod nazivom Oak (eng. hrast) [5], koji joj je dao Gosling prema starom hrastu koji je rastao u dvorištu ispred prozora njegove radne sobe. Od tog naziva kasnije se ipak odustalo zbog problema oko autorskih prava, nakon čega je usvojeno ime Java. Cilj Sun-ovog tajnog projekta bio je predviđanje smjera razvoja računarske znanosti i smišljanje načina kako ga uhvatiti.

Budućnost se očekivala u vidu nastavka minijaturizacije računalnih komponenti i postupnom proširenju računarske znanosti na područje već postojećih elektroničkih uređaja, kao što su npr. kućanski aparati. Početna ideja projekta bila je povezivanje mnoštva elektroničkih naprava sa jednim centralnim uređajem, nalik daljinskom upravljaču, odnosno gledano općenito, izgradnja sustava koji će omogućiti mrežno povezivanje i međusobnu komunikaciju velikog broja različitih elektroničkih uređaja. Da bi to postalo moguće, trebalo je razviti novi hardver, naći prikladni programski jezik, te dizajnirati i realizirati operacijski sustav sa grafičkim sučeljem, pristupačan korisniku. Odlučeno je da niti jedan programski jezik do tada, pa ni C++ ne zadovoljava potrebne uvjete za razvoj takvog sustava, te se počelo raditi na novom jeziku, Oak-u.

Zahtjevi koje je taj novi jezik trebao ispuniti bili su:

1) jednostavnost (jezik treba biti široko rasprostranjen, pa zato ne smije biti suviše složen),

2) pouzdanost (blokiranje i ponovno pokretanje elektroničkih uređaja su nedopustivi),

3) sigurnost (mrežna komunikacija mora biti zaštićena),

4) prenosivost (jezik je potrebno izvoditi na različitim uređajima, pa mora biti višeplatformski).

Tri godine nakon početka projekta, predstavljen je uređaj „*7“ koji je bio prethodnik današnjih PDA uređaja. On je unatoč svom revolucionarnom dizajnu doživio neuspjeh na tržištu, jer je bilo prerano za takav tip tehnologije.

Nakon početnog neuspjeha, Javu se probalo iskoristiti za potrebe kabelske televizije, no tamo je također doživjela neuspjeh pred već postojećim sustavom. Pojavom Interneta, u Sun-u su shvatili da je Java po svojim karakteristikama savršen izbor za izradu web aplikacija.

Java je prilagođena novoj primjeni i službeno izdana 1995. godine. Omogućila je izradu dinamičkih web stranica i složenih web aplikacija, neovisno o platformi na kojoj se izvodi, te je vrlo brzo postala sveprisutna na Internetu. Idući korak bio je ulazak na tržište mobilnih telefona što se također pokazalo uspješnim.

2.1.2. Java danas

Statistike kažu [6] da otprilike 4.5 milijuna programera trenutno radi u Javi, da je vrijednost poslova obavljenih upotrebom Java tehnologija oko 100 milijardi dolara godišnje, da se tržište igara na mobilnim telefonima procjenjuje na oko 3 milijarde dolara, te da 70% planiranih bežičnih aplikacija koristi Javu.

Budući da je namijenjena svijetu Interneta i multimedijskoj upotrebi, Java od svojih najranijih verzija sadrži podršku za zvuk, u obliku biblioteke pod nazivom Java Sound API, uz koju je potom dodana i podrška za video i 3D grafiku.

2.1.3. Zvučna podrška

U verziji Jave 1.02 u jezik je uvedena biblioteka Java Sound API. Korištenjem prvih verzija te biblioteke bilo moguće reproducirati tek jednostavne 8 kHz zvukove zapisane u AU formatu, no situacija se bitno izmijenila u verziji 1.2 kad je u Javinu zvučnu podršku uvršten HeadSpace Audio Engine tvrtke Beatnik Corporation. [7] Uz upotrebu istih sučelja, programerima je dana mogućnost korištenja AU, WAV, AIFF, MIDI, i RMF zvukova. Iako je kvaliteta zvuka poboljšana do CD kvalitete, (do frekvencije uzorkovanja od 44 kHz), nije postojao način programske kontrole kao što je pauziranje i nastavljanje reprodukcije zvuka, prikazivanje trenutnog položaja ili dobivanje obavijesti da je reprodukcija gotova. Java Sound API pružao je programerima tek osnovnu funkcionalnost.

U verziji Jave 1.3 ti nedostatci su ispravljeni. Do verzije 1.5 uklonjeno je dosta grešaka i implementirane su nove, optimizirane osnovne funkcije napisane u C-u. Time je omogućena brza i efikasna podrška za razvoj programa koji zahtijevaju kratak odziv od zvučnog sustava i namijenjeni su obavljanju velikog broja složenih zvučnih operacija koje se trebaju izvršavati u realnom vremenu.

U trenutnoj verziji Jave 1.6, Java Sound API je stabilna i zaokružena cjelina, namijenjena visokom stupnju kontrole nad zvukom i upotrebi u bilo kakvoj vrsti multimedijskih aplikacija, od jednostavnih korisničkih programa do zahtjevnih profesionalnih sustava.

2.2. Java Sound API

2.2.1. Koncept, namjena i performanse

Java Sound API je programski okvir niske razine koji pruža mogućnosti za neposredno kontroliranje i obradu ulaznih i izlaznih zvučnih zapisa u audio ili u MIDI formatu. Osim što nudi izravnu kontrolu nad zvukom, Java Sound API je dizajniran sa ciljem pružanja proširivosti i fleksibilnosti.

Java Sound API pruža najniži mogući nivo kontrole nad zvukom na Java platformi. Dijelovi API-ja su mehanizmi preko kojih je moguće programski pristupiti ili rukovati sistemskim resursima kao što su audio mikseri, MIDI sintesajzeri i ostali MIDI uređaji, čitači, snimači, pretvornici zvučnih zapisa i slično. Java Sound API ne sadrži sofisticirane zvučne editore ili grafičke alate, ali pruža mogućnosti na osnovu kojih se takvi programi mogu izgraditi. API naglašava kontrolu zvuka na niskom nivou nad onom koju korisnici tipično očekuju.

U Javi također postoje API-ji viših razina koji sadrže elemente usko vezane uz zvučne operacije. Jedan od takvih je JMF (eng. Java Media Framework), dostupan kao standardna ekspanzija Java platforme. JMF predstavlja ujedinjenu arhitekturu, komunikacijski protokol i programsko sučelje za snimanje i reprodukciju audio i video zapisa. Na ovaj način programerima se nudi jednostavna osnova za izgradnju programa za reprodukciju multimedijskih sadržaja. S druge strane, ako je potrebno izgraditi sofisticiraniju aplikaciju sa naprednijim opcijama kao što su reprodukcija segmentiranih ili velikih zvučnih zapisa, upotreba audio efekata ili manipulacija nad MIDI zapisom, Java Sound API nudi primjereniji pristup zbog većeg nivoa kontrole.

Java Sound API sastavljen je iz skupa brzih, optimiranih osnovnih funkcija napisanih u C/C++ kodu. Takve funkcije pišu se u proizvoljnom jeziku i provode se u binarni kod spreman za izvođenje na ciljanoj platformi. Na taj način se zaobilazi interpretiranje međukoda i eliminira moguće kašnjenje unutar JVM-a (eng. Java Virtual Machine), te izravno pristupa resursima sustava na niskoj razini (eng. Low level programing). Tako su na Windowsima osnovne funkcije napisane i prevedene uz pomoć Direct Sound-a, a na Linuxu uz pomoć ALSA-e. Ovakav pristup upotrijebljen je radi ubrzanja rada Java Sound API-ja, a dobivene performanse su u rangu sa klasičnim C/C++ kodom. Vrijeme učitavanja aplikacije također je skraćeno u odnosu na običan Java kod jer su sve funkcije za upravljanje zvukom unaprijed prevedene, pa se kašnjenje uglavnom odnosi na ostale Java klase, kao što su npr. klase grafičkog sučelja, klase za mrežnu komunikaciju i slično [8].

Mogućnosti i performanse Java Sound API-ja dobro demonstrira mnoštvo besplatnih aplikacija dostupnih na Internetu [9].

2.2.2. Funkcionalnost

Java Sound API sastoji se od dva funkcionalno odvojena modula. Jedan pruža audio, a drugi MIDI podršku. Svaki modul podržan je unutar svog paketa:

1) javax.sound.sampled - paket specificira sučelja za snimanje, miješanje i reprodukciju snimljenog zvuka,

2) javax.sound.midi - paket specificira sučelja za MIDI sintezu, rukovanje MIDI sekvencama i upravljanje MIDI događajima.

Osim ova dva paketa, Java Sound API još čine paketi javax.sound.sampled.spi i javax.sound.midi.spi koji sadrže servise, tj. apstraktne klase i sučelja preko kojih je moguće uvesti dodatne komponente i funkcionalnosti unutar API-ja.

2.2.3. Digitalni zapis zvuka

Paket javax.sound.sampled pruža mogućnosti za upravljanje digitalno zapisanim zvukom, koji se unutar Java Sound API-ja naziva uzorkovani zvuk (eng. sampled). Pod tim nazivom se podrazumijeva bilo kakav tip digitalnog zapisanog zvuka. Uzorci zvuka predstavljaju uzastopne slike zvučnog signala. Kad se zvuk zapisuje u analognom obliku, koristi se mikrofon, koji zvučne valove pretvara u analogne električne signale valnog oblika. Kad se zvuk prebacuje iz analognog zapisa u digitalni, koristi se AD (analogno-digitalno) sklopovlje koje električne signale pretvara u digitalne informacije postupkom uzorkovanja. Postupak [9] je ilustriran na slijedećoj slici:

Slika 1. Uzorkovanje zvučnog signala

Zvučni se signal uzorkuje na način da se u skladu sa odabranom frekvencijom uzorkovanja periodički zapisuje slika zvučnog signala, tj. iznos amplitude električnog vala kojim je predstavljen zvuk. Na gornjoj slici, plavim su točkicama označene vrijednosti zvučnog signala predstavljenog crvenom krivuljom u trenutcima uzorkovanja. Što je frekvencija uzorkovanja veća, to će zapis zvuka biti vjerniji. Ako koristimo današnju CD kvalitetu zapisa zvuka, sa frekvencijom od 44 100 Hz, to znači da 44 100 puta u sekundi zapisujemo vrijednost zvučnog signala.

Osim frekvencije uzorkovanja, za kvalitetu zapisa zvuka je bitna još i kvantizacija, tj. rezolucija zvučnog zapisa koja određuje broj bitova kojima se zapisuje iznos amplitude zvuka. Što je iznos kvantizacije veći, kvaliteta zapisa je bolja. Danas se zvuk većinom zapisuje sa 16, 24 i 32 bita.

Java Sound API generalno razlikuje dvije vrste formata zvučnih podataka. Prvu vrstu predstavlja format koji se koristi za zvučne zapise, a drugu format kojim se zapisuju zvučne datoteke.

2.2.4. Format zvučnog zapisa

Ovaj format sadrži podatke koji predstavljaju atribute promatranog zvučnog zapisa. Unutar API-ja predstavlja se objektom tipa AudioFormat koji sadrži podatke o:

1) tehnici kodiranja (često PCM),

2) broju kanala (1 za mono, 2 za stereo, ili više za višekanalni zapis kao surround 5.1),

3) frekvenciji uzorkovanja (eng. sample rate),

4) kvantizaciji,

5) frekvenciji prikaza (eng. frame rate),

6) veličini prozora, u bajtovima (eng. frame size) – ovdje se izraz prozor odnosi na količinu informacija svih zvučnih kanala koji čine promatrani zvuk u danom trenutku, izražen u bajtovima,

7) poretku bajtova (big endian / little endian).

2.2.5. Format zvučne datoteke

Dok prethodni oblik formata direktno opisuje zapis zvuka, ovaj format specificira strukturu datoteke u koju se takvi podaci spremaju. Osim zvučnih podataka, u zvučnim datotekama se često nalaze i druge informacije kao npr. podaci o autoru, atributi zapisa, kao što su trajanje, vrsta kodiranja i slično. Takvi dodatni podaci obično se zapisuju u zaglavljima zvučnih datoteka.

Java Sound API format zapisa zvučne datoteke predstavlja objektom tipa AudioFileFormat koji sadrži:

1) tip datoteke (WAVE, AIFF itd.),

2) dužinu datoteke izraženu u bajtovima,

3) dužinu datoteke izraženu u prozorima,

4) prethodno opisani AudioFormat objekt sa atributima zvuka.

2.2.6. Zvučna konfiguracija

Da bismo mogli iskoristiti dani zvučni zapis unutar nekog sustava, potreban nam je zvučni uređaj koji možemo upotrijebiti za upravljanje sa zvukom. U Java Sound API-ju svaki zvučni uređaj predstavljen je mikserom. Programski mikser je koncept koji potječe iz koncepta audio miksera.

Svrha miksera je upravljanje s jednim ili više tokova zvučnih ulaza, te jednim ili više tokova zvučnih izlaza podataka. Programski mikser često predstavlja implementaciju sučelja uređaja kao što je zvučna kartica. Svaki ulaz i izlaz na zvučnoj kartici predstavljeni su ulaznim i izlaznim priključkom na njenom mikseru. Audio mikser ima kanale ili linije signala, pri čemu svaka linija predstavlja tok jednog zvučnog signala (obično stereo signal). U svaki kanal dakle ide jedan izvor zvuka, kao što je npr. mikrofon, gitara, bubanj, gramofon itd. Svaki kanal ima potenciometre, rotacijske i klizne, preko kojih je moguće podesiti nivo i filtriranje zvuka, dodati efekte kao što je npr. reverb efekt, koji daje prostornost zvuku.

Svrha audio miksera je spajanje više ulaznih zvučnih signala u jedan ili više izlaznih, koji se onda preko izlaznih priključaka mogu slati u uređaje za snimanje i reprodukciju zvuka. Mikser je dakle središnja komponenta svake audio konfiguracije. U Java Sound API-ju se, analogno tome, svakom zvučnom konfiguracijom upravlja pomoću jednog ili više miksera.

Primjer audio miksera prikazan je na slijedećoj slici:

Slika 2. Primjer audio miksera

Java Sound API dizajniran je na način koji ne pretpostavlja predefiniranu zvučnu konfiguraciju i dopušta proizvoljan broj različitih audio komponenti unutar sustava. Podržane su uobičajene operacije za rukovanje sa ulaznim i izlaznim zvučnim signalima kao i miješanje višestrukih tokova zvuka. Primjer jedne tipične zvučne konfiguracije prikazan je na slijedećoj slici:

Slika 3. Primjer zvučne konfiguracije

Primjer pokazuje sustav sa zvučnom karticom koja ima više ulaza i više izlaza, dok je mikser izveden softverski. Izvori zvuka su programi, mreža, sintesajzer, datoteke sa zvučnim zapisom, mikrofon itd. Sav zvuk iz izvora dolazi do miksera gdje se spaja u jedan tok informacija (eng. stream) koji se preko izlaza može predati uređajima za reprodukciju.

2.2.7. MIDI protokol

MIDI standard definira komunikacijski protokol za razne elektroničke glazbene uređaje, kao što su električne klavijature i osobna računala. MIDI informacije mogu se prenositi kablovima među uređajima i spremati u standardne datoteke.

Standard je prvenstveno bio zamišljen za prijenos nota među sintesajzerima. Pojavom osobnih računala proširen je u svrhu povezivanja instrumenata sa računalima. MIDI specifikacija sastoji se od hardverskog i softverskog dijela. Hardverski dio uglavnom opisuje mehanizme i priključke za povezivanje uređaja, dok softverski dio čini većinu specifikacije, budući da današnja računala raspolažu i više nego dovoljnom procesorskom moći za izvedbu softverskih sintesajzera i sekvencera.

Unutar Java Sound API-ja MIDI standard opisan je javax.sound.midi paketom. Ovaj paket omogućava upravljanje MIDI događajima i reprodukciju zvuka na temelju tih događaja. Dok semplirani zvuk sadrži direktan prikaz zvuka, MIDI je format zapisa koji sadrži instrukcije prema kojima se neki zvuk može sintetizirati. Takav format ne opisuje direktno sam zvuk već događaje koji utječu na proces stvaranja zvuka koji se generira u sintesajzeru. Takvi događaji su npr. pritisci tipaka, okretanje potenciometara, pritiskanje gumba i slično.

MIDI događaji ne moraju biti izvedeni na vanjskim uređajima, već mogu biti i softverski generirani te spremljeni u datoteku. Programi koji stvaranju, generiraju i uređuju takve MIDI datoteke zovu se sekvenceri. Sintesajzeri koji iz takvog zapisa generiraju zvuk mogu biti vanjski uređaji, programi ili čipovi na zvučnoj kartici. Osim same reprodukcije, sintesajzeri često imaju podršku za razne efekte. Zvučne kartice često imaju ulazne i izlazne priključke preko kojih se mogu povezivati sa vanjskim MIDI uređajima.

Funkcionalnost MIDI zapisa ilustrirana je MIDI konfiguracijom na slijedećoj slici:

Slika 4. Primjer MIDI konfiguracije

Primjer pokazuje aplikaciju koja priprema glazbenu reprodukciju iz MIDI datoteke spremljene na tvrdom disku (na lijevoj strani slike). Standardni MIDI zapis čita se u softverskom sekvenceru, koji šalje MIDI poruke vanjskom sintesajzeru koji potom reproducira zvuk, ili programskom sintesajzeru koji koristi bazu datoteka sa instrukcijama za generiranje zvukova poznatih instrumenata.

Kao što se vidi na slici, MIDI poruke unutar sustava imaju vremenske oznake, po kojima ih programski sintesajzer izvodi. One se moraju ukloniti prije slanja vanjskom sintesajzeru, te mu se moraju poslati u označenim trenucima. Ova razlika između MIDI poruka unutar računalnog sustava i vanjskih MIDI komponenti postoji zbog toga što standard u vrijeme svog nastanka (1984-te godine) nije bio razvijen za računala nego je naknadno prilagođen takvoj upotrebi.

Zbog toga se MIDI specifikacija dijeli na 2 dijela:

1) MIDI 1.0 - protokol za povezivanje MIDI komponenti. Odnosi se na originalni standard, razvijen za komunikaciju među sintesajzerima.

2) Standardne MIDI datoteke - proširenje standarda, uključuje podršku za spremanje MIDI zapisa u datoteke

2.2.7.1. MIDI 1.0

U MIDI specifikaciji 1.0 podaci su predstavljeni MIDI porukama. Vrste poruka međusobno se razlikuju po prvom bajtu poruke. Taj bajt zove se statusni bajt, a označen je jedinicom na mjestu najznačajnijeg bita. Nakon njega slijede bajtovi poruke.

MIDI poruke se obično koriste kanalno. Svaki kanal se pridjeljuje pojedinom instrumentu tako da se uređaj namjesti da reagira samo na poruke poslane na određenom kanalu. Za takvu komunikaciju imamo kanalne MIDI poruke, koje imaju statusni bajt takav da se u prva četiri bita nalazi zapis kanala, a u druga četiri bita vrsta poruke. Npr. dvije najčešće korištene vrste MIDI poruka su Note On i Note Off za sviranje i prestanak sviranja određene note. Ovakve poruke obično koriste dva podatkovna bajta, jedan koji određuje visinu note i drugi koji određuje jačinu kojom je nota odsvirana.

Ova osnovna verzija MIDI specifikacije dizajnirana je za prijenos MIDI informacija između komponenti u realnom vremenu. To znači da se MIDI porukama ne pridodaju nikakve vremenske oznake, već se akcije zapisane u porukama izvode odmah po primitku poruke.

2.2.7.2. Standardne MIDI datoteke

Standardne MIDI datoteke proširuju osnovni MIDI protokol i dodaju mu mogućnost pohrane MIDI informacija. Takve datoteke zapravo sadrže MIDI događaje. MIDI događaj je MIDI poruka sa dodanom vremenskom oznakom koja određuje vrijeme izvođenja te poruke. Niz takvih događaja naziva se sekvencom.

Standardna MIDI datoteka organizirana je u trake. Svaka traka sadrži slijed nota za određeni instrument. Možemo reći da MIDI datoteka sadrži sekvence MIDI događaja za pojedine instrumente.

2.2.8. Dizajn i programska struktura MIDI modula

MIDI modul Java Sound API-ja dizajniran je sa ciljem potpune podrške MIDI standarda, te jednostavnog upravljanja MIDI uređajima. Programska implementacija se stoga svodi na simuliranje standardom specificiranih MIDI komponenti.

2.2.8.1. MIDI poruke

MIDI poruke predstavljene su apstraktnom klasom MidiMessage. Ta klasa predstavlja osnovnu, vremenski neoznačenu MIDI poruku u skladu sa specifikacijom 1.0. Također može sadržavati poruku definiranu proširenom MIDI specifikacijom ali bez vremenske oznake.

Klasa MidiMessage ima tri podklase:

1) ShortMessages - su najčešće korištene poruke. Nakon statusnog bajta slijede dva podatkovna.

2) SysexMessages - su posebne poruke vezane uz sustav. Mogu imati puno podatkovnih bajtova i obično ih definira proizvođač sustava.

3) MetaMessages - su vrsta poruka koja se zapisuje u MIDI datoteke. Sadrže tekst pjesama, podatke o tempu i slično, koje imaju značenje za sekvencer ali ne i sintesajzer.

2.2.8.2. MIDI događaji, sekvence i trake

MIDI događaji sadrže MIDI poruke i vremenske informacije o njihovom izvođenju, te se zapisuju u MIDI datoteke. Programski gledano, MIDI događaj predstavljen je klasom MidiEvent , koja je omotač oko klase poruke i njene instance moguće je zapisivati u standardne MIDI datoteke. Metode klase omogućavaju nam čitanje i postavljanje vremenskih informacija te dobavljanje osnovne MIDI poruke. Njeno postavljanje moguće je samo za vrijeme konstrukcije događaja.

Niz MIDI događaja čini traku, koja odgovara zapisu sviranja jednog instrumenta. Niz traka čini sekvencu. Programski, sekvence i trake predstavljene su klasama Track i Sequence. Njihove instance moguće je stvoriti čitanjem iz datoteke ili korištenjem konstruktora. Klase MidiEvent, Track i Sequence čine hijerarhiju u smislu vlasništva, ali ne i nasljeđivanja, jer su sve izvedene iz klase java.lang.Object.

2.2.8.3. MIDI uređaji

MIDI uređaji unutar Java Sound API-ja predstavljeni su MidiDevice sučeljem. Objekti koji implementiraju ovo sučelje imaju mogućnost komunikacije sa drugim takvim objektima odnosno MIDI uređajima. Takav objekt može biti implementiran softverski ili kao sučelje prema MIDI podršci nekog hardverskog uređaja kao što je zvučna kartica ili eksterni sintesajzer. Sučelje pruža funkcionalnosti za otvaranje i zatvaranje uređaja, kao i funkcionalnosti tipičnih ulaznih i izlaznih MIDI priključaka, kao što je slanje i primanje MIDI poruka. Uređaji specijalizirane namjene kao što su sintesajzeri i sekvenceri imaju na raspolaganju njegova podsučelja Sythesizer i Sequencer. Sučelje također sadrži unutarnju klasu MidiDevice.Info u kojoj se nalaze tekstualne informacije o uređaju.

2.2.8.4. Odašiljači i primatelji

Ovi objekti služe za odašiljanje i primanje MIDI poruka. Odašiljači moraju implementirati Transmitter sučelje a primatelji Receiver sučelje. MIDI uređaj šalje ili prima poruke preko jednog ili više takvih objekata, pri čemu se kaže da on te objekte posjeduje. Svaki odašiljač može biti spojen na jednog primatelja odjednom, i suprotno. Ako želimo da nam uređaj šalje poruke prema više primatelja odjednom, onda mora posjedovati više odašiljača pri čemu je svaki spojen sa svojim primateljem.

2.2.8.5. Sekvenceri

Sekvencer je uređaj za snimanje i reprodukciju sekvenci MIDI događaja. Posjeduje odašiljače i primatelje, jer mora slati poruke ostalim uređajima kao što su sintesajzeri ili izlazni MIDI priključci, te ih primati prilikom zapisivanja u MIDI datoteke. Sekvencer je u API-ju predstavljen objektom koji implementira Sequencer sučelje. U odnosu na svoje nadsučelje MidiDevice, ovo sučelje definira dodatne metode za osnovne mogućnosti sekvenciranja, kao što su učitavanje sekvenci iz MIDI datoteka, postavljanje tempa izvođenja i sinkroniziranje ostalih uređaja. Programi preko sučelja također mogu registrirati objekte slušače, koji se obavještavaju o izvršavanju odabranih funkcija sekvencera.

2.2.8.6. Sintisajzeri

Sintesajzer je uređaj za generiranje zvuka. To je jedini objekt u MIDI paketu Java Sound API-ja koji generira zvuk. Sintesajzer kontrolira 16 MIDI kanala. Kanali su instance klase koja implementira MidiChannel sučelje. Zvuk se može generirati direktno, korištenjem metoda ovih objekata ili slanjem poruka sintesajzeru, koji onda prema njima generira zvukove. Poruke su češći način upotrebe. Sintesajzer ih prima od sekvencera ili preko ulaznog porta. Osim informacija iz MIDI poruka, npr. o visini i jačini note, sintesajzer mora znati kako uopće generirati traženi zvuk, da bi ga mogao interpretirati na traženi način. Ova vrsta informacija predstavljena je Instrumentom. Svaki instrument sadrži skup instrukcija u prema kojima sintesajzer generira određeni zvuk. Instrumenti su ugrađeni u sintesajzer ili mu se dodaju u obliku kolekcije instrumenata (eng. soundbank).

2.2.9. Korištenje MIDI modula

U nastavku je ilustrirano korištenje MIDI modula na primjerima operacija koje se u aplikacijama najčešće izvode.

2.2.9.1. Ispitivanje i zauzimanje dostupnih MIDI uređaja

Pristupanje MIDI uređajima u Java Sound API-ju izvodi se preko klase MidiSystem, koja predstavlja središnji objekt za upravljanje MIDI sustavom. Klasa sadrži statičke metode kojima se mogu ispitati i dobaviti raspoloživi MIDI uređaji, sistemske implementacije sučelja kao što su Sequencer i Synthesizer, implementacije eksternih sučelja primatelja i odašiljača, informacije o podržanim MIDI datotekama itd. Dohvat raspoloživih uređaja obavlja se upotrebom metode:

static MidiDevice.Info[] getMidiDeviceInfo()

Metoda vraća polje MidiDevice.Info objekata koji sadrže informacije o pojedinom MIDI uređaju, kao što su ime klase, mogućnosti i tekstualni opis.

Dohvat odabranog uređaja izvodi se metodom:

static MidiDevice getMidiDevice(MidiDevice.Info info)

Osim odabira konkretnog uređaja, moguće je koristiti i podrazumijevane (eng. default) MIDI uređaje. U tu svrhu koristimo slijedeće metode:

1) static Sequencer getSequencer(),

2) static Synthesizer getSynthesizer(),

3) static Receiver getReceiver(),

4) static Transmitter getTransmitter().

Prije korištenja, MIDI uređaje je potrebno rezervirati upotrebom metode open():

if (!(device.isOpen())) {

try {

device.open();

} catch (MidiUnavailableException e) {

// obrada iznimke

}

}

Nakon što je uređaj rezerviran, potrebno ga je povezati sa drugim uređajima radi ostvarivanja komunikacije. Nakon što su potrebne operacije obavljene, uređaj se zatvara metodom close().

2.2.9.2. Odašiljanje i primanje MIDI poruka

Povezivanje dva MIDI uređaja izvodi se spajanjem odašiljača jednog uređaja sa primateljem drugog. Povezivanje se uvijek obavlja na strani odašiljača. Odašiljač sadrži metode za ispitivanje i postavljanje primatelja. Kad se primatelj postavi, znači da je između njih uspostavljena MIDI veza. Odašiljač šalje poruke primatelju koristeći njegove metode. Preko tih metoda primatelj prima poruke od drugih objekata. Oba objekta sadrže metode za zatvaranje veze, nakon čega postaju dostupni za stvaranje veza sa drugim uređajima.

U slijedećem primjeru prikazano je povezivanje sekvencera i sintesajzera:

// deklariranje referenci

Sequencer seq;

Transmitter seqTrans;

Synthesizer synth;

Receiver synthRcvr;

try {

//dohvat pretpostavljenog sekvencera

seq = MidiSystem.getSequencer();

seqTrans = seq.getTransmitter();

// dohvat pretpostavljenog sintisajzera

synth = MidiSystem.getSynthesizer();

synthRcvr = synth.getReceiver();

// povezivanje odasiljača i primatelja

seqTrans.setReceiver(synthRcvr);

} catch (MidiUnavailableException e) {

// obrada iznimke

}

Nakon upotrebe, sve primatelje i odašiljače potrebno je zatvoriti pozivanjem njihove close() metode. Svaki MIDI uređaj sadrži metodu istog potpisa, koja ga zatvara skupa sa njegovim primateljima i odašiljačima.

MIDI poruke je moguće slati i programski, bez upotrebe odašiljača. Ovakav je postupak koristan kod programa koji generiraju MIDI poruke npr. iz MIDI tipkovnice sa ekrana i šalju ih sintesajzeru koji potom proizvodi zvuk. Potrebno je instancirati praznu poruku iz klase ShortMessage i popuniti je korištenjem metode:

void setMessage(int command, int channel, int data1, int data2)

Nakon toga poruku je potrebno poslati primatelju koristeći njegovu metodu:

void send(MidiMessage message, long timeStamp)

Argument timeStamp je interna veličina koja se koristi za ispravljanje i sinkronizaciju latencije kod MIDI poruka do koje može doći zbog npr. kašnjenja u operativnom sustavu ili u mrežnom prometu. Predstavlja vrijeme u mikrosekundama od otvaranja uređaja koji sadrži primatelja kojem pripada korištena metoda send(). Ako se ne koristi, pridružuje joj se vrijednost „-1“.

Ovakvo slanje poruke ilustrira slijedeći kod:

// stvaranje prazne poruke

ShortMessage myMsg = new ShortMessage();

// sviranje note C5 (60), umjereno glasno (jačina = 90).

myMsg.setMessage(ShortMessage.NOTE_ON, 0, 60, 90);

// vremenske oznake se ne koriste

long timeStamp = -1;

// dohvat primatelja

Receiver rcvr = MidiSystem.getReceiver();

// slanje poruke

rcvr.send(myMsg, timeStamp);

2.2.9.3. Učitavanje, izvođenje i snimanje MIDI sekvenci

MIDI zapisi pohranjuju se u obliku sekvenci MIDI događaja. Za upravljanje MIDI sekvencama koristi se sekvencer. Pomoću njega se mogu izvoditi, uređivati i snimati MIDI sekvence. Sekvencer u tu svrhu obično sadrži primatelje i odašiljače. Primateljima se koristi pri snimanju, a odašiljačima pri slanju MIDI zapisa uređajima za izvođenje.

Dohvat i otvaranje sekvencera prikazani su slijedećim programskim odsječkom:

// dohvat podrazumijevanog sekvencera

Sequencer sequencer;

sequencer = MidiSystem.getSequencer();

// provjera

if (sequencer == null) {

// greska, ne postoji sekvencer ...

} else

sequencer.open();

}

Sekvencu se može dobaviti iz MIDI datoteke, moguće ju je stvoriti ručno, ili iz dobivenih MIDI poruka. Učitavanje sekvence iz datoteke može se obaviti na slijedeći način:

try {

// otvaranje datoteke

File myMidiFile = new File("seq1.mid");

// učitavanje MIDI datoteke u sekvencu

Sequence mySeq = MidiSystem.getSequence(myMidiFile);

// postavljanje sekvence

sequencer.setSequence(mySeq);

}

catch (Exception e) {

// obrada iznimke

}

Nakon učitavanja sekvence, upravljanje njezinim izvođenjem obavlja se korištenjem slijedećih metoda:

1) void start(),

2) void stop(),

3) void getMicrosecondPosition(long microsecond),

4) void setMicrosecondPosition(long microsecond),

5) public void getTempoInBPM(float bpm),

6) public void setTempoInBPM(float bpm).

Izvođenje sekvenci moguće je sinkronizirati sa drugim MIDI uređajima. Na sekvencer je također moguće priključiti i objekte slušače, koji se obavještavaju pri izvođenju registriranih događaja.

2.3. Swing2.3.1. Razvoj i namjena

Swing je biblioteka za izradu grafičkih sučelja u sklopu programskog jezika Java. Biblioteka čini dio Sun-ovog paketa grafičkih komponenti pod nazivom JFC (eng. Java Foundation Classes) u kojem su još bibliteke AWT i Java 2D. Swing je prvi razvila tvrtka Netscape Communications Corporation pod nazivom IFC (eng. Internet Foundation Classes) krajem 1996. godine. Godinu dana poslije tvrtka Sun Microsystems odlučila je uključiti biblioteku IFC kao standardni dio jezika Java.

Swing je nasljednik AWT biblioteke, izgrađen na njenim komponentama u cilju otklanjanja AWT-ovih nedostataka, koji su uglavnom bili vezani za preveliku ovisnost o grafičkim sučeljima platforme. Swing-ove komponente se prikazuju preko Java 2D biblioteke, koristeći pritom AWT-ove mehanizme komunikacije sa platformom. Na taj način je postignuta udaljenost od platformskog koda, te mogućnost kreiranja sofisticiranijih grafičkih komponenti u odnosu na AWT. Swing osim toga pruža i mogućnost odabira grafičkih tema (eng. Look And Feel), koje se mogu jednostavno kreirati i mijenjati. Ovaj mehanizam omogućava Swing-ovim komponentama da emuliraju izgled komponenti platforme na kojoj se izvode, ili da namjerno izgledaju drugačije.

2.3.2. Arhitektura

Swing je platformski nezavisan MVC (eng. Model View Controller) okvir za izradu grafičkih sučelja u programskom jeziku Java. [11] Izveden je kao jednodretveni programski model sa slijedećim karakteristikama:

1) Platformska nezavisnost - Swing je platformski nezavisan jer kreira i iscrtava vizualne komponente neovisno o platformskim komponentama, koristeći Javinu 2D grafičku biblioteku. AWT je za razliku od Swing-a gradio grafička sučelja od već postojećih sistemskih komponenti, tako što ih je pozivao kroz sistemski API.

2) Komponenta orijentacija - Swing je okvir temeljen na komponentama, tj. objektima koji prate dobro poznate/specificirane karakteristične uzorke ponašanja. Te komponente asinkrono generiraju događaje, imaju ograničena svojstva i odgovaraju na dobro poznat skup naredbi karakterističan za svaku komponentu. Swing-ove komponente su osim toga i u skladu sa Java Beans konvencijom.

3) Prilagodljivost - sve komponente u Swing-u sadržavaju vizualne modele, koje je moguće prilagoditi korisničkim potrebama. Vizualni izgled komponenti određuje se kompozicijom standardnih grafičkih elemenata kao što su okviri prozora, klizači, ukrasi itd. Korisnik programski može zadati grafičke elemente, boje, prozirnosti i slično, ovisno o svojstvima pojedine komponente. Komponenta prema korisnikovu odabiru konfigurira prikladne klase za prikazivanje (eng. renderer) takvih komponenti. Također je moguće implementirati korisničke klase za prikaz, te na taj način kreirati jedinstvene grafičke komponente.

Na slijedećoj slici prikazane su JTable komponente sa različitim implementacijama prikaza ćelija:

Slika 5. JTable komponente sa različitim implementacijama prikaza ćelija

4) Proširivost - Swing predstavlja programski okvir vrlo razdijeljene arhitekture, koja dopušta korisničku implementaciju velikog broja sučelja temeljnih komponenti. Korisnici mogu izmijeniti postojeće komponente i njihove funkcionalnosti ili ih nadograditi.

5) Okvir „lakše kategorije“ (eng. Lightweight) - konfigurabilnost je rezultat izbora da se ne koriste grafički elementi samog operativnog sustava, već se prikazivanje komponenti izvodi preko Java 2D biblioteke. Na ovaj način, okvir ima više slobode pri prikazivanju svojih komponenti i može ih prikazivati na bilo koji način, bez ograničenja koja se mogu nametati zbog odabira platforme. Ipak, jezgra svake komponente je AWT-ov Container. To je osnovna klasa u AWT okviru koju nasljeđuju sve Swing-ove klase. Na ovaj način Swing se „priključuje“ na platformske procese, kao što su preslikavanja ekrana, praćenje pokreta miša i slično, te preko njih komunicira sa sustavom.

Swing dakle nadograđuje semantiku i prikaz platformskih komponenti sa vlastitom semantikom i vizualizacijom. Npr. kad se pozove metoda paint() definirana u svakom AWT-ovom Container-u, a time i u svakoj Swing-ovoj komponenti, ona se potom prikazuje koristeći Javine grafičke mehanizme, neovisno o platformi. Osim vizualizacije, nadogradnja se odnosi i na mehanizam upravljanja događajima koji koriste komponente. Swing koristi složeniji model upravljanja od AWT-a među komponentama, koji se tek na kraju povezuje sa sustavom kroz AWT-ov model.

6) Konfigurabilnost - Swing jako ovisi o mehanizmima izvođenja. Ta ovisnost i indirektni uzorci izvođenja omogućavaju mu trenutno reagiranje na promjene u konfiguraciji. Aplikacije napisane u Swing-u tako npr. mogu mijenjati grafičke teme za vrijeme izvođenja. Osim toga, moguće je kreirati korisničke teme, koje ne mijenjaju bitno ostatak aplikacijskog koda i lako se dodaju.

Primjeri nekoliko komercijalnih grafičkih tema prikazani su na slijedećoj slici:

Slika 6. Komercijalne grafičke teme za Swing

7) Slabo vezan / MVC okvir - Swing je biblioteka koja se velikim dijelom oslanja na MVC arhitekturu. Taj tip arhitekture konceptualno razdvaja podatke predstavljene korisniku od sučelja preko kojeg su mu podaci prezentirani. Zbog toga većina komponenti sadrži modele specificirane u obliku sučelja, pri čemu su u svim konkretnim komponentama sadržane njihove podrazumijevane implementacije. Programer može koristiti podrazumijevane implementacije ili implementirati vlastite.

Modeli su odgovorni za pružanje događaja i konceptualnih svojstava precizno definiranih sučeljem za određenu komponentu. Osim toga, modeli također pružaju i programski način priključivanja slušača događaja na komponentama. Događaji koji se pri tom osluškuju su usko vezani uz model i preko njega se interpretiraju odgovarajućoj komponenti.

Za prikaz komponenti su odgovorne klase za prikazivanje. Implementiranjem vlastitih klasa za prikaz komponente ili nasljeđivanjem postojećih možemo joj izmijeniti izgled i dio funkcionalnosti. Npr. možemo implementirati tablicu tipa JTable koja u svojim ćelijama sadrži slike umjesto teksta.

Funkcionalnost komponenti pružaju editorske klase. Npr. ako nam ćelije tablice sadrže objekte tipa JButton korištenjem editorske klase možemo specificirati ponašanje koje komponenta treba izvoditi prilikom klika na ćeliju, kao što je prikaz dijaloga ili mijenjanje boje ćelije ili slično.

Na slijedećoj slici prikazana je Sun-ova Swing Demo aplikacija, u kojoj su demonstrirane neke od vizualnih mogućnosti Swing-a u kombinaciji sa Java 2D bibliotekom, pod grafičkim temama Windows i Motif:

Slika 7. Swing Demo aplikacija

3. Opis aplikacije3.1. Koncept, namjena i dizajn

Aplikacija izrađena u okviru ovog diplomskog rada predstavlja grafički MIDI sekvencer, namijenjen izgradnji jednostavnih i složenih MIDI sekvenci, te spremanju u MIDI datoteke. Grafičko sučelje aplikacije prikazano je na slijedećoj slici:

Slika 8. Grafičko sučelje aplikacije

Aplikacija se sastoji od četiri modula:

1) Glavni modul – predstavlja glavni prozor aplikacije i sadrži sve ostale module,

2) Glavni sekvencer (Sequencer) - služi za izgradnju složenih sekvenci,

3) Sporedni sekvencer (Loop Tool) - služi za izgradnju jednostavnih sekvenci (eng. loop),

4) Kontrolni modul (Mixer) - upravlja iznosom glasnoće i reverba svih raspoloživih MIDI kanala.

Podjela aplikacije na module odraz je njihove funkcionalnosti i programske implementacije. Glavni i sporedni sekvencer su slične strukture, i čine zasebne cjeline, pa su stoga programski implementirani kao podklase osnovnog sekvencera. Kontrolni modul je po funkcionalnosti također zasebna cjelina, pa je ovakva podjela najprimjerenija za preglednu organizaciju programskog koda, te vizualnu preglednost aplikacije.

3.2. Glavni modul

Glavni modul predstavlja osnovni modul aplikacije u kojem se nalaze glavni prozor i izbornik aplikacije, preglednik instrumenata Explorer i ostali moduli, Sequencer, LoopTool i Mixer. U ovom odjeljku opisani su glavni izbornik i preglednik instrumenata, jer čine neposredni dio glavnog modula, dok su ostali moduli zbog svoje izdvojenosti opisani u zasebnim odjeljcima.

3.2.1. Glavni izbornik

Glavni izbornik aplikacije sastoji se od tri podizbornika: File, Midi i Help.

3.2.1.1. Podizbornik File

Podizbornik File nudi osnovne operacije za upravljanje radom tj. projektima:

1) New - operacija postavlja program u početno stanje.

2) Open - pokreće se izbornik za odabir datoteke, prikazan na slijedećoj slici:

Slika 10. Izbornik za otvaranje datoteke

Ukoliko se učita nepodržani oblik datoteke korisnik se obavještava porukom o pogrešci:

Slika 9. Prikaz pogreške pri otvaranju datoteke

3) Save - operacija pokreće izbornik za spremanje trenutnog projekta u datoteku. Ime i ekstenzija su proizvoljni. Predložena je upotreba „jqs“ ekstenzije. Izbornik za spremanje datoteke prikazan je na slijedećoj slici:

Slika 11. Izbornik za spremanje datoteke

Podaci se spremaju u tekstualnu XML datoteku. Ovaj oblik zapisa se standardno koristi za serijalizaciju Swing objekata od verzije Jave 1.5.

Ukoliko dođe do greške prilikom spremanja datoteke, korisniku se prikazuje poruka o pogrešci:

Slika 12. Prikaz pogreške pri spremanju datoteke

4) Quit - operacija vrši zatvaranje aplikacije.

3.2.1.2. Podizbornik MIDI

Podizbornik MIDI nudi dvije operacije

1) Change Soundbank - operacija pokreće izbornik za odabir datoteke sa kolekcijom zvukova (eng. Soundbank) za Java sintesajzer, prikazan na slijedećoj slici:

Slika 13. Izbornik za odabir kolekcije zvukova

Kolekcija zvukova za Java sintesajzer obično je spremljena u datoteci sa „gm“ ekstenzijom, u GM (General MIDI) formatu. Uz aplikaciju dolazi Javina podrazumijevana kolekcija zvukova, u najkvalitetnijoj verziji, koja se učitava pri pokretanju programa, neovisno o trenutno instaliranoj podrazumijevanoj kolekciji unutar aktivnog JRE-a (Java Runtime Environment). Korisničke kolekcije zvukova u navedenom formatu moguće je kreirati upotrebom komercijalnog programa Beatnik editor tvrtke Beatnik, koja je dizajnirala Java Sound API.

Ukoliko dođe do pogreške pri učitavanju kolekcije, korisnik se o tome obavještava slijedećom porukom:

Slika 14. Prikaz pogreške pri odabiru kolekcije zvukova

Render MIDI File - operacija pokreće izbornik za spremanje datoteke sa MIDI zapisom. Datoteke sa MIDI zapisom su u standardnom SMF (eng. Standard Midi File) formatu, koji je propisala udruga MIDI Manufacturers Association [12]. Taj format je prepoznatljiv na svim računalnim platformama i MIDI sustavima. Podrazumijevana ekstenzija takvih datoteka je „MID“, ali je moguće koristiti proizvoljnu. Izbornik je prikazan na slijedećoj slici:

Slika 15. Izbornik za spremanje MIDI zapisa

Ako prilikom izvršavanja operacije dođe do pogreške, korisnik se obavještava slijedećom porukom:

Slika 16. Prikaz pogreške pri spremanju MIDI zapisa

3.2.1.3. Podizbornik Help

Podizbornik Help nudi operaciju About koja prikazuje dijalog sa informacijama o autoru i aplikaciji:

Slika 17.Prikaz informacija o autoru i aplikaciji

3.2.2. Preglednik instrumenata

Preglednik instrumenata služi za odabir raspoloživih instrumenata iz učitane kolekcije. Svaki od instrumenata moguće je preslušati korištenjem kontrola za pregled na dnu preglednika, te odabrati za korištenje u sekvencerima.

Preglednik je prikazan na slijedećoj slici:

Slika 18. Preglednik instrumenata

Za preslušavanje željenog instrumenta potrebno je odabrati jedan od 16 mogućih MIDI kanala sintesajzera na kojem će se taj instrument odsvirati. Kanal 00 je podrazumijevani kanal za pregled instrumenata, dok je kanal 09 potrebno odabrati ukoliko želimo preslušavati perkusije. U MIDI standardu taj kanal se naziva perkusijski kanal. Kod svih ostalih MIDI kanala se nakon odabira instrumenta na njemu sviraju različite note odabranog instrumenta. Posebnost perkusijskog kanala je u tome što se na njemu svaka nota odsvira kao različiti perkusijski instrument. Instrumenti perkusijskog kanala određeni su MIDI standardom udruge MIDI Manufacters Association.

Instrumenti su organizirani u skupine pod nazivom bank. Svaka skupina sadrži po 128 instrumenata. Kod podrazumijevane kolekcije zvukova, u skupini Bank 0 nalaze se osnovni instrumenti, dok je skupina Bank 1 namijenjena za preslušavanje perkusija. Ona zapravo sadrži popis perkusija na različitim notama tog kanala. Nakon učitavanja u sevencere, perkusijskim instrumentima treba dodijeliti perkusijski kanal. Ova operacija ne izvršava se automatski, jer korisničke kolekcije zvukova ne moraju biti organizirane na ovaj način.

Aplikacija je napisana na način da radi sa podrazumijevanim softverskim sintesajzerom, koji koristi resurse zvučne kartice sustava. Implementacija je takva, jer većina osobnih računala ima jednu zvučnu karticu, dakle jedan raspoloživi sintesajzer. Preglednik koristi posebnu instancu sekvencera i moguće ga je koristiti u isto vrijeme sa glavnim i sporednim sekvencerom, ali pri tome je potrebno uzeti u obzir da se MIDI poruke koje dolaze sa sva tri sekvencera izvode na jednom sintesajzeru. Budući da sintesajzer nakon primitka poruke za promjenu instrumenta na kanalu sve daljnje note na tom kanalu svira sa odabranim instrumentom, svaki različiti instrument koji nije perkusija treba imati svoj posebni kanal. Isto tako, kanal za preslušavanje instrumenta bi trebalo koristiti samo za tu svrhu.

Za odabir željenog instrumenta moguće se koristiti strelicama za navigaciju, a za preslušavanje je moguće koristiti tipke na tastaturi:

1) Enter – odabir instrumenta i učitavanje u sekvencere (dvostruki klik mišem obavlja istu operaciju),

1) Space – preslušavanje instrumenta,

2) Backspace – zaustavljanje preslušavanja.

3.3. Glavni sekvencer

Glavni sekvencer koristi se za izgradnju složenih sekvenci. Složena sekvenca predstavlja sekvencu jednostavnih sekvenci. Jednostavne sekvence se grade u sporednom sekvenceru.

Svaki instrument koji se učita iz preglednika u glavni sekvencer, automatski se odabire u sporednom sekvenceru, dok mu se u glavnom dodjeljuje traka unutar glavne sekvence. U njemu se gradi jednostavna sekvenca, koja se koristi kao osnovni element složene sekvence. Svaka traka služi za pohranu niza jednostavnih sekvenci, koje se sviraju na odabranom instrumentu, u odabrano vrijeme, sa određenim brojem ponavljanja.

Glavni sekvencer prikazan je na slijedećoj slici:

Slika 19. Glavni sekvencer

Osnovni dijelovi glavnog sekvencera su alatne trake i tablice, predočene na prethodnoj slici:

1) Kontrole za sviranje - obavljaju operacije sviranja i zaustavljanja sekvence, te sviranja sa ponavljanjem. Sviranje se pokreće uz automatsku izgradnju sekvence. Ukoliko je sviranje u tijeku gumb za sviranje služi kao pauza, dok gumb za zaustavljanje uvijek vraća sekvencer na početak sekvence.

2) Spremanje sekvence - izvodi se na način da se sve popunjene ćelije iz desne tablice zapisuju kao jednostavne sekvence u glavnu sekvencu. Prilikom editiranja tablice, potrebno je spremiti sekvencu da bi promjene mogle nastupiti.

3) Tempo - se zadaje brojem otkucaja u minuti. Pri tome otkucaj označava četvrtinsku notu. Tempo se nakon odabira automatski postavlja u oba sekvencera.

4) Broj ponavljanja - ukupno trajanje glavne sekvence određeno je brojem ponavljanja jednostavnih sekvenci, tj. brojem stupaca desne tablice. Ukoliko se odabere manji broj stupaca od trenutnog, podaci u stupcima koji se uklanjaju se odbacuju. U suprotnome se dodaju prazni stupci.

5) Kontrola glasnoće - upravlja glasnoćom cijele aplikacije. Raspon glasnoće svih kanala sintesajzera se određuje prema rasponu ove kontrole.

6) Trake / Instrumenti - lijeva tablica sekvencera sadrži imena pojedinih traka sekvence. Odabirom ćelije u sporedni sekvencer se učitavaju spremljeni podaci za pripadnu traku, kao što su ime, instrument, kanal i note koje čine njenu jednostavnu sekvencu. Početna imena traka se određuju prema instrumentima koji su dodijeljeni pojedinim trakama pri učitavanju iz preglednika. Dvostrukim klikom na ćeliju tablice ta imena se mogu mijenjati prema želji. Pritiskom tipke Delete traka se uklanja iz tablice.

7) Oznake jednostavnih sekvenci - prema njima se grade trake za pojedine instrumente na prethodno opisan način.

3.4. Sporedni sekvencer

Sporedni sekvencer služi za izgradnju jednostavnih sekvenci. Jednostavna sekvenca gradi se korištenjem dugih i kratkih nota. Kratke note predstavljaju četvrtinke i označavaju se jednom svijetlo plavom ćelijom, dok se duge note mogu protezati kroz proizvoljan broj ćelija i označene su tamno plavom bojom. Duge note obično se koriste za instrumente, a kratke za perkusije.

Jednostavne sekvence služe za izgradnju traka u glavnom sekvenceru. Kao što su note građevni elementi jednostavnih sekvenci, tako su jednostavne sekvence građevni elementi složenih sekvenci. Jednostavne sekvence imaju proizvoljnu duljinu od n*16 stupaca, jer je sekvencer namijenjen izradi najčešće korištenih sekvenci zapisanih u 4/4 taktu.

Sve jednostavne sekvence su jednake duljine, što znači da se mijenjanjem dužine jednostavne sekvence mijenjaju sve jednostavne sekvence u projektu. Dužina sekvence obično se određuje na početku pisanja sekvence i iznosi 16, 32, 64 ili 128 stupaca. Ukoliko se ne želi koristiti ponavljanje sekvenci može se jednostavno odabrati veća dužina za jednostavnu sekvencu, koja onda preuzima ulogu složene i ispunjava se notama.

Sporedni sekvencer prikazan je na slijedećoj slici:

Slika 20. Sporedni sekvencer

Osnovni elementi sporednog sekvencera su alatne trake i tablice prikazane na prethodnoj slici:

1) Kontrole za sviranje - obavljaju operacije sviranja i zaustavljanja sekvence, te sviranja sa ponavljanjem. Sviranje se pokreće uz automatsku izgradnju sekvence. Ukoliko je sviranje u tijeku gumb za sviranje služi kao pauza, dok gumb za zaustavljanje uvijek vraća sekvencer na početak sekvence.

2) Spremanje sekvence - izvodi se na način da se sve popunjene ćelije iz desne tablice zapisuju u jednostavnu sekvencu. Prilikom editiranja tablice, potrebno je spremiti sekvencu da bi promjene mogle nastupiti.

3) Odabir trake - svaka jednostavna sekvenca gradi jednu traku složene sekvence, na način da se svira na odabranim mjestima, odabrani broj puta. Nakon odabira trake, u sporedni sekvencer se učitavaju njene postavke kao što su instrument, MIDI kanal na kojem se instrument svira i glasnoća trake.

4) MIDI kanal - određuje na kojem će se kanalu sintesajzera svirati odabrana traka, tj. instrument koji joj je pridružen. Kanal automatski ostaje sačuvan nakon promijene.

5) Duljina sekvence - sve jednostavne sekvence imaju istu duljinu određene ovim parametrom. Ako se duljina sekvence poveća, tablica se ispunjava praznim ćelijama. U suprotnom, sve note iza granice duljine nove sekvence se odbacuju.

6) Kontrola glasnoće – služi određivanju iznosa glasnoće sekvenc. Vrijednost se sprema odmah nakon promijene.

7) Visine nota - pokazuju moguće visine nota s kojima se gradi jednostavna sekvenca. Raspon raspoloživih nota se prostire na 10 oktava. Srednja nota u tom rasponu je C5.

8) Duga nota - oznaka za instrumente koji proizvode tonove proizvoljnog trajanja. Početak note određuje se prvom, a kraj zadnjom tamno plavom ćelijom na istoj visini.

9) Kratka nota - obično se koristi za perkusije ili kratke tonove.

3.5. Kontrolni modul

Kontrolni modul prikazan je na slijedećoj slici:

Slika 21. Kontrolni modul

Kontrolni modul se koristi za podešavanje iznosa glasnoće i reverba svih raspoloživih MIDI kanala.

Reverb je zvučna pojava koja se događa pri generiranju zvuka u prostoru. Nakon što se prestane proizvoditi zvuk, ovisno o prostoru u kojem se zvuk rasprostire, dolazi do stvaranja raznih odjeka, zbog kojih se zvuk čuje još neko vrijeme, ali mu intenzitet brzo opada. Efekt reverba se može jasno primijetiti kad se zvuk proizvodi npr. u tunelu, katedralama, pećinama i slično. Razlikuje se od obične jeke po tome što se sastoji od mnoštva manjih i brzih odjeka, koji se ne mogu jasno razaznati. Efekt pridonosi prostornosti zvuka. Odsvirane note se ne odsijecaju naglo pri prestanku trajanja, ako se pri tom koristi reverb.

4. Programska izvedba aplikacije4.1. Pregled i organizacija koda

Kod aplikacije raspodijeljen je u tri osnovna paketa:

1) hr.fer.npecanac.diplomski.gui - paket sadrži klase koje grade grafičko sučelje aplikacije,

2) hr.fer.npecanac.diplomski.sound - paket sadrži klase koje čine zvučne komponente aplikacije,

3) hr.fer.npecanac.diplomski.utility - paket sadrži pomoćne klase.

4.1.1. Klase grafičkog sučelja

Klasni dijagram paketa sa klasama za izradu grafičkog sučelja prikazan je na idućoj slici:

Slika 22. Klase grafičkog sučelja

Značenje isprekidanih linija na slici je slijedeće:

1) Crvene linije – označavaju nasljeđivanje i pokazuju u smjeru nadklase,

2) Smeđe linije – označavaju instanciranje klase i pokazuju u smjeru klase koja se instancira,

3) Zelene linije – označavaju referenciranje među klasama.

Grafičko sučelje sastoji se od četiri osnovne klase, koje predstavljaju četiri prethodno opisana modula:

1) MainGUI - glavna i najveća klasa, predstavlja glavni modul iz kojeg se pokreće aplikacija,

2) SequencerGUI - glavni sekvencer,

3) LoopToolGUI - sporedni sekvencer,

4) MixerGUI - kontrolni modul.

Ostale klase prikazane na dijagramu pružaju funkcionalnosti koje se koriste u osnovnim klasama:

1) BasicSequencerGUI - klasa predstavlja sekvencersku nadklasu iz koje se izvode klase glavnog i sporednog sekvencera. Sadrži sve zajedničke funkcionalnosti oba sekvencera.

2) TrackProperties - klasa se koristi kao uzorak za spremanje podataka o svojstvima traka iz glavnog sekvencera. Predstavlja složeni tip podatka koji sadrži ime trake, oznaku instrumenta koji se svira na toj traci, MIDI kanal, glasnoću trake i jednostavnu sekvencu koja se na toj traci izvodi.

3) TableCellColorEditorSequencer - klasa predstavlja prilagođeni editor ćelija za glavni sekvencer. U njoj se definira ponašanje tablice u slučaju odabira ćelije, tj. način bojanja.

4) TableCellColorEditorLoopTool - klasa predstavlja prilagođeni editor ćelija za sporedni sekvencer.

5) TableCellColorRenderer - klasa određuje prilagođeni način prikaza ćelija tablice. Preko nje se definira prikaz boja u ćelijama, označavanje svakog četvrtog stupca, označavanje trenutno aktivnog stupca, prikazivanje editiranih ćelija i slično.

6) TableModelSequencer - klasa definira prilagođeni tablični model za desnu tablicu sekvencera. Preko modela se rukuje sa zaglavljem tablice, određuju joj se dimenzije, korišteni oblici selektiranja i slično.

4.1.2. Zvučne klase

Aplikacija koristi dvije zvučne klase koje se koriste kao omotači oko klasa sekvencera i sintesajzera Java Sound API-ja:

1) SequencerWrapper - klasa se koristi kao omotač oko implementacije sekvencera Java Sound API-ja. Omogućava direktno sviranje jedne note danog instrumenta bez potrebe za izgradnjom sekvence (koristi se u pregledniku instrumenata), definira metodu za izgradnju MIDI događaja i njihov zapis u trake, te se brine o popunjavanju sekvence meta-porukama koje generiraju meta-događaje, preko kojih se u grafičkom sučelju prati i označava trenutna pozicija u sekvenci.

2) SynthesizerWrapper - klasa se koristi kao omotač oko implementacije sintisajzera Java Sound API-ja. Implemetira metode za učitavanje kolekcija instrumenata iz datoteke, za pristup raspoloživim instrumentima te za postavljanje i čitanje iznosa reverba i glasnoće MIDI kanala.

4.1.3. Pomoćne klase

U paketu sa pomoćnim klasama nalazi se klasa LocalDir koja služi za dohvat reference i imena direktorija u kojem je aplikacija pokrenuta.

4.2. Implementacija zvučnog sustava

Zvučni sustav implementiran je kroz prethodno opisane klase omotače oko sekvencera i sintesajzera Java Sound API-ja. Omotači se koriste u svrhu proširivanja funkcionalnosti dostupnih zvučnih klasa. Korištenje nasljeđivanja u ovu svrhu bi bio logičniji pristup, međutim to nije moguće, jer su Sequencer i Synthesizer sučelja definirana u okviru API-ja, dok su objekti koji ih implementiraju izvedeni u platformskom kodu i ne može ih se direktno nasljeđivati. Nasljeđivanje je moguće jedino u slučaju vlastite implementacije tih sučelja.

4.2.1. Klasa SynthesizerWrapper

Klasa predstavlja omotač oko implemetacije sučelja Synthesizer u Java Sound API-ju. Sadrži slijedeće članske varijable:

1) Synthesizer m_Synthesizer - implementacija sučelja Synthesizer,

2) Instrument[] m_Instruments - polje raspoloživih instrumenata,

3) MidiChannel[] m_Channels - polje raspoloživih MIDI kanala,

4) SoundBank m_SoundBank - referenca na kolekciju instrumenata.

U konstruktoru klase obavlja se dohvat sistemske implementacije sučelja Synthesizer i učitavanje kolekcije instrumenata iz datoteke priložene uz aplikaciju. Ukoliko datoteka ne postoji, pokušava se učitati podrazumijevana kolekcija iz JRE-a, ako postoji. U slučaju neuspjeha neke od operacija dolazi do iznimke.

Kod konstruktora prikazan je u slijedećem ispisu:

public SynthesizerWrapper (File p_SoundBankFile) {

// instanciranje sintisajzera

try {

m_Synthesizer = MidiSystem.getSynthesizer();

}

catch (Exception ex) {

throw new RuntimeException(

"Unable to instantiate a Synthesizer!");

}

// otvaranje sintisajzera

try {

m_Synthesizer.open();

}

catch (Exception ex) {

throw new RuntimeException(

"Unable to open the a Synthesizer!");

}

// dohvat MIDI kanala

m_Channels = m_Synthesizer.getChannels();

// učitavanje kolekcije zvukova iz datoteke

if (p_SoundBankFile != null) {

try {

loadSoundbankFromFile(p_SoundBankFile);

}

catch (Exception e) {

// ako dode do iznimke,

// pokusava se učitati sistemska kolekcija (jre)

m_Soundbank = m_Synthesizer.getDefaultSoundbank();

e.printStackTrace();

}

}

// konstruktoru nije predana datoteka sa kolekcijom

// slijedi učitavanje sistemske kolekcije zvukova (jre)

else {

m_Soundbank = m_Synthesizer.getDefaultSoundbank();

// provjera

if (m_Soundbank != null)

{

// dohvat liste raspoloživih instrumenata

m_Instruments =

m_Synthesizer.getAvailableInstruments();

// provjera

if (m_Instruments == null){

throw new RuntimeException(

"No instruments available!");

}

// učitavanje instrumenata

m_Synthesizer.loadAllInstruments(m_Soundbank);

}

else {

throw new RuntimeException(

"Unable to get a default soundbank!");

}

}

}

Klasa sadrži slijedeće javne metode:

1) boolean isOpen() - metoda vraća status sintesajzera,

2) Patch getPatch(int p_InstIndex) - metoda vraća Patch objekt za predani indeks instrumenta,

3) Instrument getInstrument(int p_InstIndex) - metoda za predani indeks vraća odgovarajuci Instrument,

4) MidiChannel getChannel(int channel) - metoda za dohvat pojedinog kanala sintesajzera,

5) close() - metoda zatvara sintesajzer i otpusta sistemske resurse,

6) Int getInstrumentsCount() - metoda vraća broj raspoloživih instrumenata,

7) void loadSoundbankFromFile(File p_SoundbankFile) - metoda učitava kolekciju zvukova iz datoteke,

8) void setChannelVolume(int p_Channel, int p_Volume) - metoda postavlja iznos glasnoće pojedinog MIDI kanala,

9) void setChannelReverb(int p_Channel, int p_Reverb) - metoda postavlja iznos reverba pojedinog MIDI kanala.

4.2.2. Klasa SequencerWrapper

Klasa predstavlja omotač oko implemetacije sučelja Sequencer u Java Sound API-ju. Sadrži slijedeće članske varijable:

1) SynthesizerWrapper m_SynthesizerWrapper - referenca na omotač sintesajzera,

2) Sequencer m_Sequencer - implementacija sučelja Sequencer,

3) Sequence m_Sequence - zvučna sekvenca,

4) Track m_MetaTrack - traka sa meta-podacima za označavanje pozicije u sekvenci,

5) int m_SequencePPQ - trajanje četvrtinke,

6) boolean m_Looping - oznaka ponavljanja sekvence.

U konstruktoru klase se obavlja dohvat sistemske implementacije sučelja Sequencer, postavljanje osnovnih parametara sekvencera te spajanje sa omotačem sintesajzera. U slučaju neuspjeha neke od operacija dolazi do iznimke.

Kod konstruktora prikazan je u slijedećem ispisu:

public SequencerWrapper(SynthesizerWrapper p_SynthesizerWrapper,

int p_SequencePPQ) {

// provjera parametara

if (p_SequencePPQ < 1 || p_SequencePPQ > 1000)

throw new RuntimeException(

"Unable to instantiate SequencerWrapper! Wrong PPQ!“);

// provjera dostupnosti sintisajzera

if (p_SynthesizerWrapper == null || !p_SynthesizerWrapper.isOpen())

throw new RuntimeException(

"Unable to instantiate SequencerWrapper!

Synthesizer is not available!");

// instanciranje sekvencera

try {

m_Sequencer = MidiSystem.getSequencer(false);

}

catch (Exception ex) {

throw new RuntimeException(

"Unable to instantiate Sequencer!");

}

// postavljanje reference na sintisajzer

m_SynthesizerWrapper = p_SynthesizerWrapper;

// otvaranje sekvencera

try {

m_Sequencer.open();

// povezivanje sa sintisajzerom

m_Sequencer.getTransmitter().setReceiver(

m_SynthesizerWrapper.getSynthesizer().getReceiver());

}

catch (Exception ex) {

throw new RuntimeException(

"Unable to open Sequencer!");

}

// postavljanje trajanja cetrvrtinke

setSequencePPQ(m_SequencePPQ);

try {

m_Sequence = new Sequence(Sequence.PPQ, m_SequencePPQ);

}

catch (Exception e) {

e.printStackTrace();

}

}

Klasa sadrži slijedeće javne metode:

1) void PlayMidiInstrumentPreview (int p_InstIndex, int p_Channel, int p_Note, int p_Velocity ) - metoda svira notu C5 odabranog instrumenta, na odabranom kanalu, uz zadanu jačinu,

2) void PlayMidiNoteOnChanell (int p_InstIndex, int p_Channel, int p_Note, int p_Velocity) - metoda svira notu C5 odabranog instrumenta, na odabranom kanalu, uz zadanu jačinu, direktno preko sintesajzera, tj. bez korištenja sekvencera,

3) void createMidiEvent (int p_MidiCommand, int p_Channel, int p_Note, long p_Tick, int p_Velocity) - metoda kreira MIDI poruku klase ShortMessage i zamata je u MidiEvent,

4) void createAndWriteMidiEvent (int p_MidiCommand, int p_Channel, int p_Note, long p_Tick, int p_Velocity, Track p_Track) - metoda kreira MIDI poruku klase ShortMessage, zamata je u MidiEvent i upisuje u danu traku,

5) void stop () - metoda zaustavlja sviranje (bez vraćanja na početak sekvence),

6) void start (long p_TickPosition) - metoda postavlja sekvencu i kreće od zadane pozicije,

7) void resume() - metoda nastavlja sviranje u trenutnoj sekvenci od trenutne pozicije (ako je to moguće),

8) void addMetaEventListener(MetaEventListener p_Listener) - metoda sekvenceru dodaje slusač meta-događaja,

9) void createMetaTrack() - metoda sekvenci dodaje traku sa događajima na svakom tiku,

10) Byte[] convertInt2Bytes(int p_IntValue) - metoda pretvara tip int u niz bajtova,

11) int convertBytes2Int(byte [] p_ByteArray) - metoda niz bajtova pretvara u tip int,

12) boolean isRunning() - metoda vraća status sekvencera,

13) void clearSequence() - metoda briše sekvencu.

4.3. Implementacija grafičkog sučelja

Grafičko sučelje aplikacije sastavljeno je iz četiri osnovna grafička modula. Svaki modul predstavljen je svojom vizualnom klasom, dizajniranom korištenjem dodatka Visual Editor u sklopu programske okoline Eclipse [14] .

Struktura koda vizualnih klasa prikazana je slijedećim uzorkom:

// trenutni paket

// uvoz korištenih klasa

public class ImeKlase {

// konstante

// članske varijable

// vizualne komponente

// konstruktor

// glavna inicijalizacijska metoda

// inicijalizacijske metode vizualnih komponenti

// „get / set“ metode

// javne metode

// privatne metode

// pomoćne klase

}

Sve klase napisane su u skladu sa JavaBeans konvencijom. [15] U odnosu na standardne Java klase, jedina bitna razlika u strukturi koda vizualnih klasa je glavna inicijalizacijska metoda, koja se pokreće iz konstruktora. Ta metoda inicijalizira modul i pokreće inicijalizacijske metode svih njegovih vizualnih komponenti.

4.3.1. Izvedba glavnog modula

Glavni modul predstavljen je klasom MainGUI. Ova klasa predstavlja glavnu i najveću klasu aplikacije, te definira njezinu osnovnu grafičku strukturu, na koju se nadovezuju ostali moduli. Pri pokretanju aplikacije obavlja inicijalizaciju i povezivanje ostalih grafičkih modula i zvučnog sustava.

Klasa sadrži slijedeće članske varijable:

1) SequencerWrapper m_Sequencer,

2) SequencerWrapper m_LoopTool,

3) SequencerWrapper m_PreviewPlayer,

4) SynthesizerWrapper m_Synthesizer.

Prve tri varijable su reference na omotače oko sekvencera za preglednik instrumenata, te glavni i sporedni sekvencerski modul. Sekvenceri se instanciraju u glavnom modulu pri inicijalizaciji zvučnog sustava, a potom se dodijeljuju odgovarajućim modulima i međusobno povezuju. Četvrta varijabla je referenca na omotač oko sintesajzera, koja se instancira i povezuje u istoj metodi. Ostale članske varijable čine vizualne komponente, među kojima su i klase ostala 3 modula. Klase modula se u glavnom modulu nazivaju jPanelSequencer, jPanelLoopTool i jPanelMixer, jer se sve izvode iz JPanel klase i čine dio hijerarhije vizualnih JavaBean klasa glavnog modula.

Pokretanje aplikacije izvodi se iz statičke metode main() glavnog modula. Metoda je prikazana na slijedećem ispisu:

public static void main(String[] args) {

// metoda za pokretanje Swing aplikacije

SwingUtilities.invokeLater(new Runnable() {

public void run() {

// instanciranje glavnog modula

MainGUI application = new MainGUI();

// prikaz glavnog prozora

application.getJFrame().setVisible(true);

try {

// inicijalizacija zvučnog sustava

application.initializeSoundSystem();

// inicijalizacija grafičkog sučelja

Application.connectGUIModules();

}

catch(Exception ex){

// hvatanje i prikazivanje iznimke

JOptionPane.showMessageDialog(

null, ex.getMessage(),"Exception info:",

JOptionPane.ERROR_MESSAGE);

ex.printStackTrace();

}

}

}

Za pokretanje Swing aplikacija unutar glavne metode koristi se metoda:

void static SwingUtilities.invokeLater (Runnable mainClass).

Ova metoda služi za pokretanje klase nakon završetka obavljanja svih događaja u grafičkom sučelju. Na taj način se aplikacija pokreće tek nakon što su sve komponente obavile inicijalizaciju i spremne su za prikazivanje.

Glavna inicijalizacijska metoda klase je metoda:

JFrame getJFrame()

U ovoj metodi obavlja se konfiguriranje glavnog prozora aplikacije, te njegovih komponenti. Nakon prikaza glavnog prozora prvo se inicijalizira zvučni sustav, a potom se grafički moduli povezuju sa zvučnim komponentama i međusobno.

Inicijalizacija zvučnog sustava obavlja se metodom:

private void initializeSoundSystem().

Kod metode prikazan je u slijedećem ispisu:

private void initializeSoundSystem() {

// dohvat lokalnog direktorija

LocalDir localDir = new LocalDir();

m_LocalDir = localDir.getLocalDirRef();

// instanciranje sintisajzera sa kolekcijom zvukova iz datoteke

String fileName = localDir.getLocalDirName();

fileName = fileName + "/" + "soundbanks" +

"/" + "soundbank-deluxe.gm";

File soundBankfile = new File(fileName);

m_Synthesizer = new SynthesizerWrapper(soundBankfile);

// instanciranje sekvencera i povezivanje sa sintisajzerom

m_Sequencer = new SequencerWrapper(m_Synthesizer, 4);

m_LoopTool = new SequencerWrapper(m_Synthesizer, 4);

m_PreviewPlayer = new SequencerWrapper(m_Synthesizer, 4);

}

Metoda instancira sintesajzer sa podrazumijevanom kolekcijom instrumenata, a potom instancira sekvencere, predajući im sintesajzer kao argument. Povezivanje se obavlja u konstruktorima sekvencera.

Međusobno povezivanje grafičkih modula i spajanje sa zvučnim modulima obavlja metoda:

void connectGUIModules ().

Ova metoda predstavlja središnju točku aplikacije u kojoj se obavljaju spajanja svih klasa. Spajanje se izvodi postavljanjem referenci u klasama, analogno studijskom spajanju hardverskih komponenti. Osim spajanja klasa, na početku funkcije se obavlja i učitavanje dostupnih instrumenata u preglednik.

Kod metode prikazan je u slijedećem ispisu:

private void connectGUIModules () {

/* Explorer */

loadInstrumentsToExplorer();

/* Sequencer */

// povezivanje SequencerGUI - SequencerWrapper

jPanelSequencer.setSequencer(m_Sequencer);

// povezivanje Sequencer GUI - MixerGUI

jPanelSequencer.setMixerGUI(jPanelMixer);

/* LoopTool */

// povezivanje LoopToolGUI - SequencerWrapper

jPanelLoopTool.setSequencer(m_LoopTool);

// povezivanje LoopToolGUI - MixerGUI

jPanelLoopTool.setMixerGUI(jPanelMixer);

/* LoopTool - Sequencer */

// povezivanje SequencerGUI - LoopToolGUI

jPanelSequencer.setLoopToolGUI(jPanelLoopTool);

// povezivanje LoopToolGUI – SequencerGUI

jPanelLoopTool.setSequencerGUI(jPanelSequencer);

/* Mixer */

// povezivanje MixerGUI - SynthesizerWrapper

jPanelMixer.setSynthesizer(m_Synthesizer);

// povezivanje Mixer - LoopToolGUI

jPanelMixer.setLoopToolGUI(jPanelLoopTool);

}

4.3.2. Izvedba osnovnog sekvencerskog modula

Osnovni sekvencerski modul čini klasa BasicSequencerGUI. Ova klasa predstavlja osnovnu vizualnu klasu iz koje se izvode glavni i sporedni sekvencer. Klasa proširuje osnovnu kontejnersku klasu JPanel funkcionalnostima zajedničkim za oba sekvencera, kao što su slušači na kontrolama za sviranje, metode za upravljanje dimenzijama desne tablice, klase za bojanje ćelija, metoda za upravljanje oznakom trenutne pozicije u sekvenci i slično. Na taj način, sve modifikacije zajedničkih funkcionalnosti izvode se na jednom mjestu.

Klasa sadrži slijedeće članske varijable:

1) int m_TableRightColumnCount,

2) int m_TablesRowCount,

3) int m_TableRowHeight,

4) int m_TableLeftColumnWidth,

5) int m_TableRightColumnWidth,

6) int m_ActiveColumn,

7) Vector m_trackProperties,

8) SequencerWrapper m_Sequencer,

9) MainGUI m_MainGUI.

Prvih pet varijabli definiraju svojstva tablica sekvencera kao što su broj stupaca i redaka tablica, te visina i širina ćelija. Varijabla m_ActiveColumn označava osvijetljeni stupac, tj. trenutnu poziciju u sekvenci. Kolekcija m_trackProperties koristi se za spremanje svojstava pojedinih traka iz glavne sekvence. Sastoji se od niza TrackProperties objekata. Zadnje dvije varijable su reference na sekvencer i glavni modul aplikacije.

Ostale članske varijable čine vizualne komponente podijeljene na skupinu privatnih i skupinu zaštićenih komponenti. Prva skupina se koristi samo u okviru ove klase, dok se druga skupina koristi izravno u izvedenim klasama.

Klasa sadrži četiri konstruktora:

1) BasicSequencerGUI (),

2) BasicSequencerGUI (int p_RowCount, int p_ColumnCount),

3) BasicSequencerGUI (int p_RowCount, int p_ColumnCount, MainGUI p_MainGUI, SequencerWrapper p_SequencerGUI),

4) BasicSequencerGUI (int p_RowCount, int p_ColumnCount, MainGUI p_MainGUI, SequencerWrapper p_SequencerGUI, String p_TableLeftTitle).

Osnovni konstruktor koristi konstruktor nadklase JPanel, te nakon njega pokreće inicijalizacijsku metodu initialize(). Ostali konstruktori sadrže opcionalne parametre, koji se također mogu postaviti korištenjem javnih metoda za postavljanje članskih variabli (eng. geter). Inicijalizacijska metoda prikazana je u slijedećem ispisu:

private void initialize() {

// postavljanje vizualnih parametara klase

this.setSize(800, 200);

this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

this.setPreferredSize(new Dimension(300, 30));

this.add(getJPanelTop());

this.add(Box.createRigidArea(new Dimension(0,1)));

this.add(getJScrollPane());

// Slusači

// slusač promjena na kontrolama za sviranje - play / pause

jButtonPlayPause.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e) {

if (getSequencer().isRunning()) {

// pauza (stop() izvodi samo zaustavljanje,

// bez vraćanja na početak)

getSequencer().stop();

}

else {

// metoda za nadogradnju

listenerPlay(false, true, getActiveColumn());

}

}

});

// slusač promjena na kontrolama za sviranje - play looped // pause

jButtonPlayPauseLooped.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e) {

if (getSequencer().isRunning()) {

getSequencer().stop();

}

else {

// metoda za nadogradnju

listenerPlay(true, true, getActiveColumn());

}

}

});

// slusač na playback kontrolama - stop

jButtonStop.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e) {

// javna metoda

stopPlayBack();

}

});

// osvjetljavanje prve kolone

setPlayingColumn(0);

}

Metoda prvo postavlja vizualne parametre klase, potom registrira slušače i na kraju osvjetljava prvi stupac desne tablice.

Slušači su izvedeni kao anonimne unutarnje klase koje pokreću javne metode klase ili metode označene kao metode za nadogradnju. Te metode implementirane su u klasi praznim tijelima, jer su namijene implementaciji u nadklasama. Potpis tih metoda započinje sufiksom listener (eng. slušač). Slušači na kontrolama za sviranje i sviranje s ponavljanjem izvedeni su takvim metodama jer se proces izgradnje sekvenci prije sviranja razlikuje u glavnom i sporednom sekvenceru. Zaustavljanje je jednako, pa je taj slušač izveden javnom metodom, zajedničkom za oba sekvencera.

Klasa sadrži slijedeće javne metode:

1) void stopPlayBack() - metoda zaustavlja sviranje sekvence i postavlja početnu poziciju,

2) void clearSequence() - metoda čisti sekvencu tj. briše sve zvučne zapise,

3) int getBarCount() - metoda vraća broj stupaca desne tablice,

4) void setHeaderTableRight() - metoda postavljanja zaglavlja desne tablice (ispisuje vremensku liniju),

5) void setHeaderTableLeft(String p_HeaderLeftTable) - metoda postavlja zaglavlje lijeve tablice (komponente),

6) void repaintTables(boolean p_RepaintLeft, boolean, p_RepaintRight) – metoda osvježava tablice,

7) Vector getTableRightColumnIdentifiers() - metoda postavlja zaglavlja stupaca desne tablice,

8) void setVisibleToolbars(boolean p_Playback, boolean p_Track, boolean p_Channel, boolean p_BPM, boolean p_Bars, boolean p_Volume) - metoda odreduje vidljive alatne trake,

9) void setBPM (Double p_bpm) - metoda postavlja tempo u BPM-ima (eng. Beats per minute),

10) Double getBPM() - metoda dohvaća tempo u BPM-ima,

11) void setBarCounterOptions(int p_Value, int p_Min, int p_Max, int p_Step) - metoda namješta opcije na brojaču stupaca,

12) void setChannelList(String[] p_Channels) - metoda postavlja popis kanala u izbornik za odabir kanala,

13) void setRenderer(TableCellEditor p_TEditor) - metoda postavlja podrazumijevanu klasu za prikaz ćelija,

14) void setPlayingColumn(int p_Column) - metoda označava zadani stupac u tablici,

15) void highlightColumn(int p_Column, boolean p_HLightOn) - metoda označava ili poništava označavanje odabranog stupca.

Izvedene klase nadograđuju slijedeće zaštićene metode, koje se pozivaju iz slušačkih anonimnih klasa vizualnih komponenti, implementiranih na način prikazan u inicijalizacijskoj metodi klase:

1) void listenerMetaEventFired(MetaMessage event) - metoda koja se pokreće pri generiranju meta događaja na sekvenceru,

2) void listenerSetBarCount(int p_BarCount) - metoda se pokreće kod promijene veličine sekvence,

3) void listenerVolumeStateChanged() - metoda koja se pokreće pri promijeni vrijednosti glasnoće,

4) void listenerTableSelectedCellChanged(Jtable m_table, int p_SelRow, int p_SelColumn) - metoda koja se pokreće pri promjeni selekcije u tablici,

5) void listenerPlay(boolean p_Loop, boolean store, long p_TickPosition) - metoda koja pokreće sviranje sekvence,

6) void listenerStoreTrack() - metoda koja pohranjuje podatke iz tablice i gradi zvučnu sekvencu.

Klasa osnovnog sekvencerskog modula sadrži dvije pomoćne klase:

1) SelectionListener - klasa se koristi za upravljanje selekcijom nad tablicom. Predstavlja slušačku klasu koja se registrira nad tablicom, dohvaća poziciju selektirane ćelije tablice, te pokreće vanjsku metodu za nadogradnju koja izvodi potrebne operacije nad ćelijom, ovisno o potrebama sekvencera.

2) TColors - klasa sadrži konstante koje predstavljaju boje desne tablice u RGB zapisu (Red Green Blue). Na ovaj način, nijanse bojanja se mogu izmijeniti jednostavnim mijenjanjem vrijednosti zadanih konstanti.

4.3.3. Izvedba glavnog sekvencerskog modula

Klasa glavnog sekvencerskog modula izvedena je iz prethodno opisane klase osnovnog sekvencerskog modula. Ova klasa upravlja izgradnjom i sviranjem glavne sekvence.

Klasa sadrži slijedeće članske varijable:

1) boolean m_Loading - zastavica koja označava učitavanje podataka i onemogućava slušačke klase,

2) LoopToolGUI m_LoopToolGUI - referenca na sporedni sekvencerski modul,

3) MixerGUI m_MixerGUI - referenca na kontrolni modul,

4) int m_LoopToolBarCount - dužina sekvence sporednog sekvencerskog modula.

Konstruktori su izvedeni kao kod osnovnog modula, pa imamo jedan osnovni i tri opcionalna konstruktora sa dodatnim parametrima:

1) SequencerGUI(),

2) SequencerGUI(int p_RowCount, int p_ColumnCount),

3) SequencerGUI(int p_RowCount, int p_ColumnCount, MainGUI p_MainGUI, SequencerWrapper p_SequencerGUI),

4) SequencerGUI(int p_RowCount, int p_ColumnCount, MainGUI p_MainGUI, SequencerWrapper p_SequencerGUI, String p_TableLTitle).

Inicijalizacijska metoda klase prikazana je slijedećim ispisom:

private void initialize(){

// postavljanje prikazivača ćelija za desnu tablicu

setRenderer(new TableCellColorEditorSequencer());

// postavljanje vidljivih alatnih traka

setVisibleToolbars(true, false, false, true, true, true);

// namještanje brojača stupaca

setBarCounterOptions(16, 16, 999, 1);

// inicijalizacija popisa traka

setTrackProperties(new Vector());

// spriječavanje podrazumijevane akcije pritiskom na "delete"

turnOffDefaultAction(jTableLeft, "DELETE");

// postavljanje glavne aplikacijske kontrole glasnoće

setMasterVolume(127);

// dodavanje slusača promjena na kontrolu glasnoće

jSliderVolume.addChangeListener(new ChangeListener() {

public void stateChanged(ChangeEvent e) {

if (m_MixerGUI != null) {

m_MixerGUI.setGain(jSliderVolume.getValue());

}

}

});

// dodavanje slušača za uklanjanje traka na lijevu tablicu

jTableLeft.addKeyListener(new KeyListener(){

public void keyPressed(KeyEvent e) {

}

public void keyReleased(KeyEvent e) {

if(e.getKeyChar() == KeyEvent.VK_DELETE)

{

int index = jTableLeft.getSelectedRow();

if(index != -1) removeTrackFromTable(index);

}

}

public void keyTyped(KeyEvent e) {

}

});

// dodavanje slušaca promjena na lijevu tablicu

// slusac vrsi sinhronizaciju kolekcija popisa traka

// izmedu sekvencera i looptoola

jDefaultTableModelLeft.addTableModelListener(

new TableModelListener(){

public void tableChanged(TableModelEvent e) {

if (!isLoading()) {

int index = e.getLastRow();

switch(e.getType()){

case TableModelEvent.INSERT:

break;

case TableModelEvent.DELETE:

if(index != 0)

index--;

break;

case TableModelEvent.UPDATE:

String tName = (String)jDefaultTableModelLeft.getValueAt(index, 0);

try {

// provjera da li traka postoji

if ( index <

setTrackProperties().size()) {

// update u kolekciji

getTrackProperties().get(index).setTrack(tName);

}

}

catch (Exception ex)

{}

break;

default:

return;

}

synchronizeWithloopTool(index);

}

}

});

}

Inicijalizacijska metoda organizirana je na isti način kao u klasi osnovnog sekvencerskog modula. Prvo se konfigurira modul, pa se dodaju slušači. Pri konfiguraciji modula, zadaje se prilagođeni editor ćelija, odabiru se vidljive alatne trake koje koristi ovaj sekvencer, namješta se početni broj stupaca tablice, inicijalizira se kolekcija sa svojstvima traka, namješta početni nivo glasnoće aplikacije i ukida funkcionalnost nad tablicom pri korištenju Delete tipke. Ova funkcionalnost se potom mijenja korištenjem slušača, i služi brisanju odabranih traka.

Modulu se dodaje slušač koji reagira na promjenu traka u tablicama, te koji usklađuje podatke o trakama između glavnog i sporednog sekvencera. Slušač na kontroli glasnoće se koristi za kontrolu ukupne glasnoće cijele aplikacije, i preko njega je glavni sekvencer povezan sa kontrolnim modulom. Promjena ukupne glasnoće nije vidljiva na kontrolama kontrolnog modula, jer se sve vrijednosti kontrola skaliraju prema njenoj trenutnoj vrijednosti.

Klasa sadrži slijedeće javne metode:

1) void addTrackToTable(Instrument p_Instrument) - metoda obavlja dodavanje instrumenata u tablicu i popunjavanje kolekcije sa svojstvima traka,

2) void removeTrackFromTable(int p_Index) - metoda uklanja traku iz tablice,

3) void addEmptyRowsToTables(int p_AddedRowsCount) - dodavanje praznih redaka u tablice,

4) void setBarCount(int p_BarCount) - metoda postavlja broj stupaca u tablici,

5) void synchronizeWithlo