- Neuer Mitarbeiter Komponist (.claude/agents/komponist.md) - Liederbuch: Plan + Lieder 1, 2, 3, 4, 5, 12 (drehbuecher/lieder/) - Suno: feste Stimme aus dem Favoriten, Stilvorlage, ausgewählte Fassungen (suno-auswahl.md) - Suno-Lizenzantwort für Noa in der Mitte (wissen/) - Werkzeuge für Melodie-Skizzen und Leseseite - Laufende Remotion-, Stimmen- und Figurenarbeiten des Tages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MyLus898ZL9jjJJDvvxa8
166 lines
7.9 KiB
Python
166 lines
7.9 KiB
Python
# Melodie-Vorschau der Lied-Entwürfe (Refrain – Strophe – Refrain), ohne Gesang.
|
||
import numpy as np, wave, os, sys, subprocess
|
||
|
||
SR = 44100
|
||
OUT = r"C:\Users\lk-lo\Pino-Kanal\Lieder-Vorschau"
|
||
os.makedirs(OUT, exist_ok=True)
|
||
rng = np.random.default_rng(1)
|
||
|
||
NAMES = {'C':0,'C#':1,'Db':1,'D':2,'D#':3,'Eb':3,'E':4,'F':5,'F#':6,'Gb':6,'G':7,'G#':8,'Ab':8,'A':9,'A#':10,'Bb':10,'B':11}
|
||
def midi(n): # 'F#4'
|
||
return 12*(int(n[-1])+1) + NAMES[n[:-1]]
|
||
def hz(m): return 440*2**((m-69)/12)
|
||
|
||
def lead(f, dur):
|
||
n = int(SR*(dur+0.25)); t = np.arange(n)/SR
|
||
vib = 1 + 0.004*np.sin(2*np.pi*5.2*t)*np.clip((t-0.18)/0.2,0,1)
|
||
ph = 2*np.pi*f*np.cumsum(vib)/SR
|
||
s = np.sin(ph) + 0.28*np.sin(2*ph) + 0.08*np.sin(3*ph)
|
||
env = np.clip(t/0.03,0,1) * np.where(t < dur, 1.0, np.exp(-(t-dur)/0.06)) * (0.85+0.15*np.exp(-t/0.3))
|
||
return 0.22*s*env
|
||
|
||
def glocken(f, dur):
|
||
n = int(SR*1.6); t = np.arange(n)/SR
|
||
s = np.sin(2*np.pi*2*f*t)*np.exp(-t/0.55) + 0.25*np.sin(2*np.pi*2*f*2.76*t)*np.exp(-t/0.12)
|
||
return 0.07*s*np.clip(t/0.002,0,1)
|
||
|
||
def pluck(f, dur, amp=0.09):
|
||
N = int(SR/f); n = int(SR*min(dur+0.4, 3.0))
|
||
buf = rng.uniform(-1,1,N); blocks = []
|
||
for _ in range(n//N + 1):
|
||
blocks.append(buf.copy()); buf = 0.498*(buf + np.roll(buf,-1))
|
||
out = np.concatenate(blocks)[:n]
|
||
return amp*out*np.clip(np.arange(n)/(SR*0.002),0,1)
|
||
|
||
def bass(f, dur):
|
||
n = int(SR*(dur+0.1)); t = np.arange(n)/SR
|
||
s = np.sin(2*np.pi*f*t) + 0.2*np.sin(4*np.pi*f*t)
|
||
return 0.16*s*np.exp(-t/0.6)*np.clip(t/0.01,0,1)
|
||
|
||
def clap():
|
||
n = int(SR*0.12); t = np.arange(n)/SR
|
||
s = rng.uniform(-1,1,n); s = np.convolve(s, np.ones(6)/6, 'same')
|
||
return 0.12*s*np.exp(-t/0.03)
|
||
|
||
def chord_notes(c):
|
||
root = c[0] + (c[1] if len(c)>1 and c[1] in '#b' else '')
|
||
rest = c[len(root):]
|
||
r = NAMES[root]
|
||
iv = [0,3,7] if rest.startswith('m') else [0,4,7]
|
||
if '7' in rest: iv = iv + [10]
|
||
base = 48 + r if r <= 7 else 36 + r # Gitarrenlage um C3–G3
|
||
notes = [base+i for i in iv]
|
||
return 40 + (r-4) % 12, notes # Bass-Grundton
|
||
|
||
def add(mix, sig, at):
|
||
i = int(at*SR); j = min(len(mix), i+len(sig))
|
||
if j > i: mix[i:j] += sig[:j-i]
|
||
|
||
def render(name, bpm, sections, strum_every, claps=None, end_chord=None, intro=None):
|
||
beat = 60/bpm
|
||
total = sum(sum(d for _,d in mel) for mel,_ in sections)
|
||
total += sum(d for _,d in intro[1]) if intro else 0
|
||
mix = np.zeros(int(SR*(total*beat + 4)))
|
||
t0 = 0.0
|
||
parts = ([intro] if intro else []) + sections
|
||
for mel, chords in parts:
|
||
# Melodie
|
||
t = t0
|
||
for n, d in mel:
|
||
if n:
|
||
f = hz(midi(n)); add(mix, lead(f, d*beat*0.92), t*beat); add(mix, glocken(f, d*beat), t*beat)
|
||
t += d
|
||
# Akkorde
|
||
t = t0
|
||
for c, d in chords:
|
||
b, notes = chord_notes(c)
|
||
add(mix, bass(hz(b), d*beat), t*beat)
|
||
k = 0.0
|
||
while k < d - 1e-9:
|
||
for idx, m in enumerate(notes):
|
||
add(mix, pluck(hz(m), strum_every*beat, 0.07), (t+k)*beat + idx*0.014)
|
||
k += strum_every
|
||
if claps:
|
||
for cb in claps:
|
||
pass
|
||
t += d
|
||
t0 = t
|
||
if claps: # Klatscher auf festen Positionen (in Beats ab Start)
|
||
for cb in claps: add(mix, clap(), cb*beat)
|
||
if end_chord:
|
||
b, notes = chord_notes(end_chord)
|
||
add(mix, bass(hz(b), 2.5), t0*beat)
|
||
for idx, m in enumerate(notes + [notes[0]+12]):
|
||
add(mix, pluck(hz(m), 3.0, 0.08), t0*beat + idx*0.03)
|
||
add(mix, glocken(hz(m+12), 2), t0*beat + idx*0.03)
|
||
# Hall (einfach) + Normalisieren
|
||
d = int(0.11*SR); rev = np.zeros_like(mix); rev[d:] = mix[:-d]*0.22
|
||
d2 = int(0.23*SR); rev[d2:] += mix[:-d2]*0.12
|
||
mix = mix + rev
|
||
mix = mix[:int(SR*(t0*beat+3.2))]
|
||
mix *= 0.89/np.max(np.abs(mix))
|
||
fade = int(SR*1.0); mix[-fade:] *= np.linspace(1,0,fade)
|
||
wavp = os.path.join(OUT, name + '.wav')
|
||
with wave.open(wavp, 'w') as w:
|
||
w.setnchannels(1); w.setsampwidth(2); w.setframerate(SR)
|
||
w.writeframes((mix*32767).astype(np.int16).tobytes())
|
||
mp3 = os.path.join(OUT, name + '.mp3')
|
||
subprocess.run(['ffmpeg','-y','-loglevel','error','-i',wavp,'-af','loudnorm=I=-14:TP=-1.5:LRA=11','-ar','44100','-b:a','160k',mp3], check=True)
|
||
os.remove(wavp)
|
||
print(name, round(t0*beat+3.2,1), 's')
|
||
|
||
def L(s): # "G4:1 E4:1 -:2" -> [(note,beats)]
|
||
out = []
|
||
for tok in s.split():
|
||
n, d = tok.split(':'); out.append((None if n == '-' else n, float(d)))
|
||
return out
|
||
def C(s): return [(c.split(':')[0], float(c.split(':')[1])) for c in s.split()]
|
||
|
||
# Lied 1 – Zählen, C-Dur, 100 BPM
|
||
r1 = (L("G4:1 E4:1 G4:1 E4:1 -:4 A4:1 A4:1 G4:1 G4:1 E4:1 D4:1 -:2 C4:1 D4:1 E4:1 F4:1 G4:1 G4:1 G4:2 A4:.5 G4:.5 F4:.5 E4:.5 D4:1 E4:1 C4:4"),
|
||
C("C:4 F:2 G:2 C:4 G:4 C:4 F:4 G:4 C:4"))
|
||
s1 = (L("C4:2 D4:2 E4:1 E4:1 D4:1 C4:1 E4:2 F4:2 G4:1 G4:1 F4:1 E4:1 G4:2 A4:.5 G4:.5 F4:.5 E4:.5 E4:.5 D4:.5 D4:1 E4:2 C4:4 -:4"),
|
||
C("C:4 G:4 C:4 G:4 F:4 G:4 C:4 C:4"))
|
||
render('lied01-zaehlen', 100, [r1, s1, r1], 1, intro=([], C("C:4")), end_chord='C')
|
||
|
||
# Lied 2 – Farben, G-Dur, 96 BPM
|
||
r2 = (L("G4:1 G4:1 G4:1 A4:1 B4:4 B4:1 A4:1 G4:1 A4:1 B4:1 A4:1 A4:2 D4:1 D4:1 E4:1 E4:1 G4:1 G4:1 A4:2 B4:1 B4:1 A4:1 G4:1 A4:1 G4:1 G4:2"),
|
||
C("G:4 G:4 C:4 D:4 G:4 C:4 Em:4 G:4"))
|
||
s2 = (L("D4:.5 E4:.5 G4:1 D4:.5 E4:.5 G4:1 -:4 G4:.5 A4:.5 B4:.5 A4:.5 G4:.5 E4:.5 -:1 G4:.5 A4:.5 B4:.5 A4:.5 G4:.5 G4:.5 -:1"),
|
||
C("G:4 G:4 C:4 G:4"))
|
||
render('lied02-farben', 96, [r2, s2, s2, r2], 1, intro=([], C("G:4")), end_chord='G')
|
||
|
||
# Lied 3 – Einer klein und einer groß, D-Dur, 104 BPM, Zeile = 6 Schläge, Klatscher auf Schlag 5
|
||
r3 = (L("F#4:.5 G4:.5 F#4:.5 F#4:.5 A4:.5 A4:.5 B4:1 -:1.5 G4:.25 G4:.25 "
|
||
"A4:.5 G4:.5 B4:.5 A4:.5 A4:2 -:2 "
|
||
"G4:.5 G4:.5 A4:.5 G4:.25 F#4:.25 G4:.5 F#4:.5 G4:1 -:1.75 A4:.25 "
|
||
"B4:.5 A4:.5 G4:.5 F#4:.5 E4:.25 F#4:.25 E4:.5 D4:1 -:2"),
|
||
C("D:6 G:3 A7:3 G:6 D:1.5 G:1.5 A:1.5 D:1.5"))
|
||
s3 = (L("D4:.5 D4:.5 E4:.5 F#4:.5 A4:.5 F#4:.5 E4:1 -:2 D4:.5 D4:.5 E4:.5 F#4:.5 G4:.5 F#4:.5 D4:1 -:2 "
|
||
"D4:.5 D4:.5 E4:.5 F#4:.5 A4:.5 F#4:.5 E4:1 -:2 D4:.5 D4:.5 E4:.5 F#4:.5 G4:.5 F#4:.5 D4:1 -:2"),
|
||
C("D:6 G:3 D:3 D:6 G:2 A:2 D:2"))
|
||
intro3 = C("D:4")
|
||
claps3 = [4 + 6*i + 4 for i in range(12)]
|
||
render('lied03-einer-klein-einer-gross', 104, [r3, s3, r3], 2, intro=([], intro3), end_chord='D', claps=claps3)
|
||
|
||
# Lied 4 – Körperteile, F-Dur, 96 BPM
|
||
r4 = (L("F4:1 F4:1 G4:1 A4:1 G4:1 F4:1 -:2 A4:1 F4:1 A4:1 F4:1 G4:1 G4:1 G4:2 C4:1 C4:1 D4:1 E4:1 F4:1 E4:1 D4:1 C4:1 F4:1 F4:1 F4:1 G4:1 A4:1 G4:1 F4:2"),
|
||
C("F:4 F:4 F:4 C7:4 Dm:4 Gm:2 C7:2 F:4 C7:2 F:2"))
|
||
s4 = (L("C4:.5 D4:.5 E4:.5 F4:.5 G4:.5 F4:.5 -:1 -:4 F4:.5 F4:.5 G4:.5 A4:.5 G4:.5 G4:.5 -:1 F4:.5 F4:.5 G4:.5 A4:.5 G4:.5 F4:.5 -:1"),
|
||
C("F:4 F:4 F:4 C7:2 F:2"))
|
||
render('lied04-koerperteile', 96, [r4, s4, r4], 1, intro=([], C("F:4")), end_chord='F')
|
||
|
||
# Lied 5 – Zähneputzen, G-Dur, 96 BPM (Notenname „B“ im Entwurf = H)
|
||
r5 = (L("G4:1 G4:1 A4:1 B4:1 G4:4 B4:1 A4:1 B4:1 A4:1 G4:1 A4:1 G4:2 E4:1 E4:1 G4:1 G4:1 A4:1 G4:1 E4:2 E4:1 G4:1 A4:1 G4:1 E4:1 F#4:1 G4:2"),
|
||
C("G:4 G:4 G:4 D:4 Em:4 C:4 G:4 D:2 G:2"))
|
||
s5 = (L("G4:1 A4:1 B4:1 A4:1 G4:1 E4:1 D4:2 D4:1 E4:1 G4:1 E4:1 G4:1 A4:1 G4:2 G4:1 A4:1 B4:2 -:4"),
|
||
C("G:4 D:4 C:4 G:4 G:4 G:4"))
|
||
render('lied05-zaehneputzen', 96, [r5, s5, r5], 1, intro=([], C("G:4")), end_chord='G')
|
||
|
||
# Lied 12 – Gute Nacht, F-Dur, 6/8, 72 punktierte Viertel = 216 Achtel/Min.
|
||
r12 = (L("A4:2 G4:1 F4:3 A4:2 G4:1 A4:3 G4:2 F4:1 E4:3 F4:6 C4:2 E4:1 D4:3 F4:2 E4:1 G4:3 "
|
||
"A4:2 G4:1 F4:2 E4:1 D4:2 E4:1 C4:3 A4:3 G4:3 A4:3 F4:3 G4:2 F4:1 E4:2 D4:1 F4:6"),
|
||
C("F:6 F:6 C:6 F:6 C:6 F:6 Dm:6 C:6 F:6 F:6 C:6 F:6"))
|
||
s12 = (L("F4:2 G4:1 A4:3 A4:2 G4:1 F4:3 A4:2 G4:1 F4:3 G4:3 F4:3 E4:2 F4:1 G4:3 A4:2 G4:1 E4:3 -:12"),
|
||
C("F:6 F:6 Dm:6 C:6 C:6 F:6 F:6 F:6"))
|
||
render('lied12-gute-nacht', 216, [r12, s12, r12], 3, intro=([], C("F:6 F:6")), end_chord='F')
|