Temat: Język operacji na typach muzycznych z możliwością generacji do pliku MIDI
Autor: Mikołaj Szawerda
- silne statyczne typowanie
- obiekty są mutowalne i przekazywane przez referencję
./gradlew clean build
java -jar build/libs/music-programming-language.jar -f simple.hs
- reprezentacja zapisu nutowego(w postaci drzewiastej)
- sekwencja nut - operator
|
C | E | G
- równoczesne zagranie - operator
&
C & E G
- sekwencja nut - operator
- możliwość zmiany właściwości reprezentacji nutowych - składnia modifier
[(C, 4) q, C, E]{oct=2, dur=q}
- zmiana długości i oktawy dla wszystkich elementów, które nie mają podanych wartości wprost - nakładanie szablonu struktury drzewiastej na liniową strukturę(Scale, Rythm, Tablica)
(0 | 1 & 0)>>[E, G, D]
- możliwość łańcuchowania operacji - operator
|>
(wyjście przekazuje jako pierwszy argument funkcji w kolejnym stopniu)[1,2,3] |> concat [1,2] |> len
- możliwość definiowania lambdy - składnia
with(Parameters...)->ReturnType {...}
- możliwość deklaratywnego tworzenia listy - składnia
[fun(x) <| x iterable]
- funkcje wbudowane do deklaratywnego odczytu i zapisu midi
open("name.mid", Phrase, 1)
iexport("name.mid")
- obsługa operacji
if(Bool){} else{}
oraz pętlifor(T x in Expr){}
((E, 4) w | (G, 4) w | (D, 4) w) & ((C, 4) w | (D, 4) w | (F, 4) w);
[[E, G, D] |> mel, [C, D, F] |> mel]{dur=w, oct=4} |> harm;
let a = [E, G, D]{oct=4} |> mel;
let b = a |>
transpose -1 |>
harm;
(Int, Int)->Int NWD;
NWD = with(Int a, Int b)->Int {
if(b!=0){
return NWD(b, a%b);
}
return a;
};
let randGen = with(Phrase music) -> Phrase {
if(scale |> isEmpty || rythm |> isEmpty){
"Provided scale or rythm is empty" |> panic;
}
let seed = (scale |> len * rythm |> len) % 3+1;
let maxLen = [scale |> len, rythm |> len] |> max;
let lowestNote = scale |> argmin;
Template form;
for(Int i in 1->seed){
let line = [rand() % maxLen <| dumb_temp 1->(rand()%4+1)*maxLen] |> mel;
form &= line;
}
return form>>scale*form>>rythm;
}
[a |> track Piano, b, markov(["song1.mid", "song2.mid"], 1)((C, 4) q), [C, E, G] |> randGen [q, w, h] |> track BagPipe] |>
song 120, "Guitar" |>
export "demo2.mid";
let x1 = [0, 1, 2] |> concat [3,4] |>len+2;//panic
let x2 = ([0, 1, 2] |> concat [3,4] |> dot (1+3*4^7-*1+3/6*12) |>len)+2;
Program := {Statement};
Statement := DeclOrAssig ";" |
Expression ";" |
IfStmt |
ForStmt;
DeclOrAssig := (Type|"let") identifier ["=" Expression] |
identifier assig_op Expression;
Expression := LambdaExpression |
ValueExpression;
LambdaExpression := LambdaDecl | "(" LambdaDecl ")";
LambdaDecl := "with" parameters_list "->" (Type|"Void") Block {PipeExpression}
parameters_list := "(" [parameter {"," parameter}] ")";
parameter := Type identifier;
Block := "{" {Statement | ControlStatement} ";" "}"
ControlStatement := IfStmt |
ForStmt |
ReturnStmt;
PipeExpression := "|>" inline_func_call;
inline_func_call := identifier [arguments_list];
arguments_list := UnPipeableOrNested {"," UnPipeableOrNested};
UnPipeableOrNested := UnPipeableExpr | "(" Expression ")";
IfStmt := "if" "(" Expression ")" Block ["else" IfStmt | Block];
ForStmt := "for" "(" Type identifier "in" Expression ")" Block;
ReturnStmt := "return" [Expression];
ValueExpression := UnPipeableExpr {PipeExpression};
UnPipeableExpr := MathExpr [ModifierExpr]
ModifierExpr := "{" modifier_item {"," modifier_item } "}";
modifier_item := identifier "=" Expression;
MathExpr := and_term {or_op and_term};
and_term := rel_term {and_op rel_term};
rel_term := add_term {rel_op add_term};
add_term := term {add_op term};
term := factor {mul_op factor};
factor := hfactor {h_op hfactor};
hfactor := (value | "(" ValueExpression ")" ) ["as" Type];
value := (unary_value | ArrayExpr);
unary_value := ([unary_op] (IdOrFuncCall | literal)) | NoteExpr;
IdOrFuncCall := identifier ["(" arguments_list ")"]
NoteExpr := Pitch [Duration] | "rest" rythm_lit;
Pitch := "(" pitch_lit "," Expression ")" | identifier;
Duration := rythm_lit;
ArrayExpr := "[" Expression ({"," Expression} | ComprExpr) ]";
ComprExpr := "<|" identifier Expression;
Type := LitType |
CpxType |
FuncType;
FuncType := "lam" (" [type_list] ")" "->" (Type|"Void");
type_list := Type {"," Type};
LitType := Int | Double |
Bool | String;
CpxType := Scale | Rythm | Progression | Groove |
Phrase | Track | Song |
[]Type;
h_op := ">>" | "^" | "->";
mul_op := "*" | "/" | "%" | "&";
add_op := "+" | "-" | "|";
rel_op := "==" | "<=" | ">=" | "!=" | ">" | "<";
and_op := "&&";
or_op := "||";
unary_op := "-" | "+";
assig_op := "|=" | "&=" | "*=" | "^=" | "%=" | "+=" | "-=" | "/=" | "=";
identifier := letter { letter | digit };
literal := int_lit | float_lit | string_lit;
int_lit := "0" | non_zero_digit {digit};
float_lit := int_lit "." [ int_lit ];
string_lit := \" {char} \";
pitch_lit := [A-G](#);
rythm_lit := (dl|l|w|h|q|e|s|t)(_(d|t));
Typ liniowy | Interpretacja |
---|---|
[]T | Lista elementów typu T |
Typ drzewiasty | Interpretacja |
---|---|
Phrase | Nuty(wysokość, długość) |
Template | Indeksy elementu z branej liniowej struktury |
- operator
|
tworzy listę w której pierwszy węzeł jest "wartownikiem" posiada domyślne wartości, dla węzłów, które któreś z pól mają NULL - operator
&
tworzy listę, której elementy mają wspólny węzęł pełniący rolę korzenia(oraz wartownika z domyślnymi wartościami) - przy użyciu składni modyfikatora
{id=x,id2=x1}
zmianie ulegają wartownicy - węzły
Phrase
posiadają wysokość oraz długość, węzłyTemplate
posiadają wszystkie dostępne pola typów wbudowanych - operacja
>>
splotu szablonu z strukturą liniową przechodzi drzewo i buduje strukturę identyczną jak struktura szablonu gdzie węzłami są elementy z liniowej struktury o indeksach z danego węzła szablonu z nałożonymi modyfikatorami(mutacją pól) z szablonu
- funkcja
open(String filename, [Int trackNumber]
czyta podany plik midi i parsuje go do drzewa muzycznego - funkcja
export(MusicTree t, String fileName)
zapisuje obiekt do pliku MIDI
mel([]T) := tworzy z listy sekwencję |
harm([]T) := tworzy z listy drzewo &
len/head := operacje na listach
panic(String msg) := rzuca wyjątkiem
transpose := operacje muzyczne
song(Phrase,Int,String) := tworzy piosenkę do exportu
at([]T, Int) := zwraca element z listy
Operator | Interpretacja |
---|---|
a->b | tworzy listę o zakresie od a do b |
a>>b | nałożenie templat a na b |
a*b | mnożenie |
a |>b b1,b2,... | pipe b(a, b1, b2, ...) |
Typ | Modyfikatory |
---|---|
Phrase | oct, dur |
Song | instrument, tempo |
oct - oktawa, dur długość nuty, tempo - BPM, len - długość w sekundach
Język zapewnia składnię x as Type
, która realizuje rzutowanie
Język nie zapewnia mechanizmu przechwytywania wyjątków, istnieje natomiast możliwość rzucenia wyjątku przy pomocy
funkcji panic(String)
, która skutkuje wyświetleniem treści komunikatu oraz zatrzymaniem interpretera
Java 21
Gradle
JUnit5, Mockito, assertj
-
javax.sound.midi
- wbudowana biblioteka w java pozwalająca wykonywać niskopoziomowe operacje na pliku midi - operowanie na poziomie Event'ow KeyOn KeyOff -
log4j
- bardziej przejrzyste logowanie niż System.out.println() -
lombok
- generacja Builder'ow i boilerplatu, oraz null checkow
odpowiedzialny za zamianę reprezentacji tekstowej na tokenową
Testy:
- rozponawanie literałów liczbowych/tekstowych/nutowych
- rozpoznawanie słów kluczowych języka
- rozpoznawanie identyfikatorów
- rozpoznawanie komentarzy, tokeny podawane z pozycją w pliku
budowa tablicy symboli i budowa AST testy
- budowa tablicy z odpowiednimi wartościami
- rozpoznawanie produkcji i odpowiednia struktura drzewa
- drzewiasta reprezentacja kodu
- przechowuje produkcje w postaci obiektów, w raz z dostępnym dla nich kontekstem
- sprawdzenie poprawności sekwencji produkcji oraz prosta optymalizacja kodu
Testy:
- wywołanie dla przypadków pozytywnych i negatywnych
- optymalizacja kodu nieosiągalnego, nieużywanego
- wykonuje program poprzez przechodzenie AST
- implementacja własnych typów Testy:
- wykonanie przykładowych kodów źródłowych wraz z oczekiwanymi zmianami stanu
- obsługa wystąpienia "panic" i wypisanie komunikatu
moduł implementujący drzewo typów muzycznych z iteratorem wg kolejności wystąpienia
konwertuje plik midi na reprezentacje użytą w AST/interpreterze
Testy:
- odczyt pliku midi i konwersja na podany typ Progression/Groove/Phrase/Track/Song
moduł odpowiedzialny za reprezentację i operacje na łańcuchach markova
Testy
- Inicjalizacja z podaną macierzą
- Przyjęcie Phrase
Generowanie kolejnych nut na podstawie aktualnego stanu i macierzy przejść
moduł odpowiedzialny za generowanie pliku midi z podanego obiektu Song/Track
Testy:
- generacja midi z zachowaniem ustalonego tempa/długości