6. Generate a musique from equation
[1]:
import cutcutcodec
6.1. Simplified functions that generate musical note equations
A note is considered as a fundamental and harmonics.
We consider an extremely simplified ADSR envellope
[2]:
# very simple note model
def adsr(start: float, duration: float) -> str:
"""Return the Attack, Decrease, Sustain and Release envellope equation."""
atack = f"1/2 + atan(5*(t-{start}))/pi"
decrease_sustain = f"1 - 0.05*(t-{start})"
release = f"1/2 - atan(2*(t-{start+duration}-0.2))/pi"
return f"({atack}) * ({decrease_sustain}) * ({release})"
def spectral(freq: float) -> str:
"""Return the equation of an infinite note with harmonics."""
return " + ".join(f"0.25*{2**-i}*sin(2*pi*{freq*i}*t)" for i in range(1, 9))
def note(freq: float, start: float, duration: float) -> str:
"""A finite duration note."""
return f"({adsr(start, duration)}) * ({spectral(freq)})"
[3]:
# associate a frequency to each note
# for a temperated octave scale
R = 2.0**(1.0/12.0)
# diapason
A4 = 440.0
# first octave
C4 = A4*R**-9
CIS4 = DES4 = A4*R**-8
D4 = A4*R**-7
DIS4 = EES4 = A4*R**-6
E4 = A4*R**-5
F4 = A4*R**-4
FIS4 = GES4 = A4*R**-3
G4 = A4*R**-2
GIS4 = AES4 = A4*R**-1
AIS4 = BES4 = A4*R**1
B4 = A4*R**2
# copy octaves
C0, C1, C2, C3, C5, C6 = C4/16, C4/8, C4/4, C4/2, C4*2, C4*4
CIS0, CIS1, CIS2, CIS3, CIS5, CIS6 = CIS4/16, CIS4/8, CIS4/4, CIS4/2, CIS4*2, CIS4*4
DES0, DES1, DES2, DES3, DES5, DES6 = CIS0, CIS1, CIS2, CIS3, CIS5, CIS6
D0, D1, D2, D3, D5, D6 = D4/16, D4/8, D4/4, D4/2, D4*2, D4*4
DIS0, DIS1, DIS2, DIS3, DIS5, DIS6 = DIS4/16, DIS4/8, DIS4/4, DIS4/2, DIS4*2, DIS4*4
EES0, EES1, EES2, EES3, EES5, EES6 = DIS0, DIS1, DIS2, DIS3, DIS5, DIS6
E0, E1, E2, E3, E5, E6 = E4/16, E4/8, E4/4, E4/2, E4*2, E4*4
F0, F1, F2, F3, F5, F6 = F4/16, F4/8, F4/4, F4/2, F4*2, F4*4
FIS0, FIS1, FIS2, FIS3, FIS5, FIS6 = FIS4/16, FIS4/8, FIS4/4, FIS4/2, FIS4*2, FIS4*4
GES0, GES1, GES2, GES3, GES5, GES6 = FIS0, FIS1, FIS2, FIS3, FIS5, FIS6
G0, G1, G2, G3, G5, G6 = G4/16, G4/8, G4/4, G4/2, G4*2, G4*4
GIS0, GIS1, GIS2, GIS3, GIS5, GIS6 = GIS4/16, GIS4/8, GIS4/4, GIS4/2, GIS4*2, GIS4*4
AES0, AES1, AES2, AES3, AES5, AES6 = GIS0, GIS1, GIS2, GIS3, GIS5, GIS6
A0, A1, A2, A3, A5, A6 = A4/16, A4/8, A4/4, A4/2, A4*2, A4*4
AIS0, AIS1, AIS2, AIS3, AIS5, AIS6 = AIS4/16, AIS4/8, AIS4/4, AIS4/2, AIS4*2, AIS4*4
BES0, BES1, BES2, BES3, BES5, BES6 = AIS0, AIS1, AIS2, AIS3, AIS5, AIS6
B0, B1, B2, B3, B5, B6 = B4/16, B4/8, B4/4, B4/2, B4*2, B4*4
6.2. Assembling notes to create a music
[4]:
music_equation = " + ".join([ # sd mvt of the sd concerto of rachmaninof
note(C3, 0, 2), note(EES3, 0, 2), note(G3, 0, 2), note(C4, 0, 2),
note(BES2, 2, 1.5), note(F3, 2, 2), note(BES3, 2, 4), note(D4, 2, 2),
note(AES2, 3.5, 0.5),
note(G2, 4, 2), note(EES3, 4, 2), note(EES4, 4, 2),
note(F2, 6, 2), note(C3, 6, 2), note(AES3, 6, 2), note(C4, 6, 4), note(F4, 6, 2),
note(EES2, 8, 2), note(EES3, 8, 2), note(G3, 8, 2), note(G4, 8, 2),
note(GIS1, 10, 1), note(GIS2, 10, 4), note(GIS3, 10, 4), note(C4, 10, 2), note(GIS4, 10, 4),
note(FIS2, 11, 1),
note(E2, 12, 1), note(A3, 12, 1), note(CIS4, 12, 1),
note(D2, 13, 1), note(C4, 13, 1), note(DIS4, 13, 1),
note(CIS2, 14, 1), note(CIS3, 14, 1), note(CIS4, 14, 1), note(E4, 14, 1),
note(C2, 15, 1), note(C3, 15, 1), note(DIS4, 15, 1), note(FIS4, 15, 1),
note(E0, 16, 4), note(E1, 16, 4), note(GIS3, 16, 4), note(B3, 16, 4), note(E4, 16, 4), note(GIS4, 16, 4),
])
6.3. Write the music file
[5]:
SETTINGS = [{"encodec": "opus", "rate": 48000, "bitrate": 128000}]
stream = cutcutcodec.generation.audio.GeneratorAudioEquation(music_equation).out_streams[0]
stream = stream.apply_audio_cut(22)
cutcutcodec.write([stream], "/tmp/my_music.opus", streams_settings=SETTINGS)
Encoding my_music.opus: 100%|█████████████████████████████████| 22.00s/22.00s [00:11<00:00]