aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/music.py
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-26 20:21:47 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-26 20:21:47 -0300
commit59355909f2de9236af8168a26c70bcf6caa3b285 (patch)
tree686186e2086f81aa22ad25e78eb29fca3cbadc9a /scripts/music.py
download100cia-videos-59355909f2de9236af8168a26c70bcf6caa3b285.tar.gz
100cia-videos-59355909f2de9236af8168a26c70bcf6caa3b285.zip
Import video pipeline as it was
Diffstat (limited to 'scripts/music.py')
-rw-r--r--scripts/music.py166
1 files changed, 166 insertions, 0 deletions
diff --git a/scripts/music.py b/scripts/music.py
new file mode 100644
index 0000000..a3aee16
--- /dev/null
+++ b/scripts/music.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+"""Cama musical original hecha con las muestras de piano de keysound.
+
+Muestra NN => nota MIDI NN+20 (01-A-大字2组 = A0 = MIDI 21)
+Las progresiones son propias, para que el audio sea libre de derechos.
+"""
+import glob, os, wave
+import numpy as np
+
+PIANO_DIR = "/mnt/Data/GitHub/keysound/audio/piano"
+SR = 44100
+
+_paths, _cache = {}, {}
+for _p in glob.glob(os.path.join(PIANO_DIR, "*.wav")):
+ _paths[int(os.path.basename(_p).split("-")[0]) + 20] = _p
+
+NOTE = {"C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5,
+ "F#": 6, "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11}
+TRIAD = {"maj": [0, 4, 7], "min": [0, 3, 7]}
+
+
+def midi(name, octave):
+ return 12 * (octave + 1) + NOTE[name]
+
+
+def load(m):
+ m = max(21, min(108, m))
+ if m not in _cache:
+ with wave.open(_paths[m], "rb") as w:
+ d = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16)
+ d = d.reshape(-1, w.getnchannels()).astype(np.float32) / 32768.0
+ if d.shape[1] == 1:
+ d = np.repeat(d, 2, axis=1)
+ _cache[m] = d
+ return _cache[m]
+
+
+def chord(sym):
+ """'Am' -> (bajo, triada). Soporta sostenidos y sufijo m."""
+ q = "min" if sym.endswith("m") else "maj"
+ root = sym[:-1] if sym.endswith("m") else sym
+ r = NOTE[root]
+ bass = 12 * (2 + 1) + r # octava 2
+ triad = [12 * (4 + 1) + r + i for i in TRIAD[q]]
+ return bass, triad
+
+
+# progresion + tempo por video
+BEDS = {
+ "montyhall": (["Am", "F", "C", "G"], 92),
+ "nueves": (["Em", "C", "G", "D"], 88),
+ "cumple": (["C", "G", "Am", "F"], 96),
+ "luna": (["Dm", "A#", "F", "C"], 82),
+ "japones": (["G", "D", "Em", "C"], 100),
+ "benford": (["Am", "Em", "F", "G"], 94),
+ "hilbert": (["C", "Em", "Am", "F"], 80),
+ "reuleaux": (["F", "C", "Dm", "A#"], 98),
+ "regla72": (["G", "Em", "C", "D"], 104),
+ "buffon": (["Am", "G", "F", "E"], 86),
+ "collatz": (["Am", "Em", "Dm", "E"], 90),
+ "simpson": (["Cm", "G#", "A#", "Gm"], 96),
+ "cuerda": (["F", "Am", "Dm", "C"], 84),
+ "gabriel": (["Em", "Bm", "C", "G"], 76),
+ "mobius": (["D", "A", "Bm", "G"], 102),
+ "bayes": (["Dm", "Gm", "A#", "A"], 86),
+ "cicloide": (["A", "E", "F#m", "D"], 106),
+ "dados": (["Em", "Am", "D", "G"], 94),
+ "bolapeluda": (["C", "Am", "Dm", "G"], 100),
+ "caos": (["Am", "F", "Dm", "E"], 88),
+ "diferencial": (["Bm", "G", "D", "A"], 112, "motor"),
+ "galton": (["D", "Bm", "G", "A"], 96),
+ "bloques": (["E", "C#m", "A", "B"], 100),
+ "domino": (["G", "D", "Em", "C"], 104, "motor"),
+}
+
+# melodias propias: por compas, (tiempo en negras, nota MIDI, duracion en negras)
+MELODIAS = {
+ "motor": [
+ [(0, 78, 1.5), (1.5, 74, 0.5), (2, 76, 1), (3, 78, 1)], # Bm
+ [(0, 79, 1.5), (1.5, 78, 0.5), (2, 76, 1), (3, 74, 1)], # G
+ [(0, 78, 1), (1, 81, 1), (2, 78, 0.5), (2.5, 76, 0.5), (3, 74, 1)], # D
+ [(0, 73, 2), (2, 76, 1), (3, 69, 1)], # A
+ ],
+}
+
+
+def bed(name, total_s, gain=0.30, seed=0):
+ prog, bpm = BEDS[name][:2]
+ estilo = BEDS[name][2] if len(BEDS[name]) > 2 else None
+ beat = 60.0 / bpm
+ measure = 4 * beat
+ eighth = beat / 2
+ n = int((total_s + 3.0) * SR)
+ buf = np.zeros((n, 2), dtype=np.float32)
+ rng = np.random.default_rng(seed)
+
+ def hit(m, t, g, max_len=None):
+ s = load(m) * g
+ if max_len is not None:
+ k = min(len(s), int(max_len * SR))
+ s = s[:k].copy()
+ f = min(int(0.06 * SR), k)
+ s[-f:] *= np.linspace(1, 0, f)[:, None]
+ i = int(t * SR)
+ if i >= n:
+ return
+ buf[i:i + len(s)] += s[:n - i]
+
+ k = 0
+ t = 0.0
+ while t < total_s + 0.5:
+ sym = prog[k % len(prog)]
+ bass, triad = chord(sym)
+ if estilo == "motor":
+ # bajo en corcheas, como un motor; acorde en 1 y 3; melodia arriba
+ for j in range(8):
+ hit(bass + (12 if j % 2 else 0), t + j * eighth,
+ 0.34 if j % 2 == 0 else 0.20, max_len=eighth * 0.9)
+ for b_ in (0, 2):
+ for nota in triad:
+ hit(nota - 12, t + b_ * beat, 0.13, max_len=beat * 1.8)
+ vuelta = (k // 4) % 2 # la segunda vuelta, una octava arriba
+ for (b_, nota, d) in MELODIAS[estilo][k % 4]:
+ hit(nota + 12 * vuelta, t + b_ * beat, 0.30, max_len=d * beat * 1.05)
+ t += measure
+ k += 1
+ continue
+ hit(bass, t, 0.50, max_len=measure * 0.95)
+ hit(bass + 12, t + 2 * beat, 0.26, max_len=beat * 1.6)
+ pattern = [triad[0], triad[1], triad[2], triad[1],
+ triad[2], triad[1], triad[2], triad[0]]
+ for j, note in enumerate(pattern):
+ g = 0.30 if j % 2 == 0 else 0.20
+ hit(note, t + j * eighth, g, max_len=beat * 1.15)
+ if k % 4 == 3: # brillito al cerrar la vuelta
+ hit(triad[2] + 12, t + 3.5 * beat, 0.16, max_len=beat)
+ t += measure
+ k += 1
+
+ buf = buf[:int(total_s * SR)]
+ peak = np.abs(buf).max() + 1e-9
+ buf *= gain / peak
+ # entrada y salida suaves
+ fi, fo = int(1.2 * SR), int(2.0 * SR)
+ buf[:fi] *= np.linspace(0, 1, fi)[:, None]
+ buf[-fo:] *= np.linspace(1, 0, fo)[:, None]
+ return buf
+
+
+def duck(music, speech, sr=SR, floor_db=-11.0, block=512, release=0.45):
+ """Baja la musica cuando hay voz (sidechain simple)."""
+ mono = np.abs(speech).mean(axis=1) if speech.ndim > 1 else np.abs(speech)
+ nb = len(mono) // block
+ env = mono[:nb * block].reshape(nb, block).max(axis=1)
+ rel = np.exp(-block / (release * sr))
+ out = np.zeros(nb, dtype=np.float32)
+ acc = 0.0
+ for i, v in enumerate(env):
+ acc = v if v > acc else acc * rel + v * (1 - rel)
+ out[i] = acc
+ g_floor = 10 ** (floor_db / 20)
+ active = np.clip(out / 0.03, 0, 1)
+ gain = 1.0 - (1.0 - g_floor) * active
+ idx = np.linspace(0, nb - 1, len(music))
+ g = np.interp(idx, np.arange(nb), gain).astype(np.float32)
+ return music * g[:, None]