simpleMonoComposer w całej swojej okazałości |
W dalszej części notki opisuję co ten program w ogóle robi, krótką historię jego powstania i prawdopodobnie najciekawszą część, czyli różne sprawy techniczne. Projekt na bitbucket (program wykonywalny oraz kod źródłowy):
[klik]
Ale o co w ogóle chodzi?
simpleMonoComposer, jak chciałem zaznaczyć w tytule programu, to bardzo proste narzędzie do komponowania monofonicznej muzyki. Bez większej głębi, bez większych możliwości, bez sensownego interfejsu. Wybierz dźwięk, ustaw jego parametry techniczne i dodaj do niezwykle nieczytelnego notatnika. Do tego opcje odegrania naszego dzieła (cóż, nie zawsze idealnie działa :) ) jak i jego eksportu do dwóch formatów, o których ostatnie, co można powiedzieć, jest to, że komukolwiek się to przyda. Ale taka miała być idea, stąd najlepiej przejść do następnego rozdziału tej opowieści, czyli...
...ale jak takie coś mogło w ogóle powstać? Po co?
Historia powstania jest co najmniej dziwna. Na studiach, na architekturze systemów komputerowych, zostałem zmuszony do opanowania assemblera MIPSa (link do wiki, gdyż zdaję sobie sprawę, że część osób mogłaby w tej chwili właśnie tam zajrzeć). Poza tym, jak robić proste programy na laboratoria, szybko zainteresowałem się, jak robić tam wszelkie inne, dziwne rzeczy, które prawdopodobnie nigdy w życiu mi się nie przydadzą. Wtedy to, w dokumentacji dołączonej do symulatora MARS (całkiem przyjemnego assemblera MIPS) znalazłem na liście przerwań obsługę MIDI. Tak mnie to zafascynowało, że stwierdziłem, że napiszę program, w którym będę mógł sobie wygenerować kod assemblera odgrywający melodyjki. Głownie dlatego, że nie chciało mi się tego ręcznie programować :) . Tak oto powstał simpleMonoComposer. Dodatkowa funkcja eksportu do kodu Turbo Pascala to już tylko i wyłącznie efekt mojego sentymentu do tego języka, napisana praktycznie na kolanie.
Drobne problemy techniczne
Niestety, symulator MARS zawiódł moje oczekiwania. Odtwarzenie MIDI jest tam bardzo niepłynne i należy to traktować tylko jako wybryk fantazji jego programistów, a nic sensownego, wartego uwagi. Dzięki temu, dalsze pisanie tego jako zwykłego programu, nie miało już dla mnie sensu. Stąd interfejs jest taką toporną tymczasówką zrobioną na szybko w Eclipsowym Swing Designerze.
Najciekawsze, czyli zagadnienia związane z kodem
Jak nadmieniłem wcześniej, GUI nie było moim najważniejszym celem przy pisaniu tego, stąd jego kod to jeden wielki śmietnik zafundowany przez Swing Designera i moje momentami kombinowanie z doprowadzeniem go do ładu. Pomijając ten kod, zastosowałem tu parę rzeczy, które jak sądzę mogą mi się kiedyś przydać, a możliwe, że także innym.
Wczytanie całości pliku do Stringa bez użycia pętli
Bardzo przyjemny trick, który swego czasu znalazłem na sieci w tym miejscu. Na tyle sprytne, że myślę, iż warto to powtórzyć tutaj:
text = new Scanner(activeFile,"UTF-8").useDelimiter("\\A").next();
Inne zapożyczone rozwiązania pomijam, gdyż nie są aż takie wyjątkowo przydatne. Zostały one oznaczone w kodzie źródłowym.
Obliczenie częstotliwości dźwięku z MIDI
Gdy postanowiłem dopisać eksport do kodu Turbo Pascala, potrzebne było mi obliczenie częstotliwości konkretnego dźwięku. Jest to zagadnienie bardziej matematyczne niż programistyczne, jednak mimo to stwierdziłem, że warto je opisać. Kod, który odpowiada u mnie za konwersję, wygląda następująco:
Jakby to stwierdził mój wykładowca od algorytmów, można by to zmieścić w samym returnie. Jednak, czasem dla czytelności wolę sobie pozwolić na nieco więcej linijek.
Wzór, który ta metoda wykonuje, prezentuje się w zapisie matematycznym następująco:
Jest to dopasowany pod format MIDI wzór, który możemy znaleźć chociażby na Wikipedii. Względem oryginalnego wzoru została zmieniona wartość 49 na 69. Bierze się to stąd, że format MIDI obsługuje więcej dźwięków niż fortepian. Wzór odnosi się do dźwięku A4, który posiada częstotliwość 440 Hz. Na standardowej klawiaturze jest to 49. klawisz, a w MIDI jest on pod numerem 69.
Jak łatwo się domyśleć, wzór ten zwraca nam także wartości ułamkowe, a w funkcji obsługi PC Speakera w Turbo Pascalu muszę podać liczbę całkowitą. Stąd zaokrąglenie. Dla wielu osób nie powinno być to zauważalne ;) .
private int convertPitchToFrequency(int pitch){ double tmp; tmp=((double)pitch-69.0)/12; tmp=Math.pow(2,tmp)*440; return (int)Math.round(tmp); }
Wzór, który ta metoda wykonuje, prezentuje się w zapisie matematycznym następująco:
![]() |
n - numer dźwięku w MIDI |
Jak łatwo się domyśleć, wzór ten zwraca nam także wartości ułamkowe, a w funkcji obsługi PC Speakera w Turbo Pascalu muszę podać liczbę całkowitą. Stąd zaokrąglenie. Dla wielu osób nie powinno być to zauważalne ;) .
Odtworzenie dźwięku MIDI
Jest to ostatnia, ciekawsza rzecz w kodzie, którą chciałem zaprezentować. Co prawda temat ten znajdziemy w Java Tutorials, jednak moja wersja jest nieco bardziej rozbudowana. Wygląda następująco:
Synthesizer synth = MidiSystem.getSynthesizer(); ShortMessage setInstrument = new ShortMessage(); ShortMessage playNote = new ShortMessage(); ShortMessage stopNote = new ShortMessage(); synth.open(); Receiver receiver = synth.getReceiver(); setInstrument.setMessage(ShortMessage.PROGRAM_CHANGE,0,tmp.getInstrument(),0); playNote.setMessage(ShortMessage.NOTE_ON,0,tmp.getPitch(),tmp.getVolume()); stopNote.setMessage(ShortMessage.NOTE_OFF,0,tmp.getPitch()); receiver.send(setInstrument, -1); receiver.send(playNote, -1); try { Thread.sleep(tmp.getDuration()); } catch (InterruptedException e) { e.printStackTrace(); } finally { receiver.send(stopNote, -1); } synth.close();
Teraz krok po kroku. W czterech pierwszych linijkach tworzymy sobie przydatne nam zmienne. Pierwsza będzie przechowywać używany przez nas syntezator - w tym celu posłużymy się głównym systemowym. Trzy kolejne to obiekty klasy ShortMessage - wysyłamy za ich pomocą informacje do syntezatora. synth.open() rozpoczyna działanie syntezatora. Teraz tylko musimy jakoś przesyłać do niego, co ma wykonywać. W tym celu wyciągamy do zmiennej receiver "odbiornik" naszego syntezatora. Następne trzy linie ustawiają nam wiadomości do syntezatora (kolejno): ustawienie instrumentu, rozpoczęcie grania dźwięku, zakończenie grania dźwięku. W przypadku simpleMonoComposera posiadam obiekt tmp klasy Note, która zwraca mi wartości MIDI ustawionego instrumentu (getInstrument()), wysokości dźwięku (getPitch()), jego głośności (getVolume()) i długości (getDuration()). W miejscach, gdzie użyłem te metody, do własnych celów należy podawać samemu wartości typu int odpowiadające za jedną z tych rzeczy. Dla przykładu, zbudujmy trzy właśnie omawiane linijki dla następującego dźwięku:
W MIDI posiada on wartość 61. Przedział głośności w MIDI to 0-127, jednak załóżmy, że chcemy zagrać standardową głośnością, czyli 100. Do tego, odegrajmy go fortepianem, który w MIDI jest pod numerem 0. (tutaj lista wszystkich instrumentów w MIDI)
Kod będzie wyglądał wtedy następująco:
![]() |
(źródło: Wikipedia) |
Kod będzie wyglądał wtedy następująco:
setInstrument.setMessage(ShortMessage.PROGRAM_CHANGE,0,0,0); playNote.setMessage(ShortMessage.NOTE_ON,0,61,100); stopNote.setMessage(ShortMessage.NOTE_OFF,0,61);
Niestety, metoda usypiania wątku jest nieperfekcyjna - czasem zdarza mi się, że nuta jest wygrywana dłużej, niż być powinna. Jednak to już wspólna wina mojego komputera i Javy :) .
Podsumowując...
Mimo, że symulator MARS zawiódł moje oczekiwania związane z przerabianiem go w maszynkę do odgrywania muzyki, to jednak samo pisanie tego programu poduczyło mnie wielu rzeczy. Prawdopodobnie dowiedziałbym się nawet więcej, gdybym ostatecznie zdecydował się na obsługę formatu MIDI - czy to do importu, czy chociaż eksportu. Ciekawe dla mnie było to, że program od początkowych założeń tak się zmienił - nie miałem w ogóle w planach np. odtwarzania skomponowanej sekwencji. Po skończeniu programu uważam, że niestety jest to jego najlepiej działająca część.
A dodatkowym słowem zakończenia, coś co mnie bardzo zastanawiało, gdy myślałem nad napisaniem eksportu do MIDI - czemu typ byte w Javie przechowuje wartości z zakresu [-128,127], a nie [0,255]? I kto w ogóle wpadł na to, że to będzie dobry pomysł?!
Brak komentarzy:
Prześlij komentarz