Compare commits
10 Commits
create-vir
...
add-readme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d720266b87 | ||
|
|
1d01d1c373 | ||
|
|
231ce4c4c5 | ||
|
|
aa38e6b7b6 | ||
|
|
f5c72dd8a5 | ||
|
|
55adf616c5 | ||
|
|
06579e8e78 | ||
|
|
2c1f5f7942 | ||
|
|
83ef3f6888 | ||
|
|
3061dae723 |
1
Pipfile
@@ -10,6 +10,7 @@ sounddevice = "*"
|
|||||||
soundfile = "*"
|
soundfile = "*"
|
||||||
numpy = "*"
|
numpy = "*"
|
||||||
matplotlib = "*"
|
matplotlib = "*"
|
||||||
|
tkinter = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.7"
|
python_version = "3.7"
|
||||||
|
|||||||
36
examples/adeste.mus
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
println("Adeste Fideles");
|
||||||
|
println("John Francis Wade");
|
||||||
|
|
||||||
|
s1 = [@g, @g:2, @d, @g, @a:2, @d:2, @h, @a, @h, @c5, @h:2, @a, @g];
|
||||||
|
s2 = [@g:2, @f#, @e, @f#, @g, @a, @h, @f#:2, @e:4d, @d:8, @d:2d, 4];
|
||||||
|
s3 = [@d5:2, @c5, @h, @c5:2, @h:2, @a, @h, @g, @a, @f#:4d, @e:8, @d];
|
||||||
|
s4 = [@g, @g, @f#, @g, @a, @g:2, @d, @h, @h, @a, @h, @c5, @h:2, @a];
|
||||||
|
s5 = [@h, @c5, @h, @a, @g, @f#:2, @g, @c5, @h:2, @a:4d, @g:8, @g:1];
|
||||||
|
|
||||||
|
S = s1 + s2 + s3 + s4 + s5;
|
||||||
|
|
||||||
|
a1 = [@d, @d:2, @d, @d, @e:2, @d:2, @d, @d, @d, @e, @d:2, @d, @h3];
|
||||||
|
a2 = [@h3, @c#, @d, @c#, @d, @d, @d, @d, @d:2, @c#:4d, @d:8, @d:2d, 4];
|
||||||
|
a3 = [@d:2, @e:8, @f#:8, @g, @g, @f#, @g:2, @d, @d, @e, @e, @d:2, @d];
|
||||||
|
a4 = [@d, @d:1, @d:2d, @d, @d:1, @d:2d];
|
||||||
|
a5 = [@g, @f#, @g, @d, @d:8, @c#:8, @d:2, @d, @e, @d:2, @d:4d, @h3:8, @h3:1];
|
||||||
|
|
||||||
|
A = a1 + a2 + a3 + a4 + a5;
|
||||||
|
|
||||||
|
t1 = [@h3, @h3:2, @h3, @h3, @c:2, @a3:2, @g3, @a3, @g3, @g3, @g3:2, @f#3, @g3];
|
||||||
|
t2 = [@g3:2, @a3, @a3, @a3, @g3, @f#3, @d3, @a3:2, @g3:4d, @f#3:8, @f#3:2d, 4];
|
||||||
|
t3 = [@h3:2, @c, @d, @c:2, @d:2, @d, @g3, @h3, @c, @a3:2, @f#3];
|
||||||
|
t4 = [@h3, @h3, @a3, @h3, @c, @h3:2d, @g3, @g3, @f#3, @g3, @a3, @g3:2, @f#3];
|
||||||
|
t5 = [@d, @d, @d, @a3, @a3, @a3:2, @g3:2, @g3:2, @f#3:4d, @g3:8, @g3:1];
|
||||||
|
|
||||||
|
T = t1 + t2 + t3 + t4 + t5;
|
||||||
|
|
||||||
|
b1 = [@g3, @g3:2, @h3, @g3, @g3:2, @f#3:2, @g3, @f#3, @g3, @c3, @d3:2, @d3, @e3];
|
||||||
|
b2 = [@e3:2, @d3, @a2, @d3, @h2, @f#2, @g2, @a2:2, @a2:4d, @d3:8, @d2:2d, 4];
|
||||||
|
b3 = [@h3:2, @a3, @g3, @a3:2, @g3:2, @f#3, @g3, @e3, @c3, @d3:2, @d3];
|
||||||
|
b4 = [4, 1, 1, 1, 2, 4];
|
||||||
|
b5 = [@g3, @a3, @g3, @f#3, @e3, @d3, @c3, @h2, @c3, @d3:2, @d3:4d, @g2:8, @g2:1];
|
||||||
|
|
||||||
|
B = b1 + b2 + b3 + b4 + b5;
|
||||||
|
|
||||||
|
synth({ tuning -> 431, attack -> 100, decay -> 1.2, overtones -> [0.7, 0, 0.2, 0.1] }, S, A, T, B);
|
||||||
42
examples/bohemian.mus
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
println("Bohemian Rhapsody :: part");
|
||||||
|
println("by Queen");
|
||||||
|
|
||||||
|
m1 = [@d:8, @d:8, @d:2, 8, @b3:8, @c:8, @d:16, @d:16, @d, 4, 8, @d:16, @d:16];
|
||||||
|
m2 = [@eb:8, @f:8, @eb:8, @d:8, @c, @c:8, @d:8, @eb:16, @f:8, @eb:8d, @d:8, @c, 4, @d:8, @d:8, @d:2, @d:8, @f:8];
|
||||||
|
m3 = [@a:8d, @g:16, @g:2, 8, @g:8, @b:8, @b:8, @b:8, @b:8, @b:8d, @g:16, @d:8d, @c:16, @c:2, 2];
|
||||||
|
M1 = [1, 1] + m1 + m2 + m3;
|
||||||
|
|
||||||
|
p_B = [@d:8, @f3:8, @b3:8, @d:8, @g:8, @f3:8, @f:8, @f:8];
|
||||||
|
p_g = [@d:8, @g3:8, @b3:8, @d:8, @a:8, @g3:8, @g:8, @b3:8];
|
||||||
|
p_c = [@g:8, @c:8, @eb:8, @g:8, @d5:8, @c:8, @c5:8, @c:8];
|
||||||
|
p_cF = [@b:8, @c:8, @eb:8, @g:8, @a:8, @eb:8, @f:8, @c:8];
|
||||||
|
p1 = [@eb:8, @c:8, @eb:8, @g:8, @eb:8, @g:8, @eb:8, @g:8, @eb:8, @a3:8, @eb:8, @g:8, @eb:8, @g:8, @eb:8, @g:8];
|
||||||
|
|
||||||
|
P1 = p_B + p_B + p_B + p_g + p_c + p_cF + p_B + p_g + p1;
|
||||||
|
|
||||||
|
b1 = [@b3:1, @g3:1, @c3:1, @c3:2, @f3:2];
|
||||||
|
b2 = [@b3:2d, @a3, @g3:1, @c3:2, @h2, @b2, @a2:2, @ab2, @g2];
|
||||||
|
B1 = [@b3:1, @b3:1] + b1 + b2;
|
||||||
|
|
||||||
|
# Eb
|
||||||
|
m1 = [@g:16, @g:16, @g:2, 8, @f:8, @g:16, @ab:16, @g:2, 4, 8, @g:16, @g:16, @ab:8d, @g:16, @g:8, @f:16, @f:2, @b3:16];
|
||||||
|
m2 = [@b3:8, @f:8, @f:8, @g:16, @g:8d, @ab:8, @ab:8, @b:16, @ab:16, @ab:8, @g:8, 8, @f:16, @g:16, @b:4d, @f:16, @g:16];
|
||||||
|
m3 = [@eb:4d, @b3:16, @b3:16, @h3:8, @db:8, @h3:16, @db:16, @h3:8, @b3:2, 2];
|
||||||
|
M2 = m1 + m2 + m3;
|
||||||
|
|
||||||
|
p_Eb = [@b:8, @eb:8, @eb5:8, @eb:8, @b:8, @eb:8, @f:8, @b:8];
|
||||||
|
p_f = [@ab:8, @f:8, @ab:8, @ab:8, @ab:8, @eb:8, @ab:8, @d:8];
|
||||||
|
p_B = [@d, @f, @g, @ab];
|
||||||
|
p_EbB = [@b:8, @eb:8, @eb5:8, @eb:8, @b:8, @d:8, @b:8, @d:8];
|
||||||
|
p_cf = [@g:8, @c:8, @g:8, @c:8, @h3:2];
|
||||||
|
P2 = p_Eb + p_c + p_f + p_B + p_EbB + p_cf;
|
||||||
|
|
||||||
|
b1 = [@eb3:2d, @d3, @c3:1, @f3:4d, @e3:8, @eb3, @d3, @b2, @b2, @b2, @b2, @eb3:2, @d3:2];
|
||||||
|
b2 = [@c2:2, @f2:2, @eb2:2, 2];
|
||||||
|
B2 = b1 + b2;
|
||||||
|
|
||||||
|
M = M1 + M2;
|
||||||
|
P = P1 + P2;
|
||||||
|
B = B1 + B2;
|
||||||
|
|
||||||
|
synth({ tuning -> 432, bpm -> 72, overtones -> [0.5, 0.3, 0.15, 0.05], decay -> 0.8 }, transpose(12, M), P, B);
|
||||||
120
examples/cantina.mus
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
println("Star Wars :: Cantina Band");
|
||||||
|
println("by John Williams");
|
||||||
|
|
||||||
|
cb1 = [@a, @d5, @a, @d5] + [@a:8, @d5, @a:8, 8, @g#:8, @a];
|
||||||
|
cb2 = [@a:8, @g#:8, @a:8, @g:8, 8, @f#:8, @g:8, @gb:8];
|
||||||
|
cb3 = [@f:4d, @d:2, 8];
|
||||||
|
cb4 = [@g:8, 8, @g:4d, @f#:8, @g];
|
||||||
|
cb5 = [@c5:8, @b, @a, @g:4d];
|
||||||
|
cb6 = [@c5:8, 8, @c5:4d, @a:8, @g];
|
||||||
|
cb7 = [@f:4d, @d:2, 8];
|
||||||
|
cb8 = [@d:2, @f:2, @a:2, @c5:2];
|
||||||
|
cb9 = [@eb5, @d5, @g#:8, @a, @f:8];
|
||||||
|
|
||||||
|
CB1 = cb1 + cb2 + cb3 + cb1 + cb4 + cb5 + cb1 + cb6 + cb7 + cb8 + cb9 + [1];
|
||||||
|
|
||||||
|
cb11 = 2 ^ [8, @a5, @f5:8, @a5:8, 8, 4];
|
||||||
|
cb12 = [8, @a5, @f5:8, @g#5:8, @a5, @f5:8];
|
||||||
|
cb13 = [@f5:4d, @d5:2, 8];
|
||||||
|
cb14 = [8, @a5, @f5:8, @g#5:8, @a5, @g5:8];
|
||||||
|
cb15 = [@g5:2, @c5:2];
|
||||||
|
cb16 = [@b:8, @d5:8, @f5, @h:8, @d5:8, @f5];
|
||||||
|
cb17 = [@g#:8, @a5, @d5:2, 8];
|
||||||
|
cb18 = [@d:8, @f:8, @h:8, @d5:8, @g#:8, @a, @f:8];
|
||||||
|
cb19 = [@f:2d, 4];
|
||||||
|
|
||||||
|
CB2 = cb11 + cb12 + cb13 + cb11 + cb14 + cb15 + cb11 + cb12 + cb13 + cb16 + cb17 + cb18 + cb19;
|
||||||
|
|
||||||
|
cb20 = [@f, 8, @ab, @f:8, @g];
|
||||||
|
cb21 = [8, @f:8, @ab:8, @f:8, @g:8, @f:8, @ab:8, @d:8];
|
||||||
|
cb22 = [@f, 8, @ab, @f:8, @g];
|
||||||
|
cb23 = [@f:8, @f:8, @ab:8, @f:8, @g:8, @f:8, @ab:8, @f:8];
|
||||||
|
cb24 = [@g:8, @f:8, @ab:8, @f:8, @g:8, @f:8, @ab:8, @d:8];
|
||||||
|
cb25 = [@f:8, @f:8, @ab:8, @f:8, @ab:8, @f, @f:8, @f:2d, 4];
|
||||||
|
|
||||||
|
CB3 = cb20 + cb21 + cb22 + cb21 + cb22 + cb21 + cb23 + cb24 + cb22 + cb21 + cb22 + cb21 + cb22 + cb21 + cb25;
|
||||||
|
|
||||||
|
cb26 = [@c5, 8, @e5:8, 4, @g5];
|
||||||
|
cb27 = [@g5:8, @g5, @g5:8, @g5, @e5:8, @c5:8];
|
||||||
|
cb28 = [@f5, @f5:8, @f5:8, @f5:8, @g5, @a5:8, @a5:1];
|
||||||
|
cb29 = [@e5, @e5, @g5:8, @g5, @c5:8];
|
||||||
|
cb30 = [@c5:8, @e5, @c5:8, @e5, @g5];
|
||||||
|
cb31 = [@f5, @f5:8, @f5, @g5, @a5:8, @a5:1];
|
||||||
|
cb32 = [@b5, @b5, @d6, @b5:8, @db6:8];
|
||||||
|
cb33 = [@db6:8, @db6:8, @b5, @db6, @b5];
|
||||||
|
cb34 = [@f5, @f5, @f5:8, @ab5, @d6:8, @d6:1];
|
||||||
|
cb35 = [8, @b, @d5:8, @g5, @g5];
|
||||||
|
cb36 = [8, @e5, 8, @e5, @e5];
|
||||||
|
cb37 = [8, @f5, @f5:8, @f5, @f5:8, @f5:8];
|
||||||
|
cb38 = [@f5:8, @f5, @f5:8, @f5:8, @f5:8, @f5];
|
||||||
|
|
||||||
|
CB4 = cb26 + cb27 + cb28 + cb29 + cb30 + cb31 + cb32 + cb33 + cb34 + cb35 + cb36 + cb37 + cb38;
|
||||||
|
|
||||||
|
cb39 = [@ab:8, @gb:8, @ab:8, @gb:8, @ab:8, @gb:8, @ab:8, @gb:8];
|
||||||
|
cb40 = [@ab:8, @db5, @ab:8, @db5, @ab:8, @db5:8];
|
||||||
|
cb41 = [8, @ab:8, @b:8, @h:8, @c5:8, @h:8, @b:8, 8];
|
||||||
|
cb42 = [@ab:8, @db5, @ab:8, @db5, @ab:8, @a:8];
|
||||||
|
cb43 = [@b:8, @a:8, @b:4d, @a:8, @b:8, @a:8];
|
||||||
|
cb44 = [@b:8, @a:8, @b:8, @h:8, @c5:8, 8, @gb:8, @g:8];
|
||||||
|
cb45 = [@b:8, @a:8, @b:4d, @a:8, @b:8, @h:8];
|
||||||
|
cb46 = [@c5:8, @g:8, @e:8, @c:8, 8, @c:8, @db:8, @c:8];
|
||||||
|
cb47 = [@db:8, @b3, @db:8, @fb:8, @db, @fb:8];
|
||||||
|
cb48 = [@f:8, @e:8, @f:8, @gb:8, @g:8, @c, @g:8];
|
||||||
|
cb49 = [@ab:8, @g:8, @ab:8, @a:8, @b:8, @a:8, @b:8, @h:8];
|
||||||
|
cb50 = [@c5:8, @c5:8, 4, @c5:8, @c5:8, 4];
|
||||||
|
cb51 = [@c5, @e5:2, @f5:8, @db5:8];
|
||||||
|
cb52 = [@ab:8, @f:8, @fb:8, @eb:8, @d:8, 4, @gb:8];
|
||||||
|
cb53 = [@c5:8, @c5, @gb:8, @c5:8, @c5, @h:8];
|
||||||
|
cb54 = [@c5:8, @c5, @h:8, @c5:8, @c5, @c5:8];
|
||||||
|
cb55 = [@db5:8, @fb:8, @db5:4d, @fb:8, @db5:8, @fb:8];
|
||||||
|
cb56 = [@db5:8, @fb:8, @db5:8, @fb:8, @eb:8, @c5, @c5:8];
|
||||||
|
cb57 = [@db5:8, @fb:8, @db5:8, @fb:8, @eb:8, @c5, @h3:8];
|
||||||
|
cb58 = [@b3:8, @db:8, @gb:8, @b:8, @c:8, @e:8, @g:8, @c5:8];
|
||||||
|
cb59 = [@db5:8, @db, @c:8, @db, 4];
|
||||||
|
cb60 = [4, @a3, @b3, @a3, @b3, @a3, @gb3:8, @f3:8, @d3:8, 8];
|
||||||
|
cb61 = [8, @a3, @a3, @a3, @a3, @a3, @a3:8, @a3:8, @a3];
|
||||||
|
|
||||||
|
CB5 = cb39 + cb40 + cb41 + cb39 + cb42 + cb43 + cb44 + cb39 + cb42 + cb45 + cb46 + cb47 + cb48 + cb49 + cb50 + cb50 + cb51 + cb52 + cb53 + cb54 + cb55 + cb56 + cb55 + cb56 + cb55 + cb57 + cb58 + cb59 + cb60 + cb60 + cb60;
|
||||||
|
|
||||||
|
CB = CB1 + CB2 + CB3 + (2 ^ CB4) + CB5;
|
||||||
|
|
||||||
|
b1 = (3 ^ [@d3, 4, @a2, 4]) + [@f3, 4, @c3, 4, @d3, 4, @a2, 4, @d3, 4, @a2, 4, @g2, 4, @g2, 4];
|
||||||
|
b2 = [@c3, @d3, @d#3, @e3, @d3, 4, @a2, 4, @d3, 4, @a2, 4, @c3, 4, @c3, 4];
|
||||||
|
b3 = [@f3, 4, @c3, 4, @b2, 4, @b2, 4, @f3, 4, @d3, 4, @g3, 4, @c3, 4, @f3, @e3, @d3, @c3];
|
||||||
|
|
||||||
|
B1 = b1 + b2 + b3;
|
||||||
|
|
||||||
|
b4 = (3 ^ [@d3, 4, @a2, 4]) + [@d3:4d, @g#2:4d, @a2];
|
||||||
|
b5 = (3 ^ [@d3, 4, @a2, 4]) + [@eb3:2, @c3, @c3];
|
||||||
|
b6 = (4 ^ [@d3, 4, @a2, 4]);
|
||||||
|
b7 = [@b2:8, @b2:8, 4, @h2:8, @h2:8, 4, @c3:4d, @d3:8, 8, @d3:8, @d3, @g2, 4, @c3:4d, @f3:4d, @e3, @d3, @c3];
|
||||||
|
|
||||||
|
B2 = b4 + b5 + b6 + b7;
|
||||||
|
|
||||||
|
b8 = 3 ^ [@d3, @c3, @b2, @a2, @g2, @f2, @e2, @d2];
|
||||||
|
b9 = [@d3, 4, 2, 2, 4, @a2];
|
||||||
|
b10 = [@d3, @b2, @a2, @a2, @d3:8, 8, 4, 2];
|
||||||
|
|
||||||
|
B3 = b8 + b9 + b8 + b10;
|
||||||
|
|
||||||
|
b11 = [@c3, 4, @g2, 4, @c3, 8, @b2:4d, @g2, @f2, 4, @c3, 4, @f3, 8, @e3:4d, @d3];
|
||||||
|
b12 = [@c3, 4, @g3, 4, @c3, 8, @b2:4d, @g2, @f2, 4, @c3, 4, @d3, 8, @f#2:4d, @a2];
|
||||||
|
b13 = [@b2, 4, @b2, 4, @h2, 4, @h2, 4, @c3, 4, @c3, 4, @d3, 8, @d3:4d, @f#2];
|
||||||
|
b14 = [@g2, 4, @d3, 4, @c3, 8, @c3:4d, @g3, @f3, 4, @db3, 4, @f3, @f3, @e3, @d3];
|
||||||
|
|
||||||
|
B4 = b11 + b12 + b13 + b14;
|
||||||
|
|
||||||
|
b15 = [@db3, @c3, @b2, @ab2, @gb2, @f2, @eb2, @db2, @db3, @b2, @a2, @ab2];
|
||||||
|
b16 = [@db3, @c3, @b2, @ab2, @gb2, @f2, @eb2, @db2, @eb2, @e2, @f2, @gb2];
|
||||||
|
b17 = [@e2, @d2, @h2, @ab2, @h2, @ab2, @f2, @eb2, @db2, @c2, @b1, @ab1];
|
||||||
|
# ^ or B?
|
||||||
|
b18 = [@gb2, @gb2, @gb2, @db3, @c3, @c3, @e3, @g3, @gb3, @gb3, @g3, @g3];
|
||||||
|
b19 = [@ab3, @db3, @c3, @c3, @f3, @c3, @gb3, @gb3, @c3, 4, @c3, 4];
|
||||||
|
b20 = [@f3, 4, @f3, 4, @c3, @c3, 4, @db3:8, @f3:8, @ab3:8, @a3:8, @b3:8, @h3:8, @c:8, 8, 8, 16, @g3:16];
|
||||||
|
b21 = [@c3, 4, @c3, 4, @f3, 4, @f3, 4, @gb2, 4, @ab2, 4, @a2, 4, @db3, 4, @gb2, 4, @ab2, 4, @a2, 4, @db3, 4, @d, 4, @eb, 4];
|
||||||
|
b22 = [@e3, 4, @f3, 4, @gb3, 4, @g3, 4, @db3, 4, @db3, 4];
|
||||||
|
B5 = b15 + b16 + b17 + b18 + b19 + b20 + b21 + b22;
|
||||||
|
|
||||||
|
B = B1 + B2 + B3 + (2 ^ B4) + B5;
|
||||||
|
|
||||||
|
synth({ tuning -> 432, bpm -> 270 }, flat(CB), flat(B));
|
||||||
31
examples/les_anges.mus
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
println("Les Anges dans nos campagnes");
|
||||||
|
|
||||||
|
s = [@a, @a, @a, @c5, @c5:4d, @b:8, @a:2, @a, @g, @a, @c5, @a:4d, @g:8, @f:2];
|
||||||
|
sc = [@c5:2, @d5:8, @c5:8, @b:8, @a:8, @b:2, @c5:8, @b:8, @a:8, @g:8, @a:2, @b:8, @a:8, @g:8, @f:8, @g:4d, @c:8, @c:2, @f, @g, @a, @b];
|
||||||
|
sca = [@a:2, @g, 4];
|
||||||
|
scb = [@a:2, @g:2, @f:2d, 4];
|
||||||
|
|
||||||
|
S = flat(2^s, sc, sca, sc, scb);
|
||||||
|
|
||||||
|
a = [@f, @f, @e, @e, @g, @e, @f:2, @f, @e, @f, @f, @f, @e, @f:2];
|
||||||
|
ac = [@f, @a:8, @g:8, @f:2d, @g:8, @f:8, @e:2d, @f:8, @e:8, @d:2, @c:4d, @c:8, @c:2, @c, @e, @f, @g];
|
||||||
|
aca = [@f:2, @e, 4];
|
||||||
|
acb = [@f:2, @e:2, @c:2d, 4];
|
||||||
|
|
||||||
|
A = flat(2^a, ac, aca, ac, acb);
|
||||||
|
|
||||||
|
t = [@c, @c, @c, @c, @d, @c, @c:2, @c, @c, @c, @c, @c:4d, @b3:8, @a3:2];
|
||||||
|
tc = [@a3:2, @d:1, @c:1, @b3:2, @g3, @f3, @e3:2, @f3, @c, @c, @d];
|
||||||
|
tca = [@c:2, @c, 4];
|
||||||
|
tcb = [@c:2d, @b3, @a3:2d, 4];
|
||||||
|
|
||||||
|
T = flat(2^t, tc, tca, tc, tcb);
|
||||||
|
|
||||||
|
b = [@f3, @f3, @a3, @a3, @g3, @c3, @f3:2, @f3, @c3, @f3, @a3, @c, @c3, @f3:2];
|
||||||
|
bc = [@f3:2, @d3, @f3, @g3:2, @c3, @e3, @f3:2, @b2, @d3, @e3, @d3, @c3, @b2, @a2, @c3, @f3, @b2];
|
||||||
|
bca = [@c3:2, @c3, 4];
|
||||||
|
bcb = [@c3:1, @f3:2d, 4];
|
||||||
|
|
||||||
|
B = flat(2^b, bc, bca, bc, bcb);
|
||||||
|
|
||||||
|
synth({ tuning -> 432, overtones -> [0.6, 0.3, 0.07, 0.03], attack -> 200, decay -> 0.6 }, S, A, T, B);
|
||||||
18
examples/per_crucem.mus
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
println("Per Crucem - canon");
|
||||||
|
|
||||||
|
d1 = [@d:2, @d:2, @c:2, @f:4d, @f:8, @f, @f, @e, @d, @e:2, @d:2];
|
||||||
|
d2 = [@d:8, @f:8, @a:8, @d5:8, @b:8, @a:8, @g, @c:8, @e:8, @g:8, @c5:8, @a:8, @g:8, @f, @b3:8, @d:8, @f:8, @b:8, @g:8, @f:8, @e, @a, @e, @f:2];
|
||||||
|
d3 = [@a:2, @b:2, @c5:2, @a:4d, @a:8, @b, @f, @g, @g, @a, @e, @f:2];
|
||||||
|
d4 = [@d5:8, @c5:8, @b:8, @a:8, @g:8, @a:8, @b, @c5:8, @b:8, @a:8, @g:8, @f:8, @g:8, @a, @b:8, @a:8, @g:8, @f:8, @e:8, @f:8, @g, @a, @e, @f:2];
|
||||||
|
d5 = [@d5:2, @d5:2, @c5:2, @c5:2, @d5:8, @c5:8, @b:8, @a:8, @b, @b, @a:2, @a:2];
|
||||||
|
d6 = [@d:8, @d:8, @d:8, @d:8, @g:8, @g:8, @g, @c:8, @c:8, @c:8, @c:8, @f:8, @f:8, @f, @b3:8, @b3:8, @b3:8, @b3:8, @e:8, @e:8, @e, @a3, @a3, @d:2];
|
||||||
|
|
||||||
|
p = [1, 1, 1, 1];
|
||||||
|
|
||||||
|
S = d1 + d2 + d3 + d4 + d5 + d6 + d1 + d2 + d3;
|
||||||
|
A = p + d1 + d2 + d3 + d4 + d5 + d6 + d1 + d2;
|
||||||
|
T = p + p + d1 + d2 + d3 + d4 + d5 + d6 + d1;
|
||||||
|
B = p + p + p + d1 + d2 + d3 + d4 + d5 + d6;
|
||||||
|
|
||||||
|
wv = wave(S, A, transpose(-12, T), transpose(-12, B));
|
||||||
|
synth(wv);
|
||||||
31
examples/quem_pastores.mus
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
println("Narodził się nam Zbawiciel / Quem pastores laudavere");
|
||||||
|
println("mel.: XIV w.");
|
||||||
|
println("sł.: XV - XVI w.");
|
||||||
|
println("harm.: Bartłomiej Pluta");
|
||||||
|
|
||||||
|
s1 = [@f:2, @a, @c5:2, @a, @c5:2, @d5, @c5, @g:2];
|
||||||
|
s2 = [@a:2, @c5, @b:2, @g, @f:2, @d, @e, @c:2];
|
||||||
|
s3 = [@a:2, @b, @c5:2, @d5, @c5:2, @g, @a, @f:2];
|
||||||
|
s4 = [@b:2, @b, @a, @g, @a, @f, @d, @e, @f:2d];
|
||||||
|
|
||||||
|
a1 = [@c:2, @f, @g:2, @f, @f, @g, @f, @f, @f, @e];
|
||||||
|
a2 = [@f, @e, @eb, @d:2, @c, @d:2, @d, @c, @c, @b3];
|
||||||
|
a3 = [@c:2, @c, @f, @d, @g, @g:2, @e, @e, @d:2];
|
||||||
|
a4 = [@f:2, @f, @f, @e, @f, @c:2, @c, @c:2d];
|
||||||
|
|
||||||
|
t1 = [@a, @b, @c5, @c5, @b, @c5, @a:2, @b, @a, @c5:2];
|
||||||
|
t2 = [@c5:2, @a, @b:2, @c5, @d5, @c, @b, @g, @g:2];
|
||||||
|
t3 = [@f, @a, @g, @a:2, @b, @b:2, @g, @g, @a:2];
|
||||||
|
t4 = [@d5, @db5, @db5, @c5, @b, @a, @g:2, @b, @a:2d];
|
||||||
|
|
||||||
|
b1 = [@f:2, @f, @e:2, @f, @f:2, @f, @c, @c:2];
|
||||||
|
b2 = [@f:2, @f#, @g, @f, @e, @d, @f, @g, @c, @e:2];
|
||||||
|
b3 = [@f:2, @e, @f, @f#, @g, @e, @d, @c, @c#, @d, @c];
|
||||||
|
b4 = [@b3:2, @b, @c:2, @c, @c:2, @c, @f:2d];
|
||||||
|
|
||||||
|
S = s1 + s2 + s3 + s4;
|
||||||
|
A = a1 + a2 + a3 + a4;
|
||||||
|
T = transpose(-12, t1 + t2 + t3 + t4);
|
||||||
|
B = transpose(-12, b1 + b2 + b3 + b4);
|
||||||
|
|
||||||
|
synth({ overtones -> [0.6, 0.25, 0.1] + (2 ^ 0.0) + [0.05], attack -> 50, decay -> 1 }, S, A, T, B);
|
||||||
BIN
img/notes/cd4fga8c.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
img/notes/cdef.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
img/notes/poly1.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
img/notes/poly2.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
img/notes/starwars.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
img/notes/twinkle1.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
img/notes/twinkle2.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
img/notes/twinkle3.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
img/plots/a_127.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
img/plots/a_1overtone.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
img/plots/a_1overtone_fft.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
img/plots/a_2overtones.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
img/plots/a_2overtones_fft.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
img/plots/a_3overtones.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
img/plots/a_3overtones_fft.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
img/plots/a_440.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
img/plots/attack0.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
img/plots/attack1.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
img/plots/attack10.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
img/plots/attack100.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
img/plots/attack5.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
img/plots/c.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
img/plots/decay0.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
img/plots/decay05.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
img/plots/decay1.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
img/plots/decay10.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
img/plots/decay5.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
img/plots/default.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
img/plots/example_config.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
img/plots/spectrum.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
img/schemas/wait_fsm.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -113,11 +113,11 @@ def LoopParser(input):
|
|||||||
return Parser.allOf(
|
return Parser.allOf(
|
||||||
ExpressionWithoutLoopParser,
|
ExpressionWithoutLoopParser,
|
||||||
Parser.optional(loopParameters),
|
Parser.optional(loopParameters),
|
||||||
Parser.terminal(TokenType.DASH, createNode=Operator.withValue),
|
Parser.terminal(TokenType.CARET, createNode=Operator.withValue),
|
||||||
StatementParser,
|
StatementParser,
|
||||||
Parser.optional(loopFilter),
|
Parser.optional(loopFilter),
|
||||||
createNode=Loop.loop,
|
createNode=Loop.loop,
|
||||||
name="dash-loop"
|
name="caret-loop"
|
||||||
)(input)
|
)(input)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
0
smnp/cli/__init__.py
Normal file
23
smnp/cli/parser.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
VERSION = "0.1"
|
||||||
|
DESCRIPTION = """
|
||||||
|
Simple Music Notation Processor is a command line tool enabling you to do some music stuff using custom domain-specific language.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CliParser(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.parser = argparse.ArgumentParser(description=DESCRIPTION)
|
||||||
|
self.parser.add_argument('file', nargs='*', help='a file containing SMNP code')
|
||||||
|
self.parser.add_argument('-c', '--code', action='append', default=[], type=str, help='a string with SMNP code')
|
||||||
|
self.parser.add_argument('-m', '--mic', action='store_true', help='test microphone level')
|
||||||
|
self.parser.add_argument('-C', '--config', type=argparse.FileType('-r'), help='a file containing settings (not implemented yet)')
|
||||||
|
self.parser.add_argument('-p', '--params', action='append', help='pass arguments to program (not implemented yet)')
|
||||||
|
self.parser.add_argument('-v', '--version', action='version')
|
||||||
|
self.parser.add_argument('--tokens', action='store_true', help='print tokens of parsed code')
|
||||||
|
self.parser.add_argument('--ast', action='store_true', help='print abstract syntax tree of parsed code')
|
||||||
|
self.parser.add_argument('--dry-run', action='store_true', help='don\'t execute passed code')
|
||||||
|
self.parser.version = VERSION
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
return self.parser.parse_args()
|
||||||
492
smnp/library/code/main.mus
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
function flat(list lists...) {
|
||||||
|
return _flat(lists as l ^ _flat(l, []), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _flat(list l, list output) {
|
||||||
|
l as elem ^ {
|
||||||
|
if (typeOf(elem) == list) {
|
||||||
|
output = _flat(elem, output);
|
||||||
|
} else {
|
||||||
|
output = output + [elem];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
extend note as n {
|
||||||
|
function withOctave(integer octave) {
|
||||||
|
return Note(n.pitch, octave, n.duration, n.dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDuration(integer duration) {
|
||||||
|
return Note(n.pitch, n.octave, duration, n.dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDot(bool dot) {
|
||||||
|
return Note(n.pitch, n.octave, n.duration, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toIntRepr() {
|
||||||
|
return n.octave * 12 + _pitchToNumber(n.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transpose(integer value) {
|
||||||
|
return noteFromIntRepr(n.toIntRepr() + value, n.duration, n.dot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteFromIntRepr(integer intRepr, integer duration, bool dot) {
|
||||||
|
pitch = _numberToPitch(mod(intRepr, 12));
|
||||||
|
octave = Integer(intRepr / 12);
|
||||||
|
return Note(pitch, octave, duration, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mod(integer a, integer b) {
|
||||||
|
return a - b * Integer(a/b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pitchToNumber(string pitch) {
|
||||||
|
return _keysToIntMapper(
|
||||||
|
"C",
|
||||||
|
"CIS",
|
||||||
|
"D",
|
||||||
|
"DIS",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"FIS",
|
||||||
|
"G",
|
||||||
|
"GIS",
|
||||||
|
"A",
|
||||||
|
"AIS",
|
||||||
|
"H"
|
||||||
|
).get(pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _keysToIntMapper(keys...) {
|
||||||
|
return Map(keys as (i, key) ^ [key, i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _numberToPitch(integer number) {
|
||||||
|
return ["C", "CIS", "D", "DIS", "E", "F", "FIS", "G", "GIS", "A", "AIS", "H"].get(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transpose(integer value, <note, integer, list<note, integer>> notes...) {
|
||||||
|
if (notes.size == 1) {
|
||||||
|
first = notes.get(0);
|
||||||
|
if (typeOf(first) == integer) {
|
||||||
|
return first;
|
||||||
|
} else if (typeOf(first) == note) {
|
||||||
|
return first.transpose(value);
|
||||||
|
} else if (typeOf(first) == list) {
|
||||||
|
return _transpose(value, first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
noteOrInteger = false;
|
||||||
|
lists = false;
|
||||||
|
|
||||||
|
notes as n ^ {
|
||||||
|
if (typeOf(n) == note or typeOf(n) == integer) {
|
||||||
|
noteOrInteger = true;
|
||||||
|
if (lists) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
} else if (typeOf(n) == list) {
|
||||||
|
lists = true;
|
||||||
|
if (noteOrInteger) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output = [];
|
||||||
|
notes as n ^ {
|
||||||
|
if (typeOf(n) == integer) {
|
||||||
|
output = output + [n];
|
||||||
|
} else if (typeOf(n) == note) {
|
||||||
|
output = output + [n.transpose(value)];
|
||||||
|
} else if (typeOf(n) == list) {
|
||||||
|
output = output + [_transpose(value, n)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _transpose(integer value, list<note, integer> notes) {
|
||||||
|
output = [];
|
||||||
|
notes as n ^ {
|
||||||
|
if (typeOf(n) == integer) {
|
||||||
|
output = output + [n];
|
||||||
|
} else if (typeOf(n) == note) {
|
||||||
|
output = output + [n.transpose(value)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function transposeTo(note target, <note, integer, list<note, integer>> notes...) {
|
||||||
|
if (notes.size == 1) {
|
||||||
|
first = notes.get(0);
|
||||||
|
if (typeOf(first) == integer) {
|
||||||
|
return first;
|
||||||
|
} else if (typeOf(first) == note) {
|
||||||
|
return _transposeTo(target, notes).get(0);
|
||||||
|
} else if (typeOf(first) == list) {
|
||||||
|
return _transposeTo(target, first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
noteOrInteger = false;
|
||||||
|
lists = false;
|
||||||
|
|
||||||
|
notes as n ^ {
|
||||||
|
if (typeOf(n) == note or typeOf(n) == integer) {
|
||||||
|
noteOrInteger = true;
|
||||||
|
if (lists) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
} else if (typeOf(n) == list) {
|
||||||
|
lists = true;
|
||||||
|
if (noteOrInteger) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noteOrInteger) {
|
||||||
|
return _transposeTo(target, notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lists) {
|
||||||
|
return notes as n ^ _transposeTo(target, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _transposeTo(note target, list<note, integer> notes) {
|
||||||
|
if (notes.size == 0) {
|
||||||
|
throw "Provide list with one note at least";
|
||||||
|
}
|
||||||
|
|
||||||
|
firstNote = notes.get(0);
|
||||||
|
semitones = semitones(firstNote, target);
|
||||||
|
return transpose(semitones, notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tuplet(integer n, integer m, note notes...) {
|
||||||
|
if (n != notes.size) {
|
||||||
|
throw "Expected " + n.toString() + " notes exactly, whereas " + notes.size.toString() + " was passed";
|
||||||
|
}
|
||||||
|
|
||||||
|
return notes as x ^ x.withDuration(x.duration * n / m);
|
||||||
|
}
|
||||||
|
|
||||||
|
extend list as l with function contains(expectedValue) {
|
||||||
|
return (l as value ^ value % value == expectedValue).size > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extend map as m {
|
||||||
|
function containsKey(expectedKey) {
|
||||||
|
return m.keys.contains(expectedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsValue(expectedValue) {
|
||||||
|
return m.values.contains(expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function contains(key, value) {
|
||||||
|
if (m.keys.contains(key)) {
|
||||||
|
return m.get(key) == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sample(items...) {
|
||||||
|
if (items.size == 0) {
|
||||||
|
throw "Provide one item at least";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.size == 1 and typeOf(items) == list) {
|
||||||
|
return items.get(0).get(rand(0, items.get(0).size-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.get(rand(0, items.size-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
extend string as s with function join(list<string> l) {
|
||||||
|
output = "";
|
||||||
|
l as (index, item) ^ {
|
||||||
|
output = output + item;
|
||||||
|
if (index < l.size - 1) {
|
||||||
|
output = output + s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function random(map<string><> items...) {
|
||||||
|
accumulator = 0;
|
||||||
|
items as (index, item) ^ {
|
||||||
|
if (item.size != 2) {
|
||||||
|
throw "Expected lists with two items: percent and value";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not item.containsKey("percent")) {
|
||||||
|
throw "Item " + (index+1).toString() + " does not have 'percent' key";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not item.containsKey("value")) {
|
||||||
|
throw "Item " + (index+1).toString() + " does not have 'value' key";
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator = accumulator + item.get("percent");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accumulator != 100) {
|
||||||
|
throw "Sum of first element of each item must be equal to 100";
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator = 0;
|
||||||
|
random = rand(0, 99);
|
||||||
|
items as item ^ {
|
||||||
|
accumulator = accumulator + item.get("percent");
|
||||||
|
if (random < accumulator) {
|
||||||
|
return item.get("value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function semitones(<note, integer, list<note, integer>> notes...) {
|
||||||
|
noteOrInteger = false;
|
||||||
|
lists = false;
|
||||||
|
|
||||||
|
notes as n ^ {
|
||||||
|
if (typeOf(n) == note or typeOf(n) == integer) {
|
||||||
|
noteOrInteger = true;
|
||||||
|
if (lists) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
} else if (typeOf(n) == list) {
|
||||||
|
lists = true;
|
||||||
|
if (noteOrInteger) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noteOrInteger) {
|
||||||
|
return _semitones(notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lists) {
|
||||||
|
output = [];
|
||||||
|
notes as n ^ {
|
||||||
|
output = output + [_semitones(n)];
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (output.size == 1) {
|
||||||
|
# return output.get(0)
|
||||||
|
#}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _semitones(list<note, integer> notes) {
|
||||||
|
onlyNotes = notes as n ^ n % typeOf(n) == note;
|
||||||
|
|
||||||
|
if (onlyNotes.size == 2 and typeOf(onlyNotes.get(0)) == note and typeOf(onlyNotes.get(1)) == note) {
|
||||||
|
first = onlyNotes.get(0);
|
||||||
|
second = onlyNotes.get(1);
|
||||||
|
return second.toIntRepr() - first.toIntRepr();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyNotes.size < 2) {
|
||||||
|
throw "Provide 2 notes at least to evaluate semitones between them";
|
||||||
|
}
|
||||||
|
|
||||||
|
output = [];
|
||||||
|
range(1, onlyNotes.size-1) as i ^ {
|
||||||
|
output = output + [onlyNotes.get(i).toIntRepr() - onlyNotes.get(i-1).toIntRepr()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringInterval(integer semitones) {
|
||||||
|
return [
|
||||||
|
"1",
|
||||||
|
"2m",
|
||||||
|
"2M",
|
||||||
|
"3m",
|
||||||
|
"3M",
|
||||||
|
"4",
|
||||||
|
"5d/4A",
|
||||||
|
"5",
|
||||||
|
"6m",
|
||||||
|
"6M",
|
||||||
|
"7m",
|
||||||
|
"7M"
|
||||||
|
].get(semitones);
|
||||||
|
}
|
||||||
|
|
||||||
|
function interval(<note, integer, list<note, integer>> notes...) {
|
||||||
|
noteOrInteger = false;
|
||||||
|
lists = false;
|
||||||
|
|
||||||
|
notes as n ^ {
|
||||||
|
if (typeOf(n) == note or typeOf(n) == integer) {
|
||||||
|
noteOrInteger = true;
|
||||||
|
if (lists) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
} else if (typeOf(n) == list) {
|
||||||
|
lists = true;
|
||||||
|
if (noteOrInteger) {
|
||||||
|
throw "Mixing notes and integers with lists of them is not supported";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noteOrInteger) {
|
||||||
|
semitones = _semitones(notes);
|
||||||
|
if (typeOf(semitones) == list) {
|
||||||
|
return semitones as n ^ stringInterval(n);
|
||||||
|
} else {
|
||||||
|
return stringInterval(semitones);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lists) {
|
||||||
|
output = [];
|
||||||
|
notes as n ^ {
|
||||||
|
semitones = _semitones(n);
|
||||||
|
if (typeOf(semitones) == list) {
|
||||||
|
output = output + [_semitones(n) as semitone ^ stringInterval(semitone)];
|
||||||
|
} else {
|
||||||
|
output = output + [stringInterval(semitones)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (output.size == 1) {
|
||||||
|
# return output.get(0);
|
||||||
|
#}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteRange(note a, note b, string filter = "all") {
|
||||||
|
filters = {
|
||||||
|
"all" -> [ "C", "CIS", "D", "DIS", "E", "F", "FIS", "G", "GIS", "A", "AIS", "H" ],
|
||||||
|
"diatonic" -> [ "C", "D", "E", "F", "G", "A", "H" ],
|
||||||
|
"chromatic" -> [ "CIS", "DIS", "FIS", "GIS", "AIS" ]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (not filters.containsKey(filter)) {
|
||||||
|
throw "Unknown filter: '" + filter + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
notes = range(a.toIntRepr(), b.toIntRepr()) as intRepr ^ noteFromIntRepr(intRepr, a.duration, a.dot);
|
||||||
|
return notes as n ^ n % filters.get(filter).contains(n.pitch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function range(<integer, float> a, <integer, float> b, <integer, float> step = 1) {
|
||||||
|
if (not (step > 0)) {
|
||||||
|
throw "Step should be greater than 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a > b) {
|
||||||
|
throw "Upper range value should be greater than lower or equal to";
|
||||||
|
}
|
||||||
|
|
||||||
|
output = [];
|
||||||
|
|
||||||
|
i = a;
|
||||||
|
i <= b ^ {
|
||||||
|
output = output + [i];
|
||||||
|
i = i + step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function alert(<integer, bool> cycles = true, string melody = "beep", list<float, integer> overtones = [0.5, 0.0, 0.0, 0.5]) {
|
||||||
|
if (not [integer, bool].contains(typeOf(cycles))) {
|
||||||
|
throw "Provide 'true' or number of cycles as first argument";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeOf(cycles) == integer) {
|
||||||
|
if (cycles < 1) {
|
||||||
|
throw "Number of cycles cannot be less than 1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeOf(cycles) == bool) {
|
||||||
|
if (not cycles) {
|
||||||
|
throw "Provide 'true' or number of cycles as first argument";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notes = {
|
||||||
|
"beep" -> [@c5:16, 32, @c5:16, 3],
|
||||||
|
"s1" -> noteRange(@c5:32, @g5:32),
|
||||||
|
"s2" -> _upDown(noteRange(@c5:32, @g5:32)),
|
||||||
|
"s3" -> [@a5:16, @d5:16],
|
||||||
|
"semitone" -> [@c5:16, @db5:16]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (not notes.containsKey(melody)) {
|
||||||
|
throw "Unknown melody '" + melody + "'. Available: 'beep', 's1', 's2', 's3' and 'semitone'";
|
||||||
|
}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
bpm -> 120,
|
||||||
|
decay -> 0.5,
|
||||||
|
attack -> 200,
|
||||||
|
overtones -> overtones
|
||||||
|
};
|
||||||
|
|
||||||
|
wave = wave(config, notes.get(melody));
|
||||||
|
|
||||||
|
cycles ^ synth(wave);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _upDown(list l) {
|
||||||
|
return l + -l;
|
||||||
|
}
|
||||||
|
|
||||||
|
function metronome(integer bpm = 120, integer beats = 4, countMeasures = false) {
|
||||||
|
accent = wave({
|
||||||
|
overtones -> flat([0.5, 0.1, 10^0, 0.1, 10^0, 0.1, 20^0, 0.1, 25^0, 0.05, 25^0, 0.05]),
|
||||||
|
attack -> 0,
|
||||||
|
decay -> 5,
|
||||||
|
bpm -> bpm
|
||||||
|
}, @c);
|
||||||
|
|
||||||
|
beat = wave({
|
||||||
|
overtones -> flat([0.5, 10^0, 0.3, 10^0, 0.2]),
|
||||||
|
attack -> 0,
|
||||||
|
decay -> 100,
|
||||||
|
bpm -> bpm
|
||||||
|
}, @c);
|
||||||
|
|
||||||
|
measure = 1;
|
||||||
|
true ^ {
|
||||||
|
if (countMeasures) {
|
||||||
|
println(measure);
|
||||||
|
measure = measure + 1;
|
||||||
|
}
|
||||||
|
synth(accent);
|
||||||
|
beats - 1 ^ synth(beat);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
smnp/main.py
@@ -1,17 +1,38 @@
|
|||||||
import sys
|
from smnp.cli.parser import CliParser
|
||||||
|
|
||||||
from smnp.error.base import SmnpException
|
from smnp.error.base import SmnpException
|
||||||
from smnp.library.loader import loadStandardLibrary
|
from smnp.library.loader import loadStandardLibrary
|
||||||
|
from smnp.module.mic.lib.detector.noise import NoiseDetector
|
||||||
from smnp.program.interpreter import Interpreter
|
from smnp.program.interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
def interpretFile(args, file):
|
||||||
|
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
|
||||||
|
Interpreter.interpretFile(file, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv)
|
||||||
|
|
||||||
|
|
||||||
|
def interpretString(args, string):
|
||||||
|
stdLibraryEnv = loadStandardLibrary() if not args.dry_run else None
|
||||||
|
Interpreter.interpretString(string, printTokens=args.tokens, printAst=args.ast, execute=not args.dry_run, baseEnvironment=stdLibraryEnv, source='<cli>')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
stdLibraryEnv = loadStandardLibrary()
|
parser = CliParser()
|
||||||
Interpreter.interpretFile(sys.argv[1], printTokens=False, printAst=False, execute=True, baseEnvironment=stdLibraryEnv)
|
args = parser.parse()
|
||||||
|
|
||||||
|
if args.mic:
|
||||||
|
nd = NoiseDetector()
|
||||||
|
nd.test()
|
||||||
|
|
||||||
|
for code in args.code:
|
||||||
|
interpretString(args, code)
|
||||||
|
|
||||||
|
for file in args.file:
|
||||||
|
interpretFile(args, file)
|
||||||
|
|
||||||
except SmnpException as e:
|
except SmnpException as e:
|
||||||
print(e.message())
|
print(e.message())
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Program interrupted")
|
print("Program interrupted")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from smnp.module.synth.function import synth, pause, plot, compile
|
from smnp.module.synth.function import synth, pause, plot, compile, fft
|
||||||
|
|
||||||
functions = [ synth.function, pause.function, plot.function, compile.function ]
|
functions = [ synth.function, pause.function, plot.function, compile.function, fft.function ]
|
||||||
methods = []
|
methods = []
|
||||||
@@ -12,6 +12,7 @@ DEFAULT_BPM = 120
|
|||||||
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
|
DEFAULT_OVERTONES = [0.4, 0.3, 0.1, 0.1, 0.1]
|
||||||
DEFAULT_DECAY = 4
|
DEFAULT_DECAY = 4
|
||||||
DEFAULT_ATTACK = 100
|
DEFAULT_ATTACK = 100
|
||||||
|
DEFAULT_TUNING = 440
|
||||||
|
|
||||||
|
|
||||||
def getBpm(config):
|
def getBpm(config):
|
||||||
@@ -72,16 +73,29 @@ def getAttack(config):
|
|||||||
return DEFAULT_ATTACK
|
return DEFAULT_ATTACK
|
||||||
|
|
||||||
|
|
||||||
|
def getTuning(config):
|
||||||
|
key = Type.string("tuning")
|
||||||
|
if key in config.value:
|
||||||
|
tuning = config.value[key]
|
||||||
|
if not tuning.type in [Type.INTEGER, Type.FLOAT] or tuning.value < 0:
|
||||||
|
raise RuntimeException("The 'tuning' property must be non-negative integer or float", None)
|
||||||
|
|
||||||
|
return tuning.value
|
||||||
|
|
||||||
|
return DEFAULT_TUNING
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
def __init__(self, bpm, overtones, decay, attack):
|
def __init__(self, bpm, overtones, decay, attack, tuning):
|
||||||
self.bpm = bpm
|
self.bpm = bpm
|
||||||
self.overtones = overtones
|
self.overtones = overtones
|
||||||
self.decay = decay
|
self.decay = decay
|
||||||
self.attack = attack
|
self.attack = attack
|
||||||
|
self.tuning = tuning
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default():
|
def default():
|
||||||
return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK)
|
return Config(DEFAULT_BPM, DEFAULT_OVERTONES, DEFAULT_DECAY, DEFAULT_ATTACK, DEFAULT_TUNING)
|
||||||
|
|
||||||
|
|
||||||
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
|
_signature1 = varargSignature(listOf(Type.NOTE, Type.INTEGER))
|
||||||
@@ -114,8 +128,9 @@ def __function3(config, notes):
|
|||||||
overtones = getOvertones(config)
|
overtones = getOvertones(config)
|
||||||
decay = getDecay(config)
|
decay = getDecay(config)
|
||||||
attack = getAttack(config)
|
attack = getAttack(config)
|
||||||
|
tuning = getTuning(config)
|
||||||
|
|
||||||
return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack))
|
return compilePolyphony(rawNotes, Config(bpm, overtones, decay, attack, tuning))
|
||||||
|
|
||||||
|
|
||||||
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
|
_signature4 = varargSignature(ofTypes(Type.NOTE, Type.INTEGER), ofType(Type.MAP))
|
||||||
@@ -128,8 +143,9 @@ def __function4(config, notes):
|
|||||||
overtones = getOvertones(config)
|
overtones = getOvertones(config)
|
||||||
decay = getDecay(config)
|
decay = getDecay(config)
|
||||||
attack = getAttack(config)
|
attack = getAttack(config)
|
||||||
|
tuning = getTuning(config)
|
||||||
|
|
||||||
return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack))
|
return compilePolyphony([ notes ], Config(bpm, overtones, decay, attack, tuning))
|
||||||
|
|
||||||
|
|
||||||
function = CombinedFunction(
|
function = CombinedFunction(
|
||||||
|
|||||||
17
smnp/module/synth/function/fft.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from smnp.function.model import Function
|
||||||
|
from smnp.function.signature import signature
|
||||||
|
from smnp.type.model import Type
|
||||||
|
from smnp.type.signature.matcher.list import listOf
|
||||||
|
|
||||||
|
_signature = signature(listOf(Type.FLOAT))
|
||||||
|
def _function(env, signal):
|
||||||
|
raw = [ x.value for x in signal.value ]
|
||||||
|
N = len(raw)
|
||||||
|
fft = np.fft.fft(raw)/N
|
||||||
|
fft = fft[range(int(N/2))]
|
||||||
|
return Type.list([ Type.float(float(abs(x))) for x in fft ])
|
||||||
|
|
||||||
|
|
||||||
|
function = Function(_signature, _function, 'fft')
|
||||||
@@ -10,4 +10,4 @@ def _function(env, wave):
|
|||||||
plot(rawWave)
|
plot(rawWave)
|
||||||
|
|
||||||
|
|
||||||
function = Function(_signature, _function, 'plotWave')
|
function = Function(_signature, _function, 'plot')
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import matplotlib.pyplot as plt
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
|
|
||||||
|
from smnp.note.pitch import Tuning
|
||||||
from smnp.type.model import Type
|
from smnp.type.model import Type
|
||||||
|
|
||||||
FS = 44100
|
FS = 44100
|
||||||
@@ -25,7 +26,8 @@ def play(wave):
|
|||||||
|
|
||||||
|
|
||||||
def compilePolyphony(notes, config):
|
def compilePolyphony(notes, config):
|
||||||
compiledLines = [1 / len(notes) * compileNotes(line, config) for line in notes]
|
tuning = Tuning(config.tuning)
|
||||||
|
compiledLines = [1 / len(notes) * compileNotes(line, config, tuning) for line in notes]
|
||||||
return sum(adjustSize(compiledLines))
|
return sum(adjustSize(compiledLines))
|
||||||
|
|
||||||
|
|
||||||
@@ -35,17 +37,17 @@ def adjustSize(compiledLines):
|
|||||||
return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]
|
return [np.concatenate([line, np.zeros(maxSize - len(line))]) for line in compiledLines]
|
||||||
|
|
||||||
|
|
||||||
def compileNotes(notes, config):
|
def compileNotes(notes, config, tuning):
|
||||||
dispatcher = {
|
dispatcher = {
|
||||||
Type.NOTE: lambda note, overtones: sineForNote(note.value, config),
|
Type.NOTE: lambda note, overtones: sineForNote(note.value, config, tuning),
|
||||||
Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
|
Type.INTEGER: lambda note, overtones: silenceForPause(note.value, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return np.concatenate([dispatcher[note.type](note, config) for note in notes])
|
return np.concatenate([dispatcher[note.type](note, config) for note in notes])
|
||||||
|
|
||||||
|
|
||||||
def sineForNote(note, config):
|
def sineForNote(note, config, tuning):
|
||||||
frequency = note.toFrequency()
|
frequency = note.toFrequency(tuning)
|
||||||
duration = 60 * 4 / note.duration / config.bpm
|
duration = 60 * 4 / note.duration / config.bpm
|
||||||
duration *= 1.5 if note.dot else 1
|
duration *= 1.5 if note.dot else 1
|
||||||
return sound(frequency, duration, config)
|
return sound(frequency, duration, config)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def getValueAccordingToType(value, type):
|
|||||||
|
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
raise RuntimeException(f"Type {type.value.name.lower()} is not suuported", None)
|
raise RuntimeException(f"Type {type.value.name.lower()} is not supported", None)
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(f"Invalid value '{value}' for type {type.value.name.lower()}", None)
|
raise RuntimeException(f"Invalid value '{value}' for type {type.value.name.lower()}", None)
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class Note:
|
|||||||
self.duration = duration
|
self.duration = duration
|
||||||
self.dot = dot
|
self.dot = dot
|
||||||
|
|
||||||
def toFrequency(self):
|
def toFrequency(self, tuning):
|
||||||
return self.note.toFrequency() * 2 ** self.octave
|
return tuning[self.note] * 2 ** self.octave
|
||||||
|
|
||||||
def transpose(self, interval):
|
def transpose(self, interval):
|
||||||
origIntRepr = self._intRepr()
|
origIntRepr = self._intRepr()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from smnp.error.note import NoteException
|
from smnp.error.note import NoteException
|
||||||
|
|
||||||
|
_semitone = 2**(1/12)
|
||||||
|
|
||||||
class NotePitch(Enum):
|
class NotePitch(Enum):
|
||||||
C = 0
|
C = 0
|
||||||
@@ -17,21 +18,21 @@ class NotePitch(Enum):
|
|||||||
AIS = 10
|
AIS = 10
|
||||||
H = 11
|
H = 11
|
||||||
|
|
||||||
def toFrequency(self):
|
# def toFrequency(self):
|
||||||
return {
|
# return {
|
||||||
NotePitch.C: 16.35,
|
# NotePitch.C: 16.35,
|
||||||
NotePitch.CIS: 17.32,
|
# NotePitch.CIS: 17.32,
|
||||||
NotePitch.D: 18.35,
|
# NotePitch.D: 18.35,
|
||||||
NotePitch.DIS: 19.45,
|
# NotePitch.DIS: 19.45,
|
||||||
NotePitch.E: 20.60,
|
# NotePitch.E: 20.60,
|
||||||
NotePitch.F: 21.83,
|
# NotePitch.F: 21.83,
|
||||||
NotePitch.FIS: 23.12,
|
# NotePitch.FIS: 23.12,
|
||||||
NotePitch.G: 24.50,
|
# NotePitch.G: 24.50,
|
||||||
NotePitch.GIS: 25.96,
|
# NotePitch.GIS: 25.96,
|
||||||
NotePitch.A: 27.50,
|
# NotePitch.A: 27.50,
|
||||||
NotePitch.AIS: 29.17,
|
# NotePitch.AIS: 29.17,
|
||||||
NotePitch.H: 30.87
|
# NotePitch.H: 30.87
|
||||||
}[self]
|
# }[self]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -50,6 +51,14 @@ class NotePitch(Enum):
|
|||||||
raise NoteException(f"Note '{string}' does not exist")
|
raise NoteException(f"Note '{string}' does not exist")
|
||||||
|
|
||||||
|
|
||||||
|
class Tuning(object):
|
||||||
|
def __init__(self, a4=440):
|
||||||
|
self.tuning = { value: a4/_semitone**(57-i) for i, value in enumerate(NotePitch.__members__) }
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return self.tuning[str(item)]
|
||||||
|
|
||||||
|
|
||||||
stringToPitch = {
|
stringToPitch = {
|
||||||
'cb': NotePitch.H,
|
'cb': NotePitch.H,
|
||||||
'c': NotePitch.C,
|
'c': NotePitch.C,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class ReturnEvaluator(Evaluator):
|
|||||||
# Disclaimer
|
# Disclaimer
|
||||||
# Exception system usage to control program execution flow is really bad idea.
|
# Exception system usage to control program execution flow is really bad idea.
|
||||||
# However because of lack of 'goto' instruction equivalent in Python
|
# However because of lack of 'goto' instruction equivalent in Python
|
||||||
# there is to need to use some mechanism to break function execution on 'return' statement
|
# there is a need to use some mechanism to break function execution on 'return' statement
|
||||||
# and immediately go to Environment's method 'invokeFunction()' or 'invokeMethod()',
|
# and immediately go to Environment's method 'invokeFunction()' or 'invokeMethod()',
|
||||||
# which can handle value that came with exception and return it to code being executed.
|
# which can handle value that came with exception and return it to code being executed.
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ class PowerEvaluator(Evaluator):
|
|||||||
if not right.type in supportedTypes:
|
if not right.type in supportedTypes:
|
||||||
raise RuntimeException(f"Operator '{node.operator.value}' is supported only by {[t.name.lower() for t in supportedTypes]} type", node.right.pos)
|
raise RuntimeException(f"Operator '{node.operator.value}' is supported only by {[t.name.lower() for t in supportedTypes]} type", node.right.pos)
|
||||||
|
|
||||||
return Type.integer(int(left.value ** right.value))
|
return Type.float(float(left.value ** right.value))
|
||||||
@@ -36,7 +36,7 @@ tokenizers = (
|
|||||||
defaultTokenizer(TokenType.SLASH),
|
defaultTokenizer(TokenType.SLASH),
|
||||||
defaultTokenizer(TokenType.MINUS),
|
defaultTokenizer(TokenType.MINUS),
|
||||||
defaultTokenizer(TokenType.PLUS),
|
defaultTokenizer(TokenType.PLUS),
|
||||||
defaultTokenizer(TokenType.DASH),
|
defaultTokenizer(TokenType.CARET),
|
||||||
defaultTokenizer(TokenType.DOTS),
|
defaultTokenizer(TokenType.DOTS),
|
||||||
defaultTokenizer(TokenType.AMP),
|
defaultTokenizer(TokenType.AMP),
|
||||||
defaultTokenizer(TokenType.DOT),
|
defaultTokenizer(TokenType.DOT),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TokenType(Enum):
|
|||||||
SLASH = '/'
|
SLASH = '/'
|
||||||
MINUS = '-'
|
MINUS = '-'
|
||||||
PLUS = '+'
|
PLUS = '+'
|
||||||
DASH = '^'
|
CARET = '^'
|
||||||
DOTS = '...'
|
DOTS = '...'
|
||||||
AMP = '&'
|
AMP = '&'
|
||||||
DOT = '.'
|
DOT = '.'
|
||||||
|
|||||||