"""Nine mosaics: each built only from chopped samples of one Treblo track (never the whole track), re-sequenced on its own grid, with long dark reverbs, tape echoes, freezes, sweeps and sidechain. usage: python3 compose.py name [name...]""" import sys, numpy as np import sampler as S from sampler import Mix, cut, loop, period, snap, stretch, pitch, resample, reverb, echo, freeze, sweep, tapestop, crackle, norm, lp, hp, bp, fade, rms OUT = '/home/claude/afterlight/mosaic/audio/' import os; os.makedirs(OUT, exist_ok=True) SR = S.SR def steps(name, t0, P, n): """slice one period into n equal steps""" d = P / n; return [cut(name, t0 + i * d, t0 + (i + 1) * d + 0.004, 0.002, 0.006) for i in range(n)] def play_pattern(m, layer, sl, pat, t0, step, g=1.0, name=None, src_t0=0, P=None, reps=1): n = len(sl) for r in range(reps): for i, k in enumerate(pat): if k is None: continue t = t0 + (r * len(pat) + i) * step m.put(layer, sl[k], t, g, src=name, src_t=round(src_t0 + k * (P / n if P else step), 3), tag=f'chop{k}') return t0 + reps * len(pat) * step def ember(x, secs=14, g=1.0, damp=1800): """the sound's afterglow: an Eno-length dark tail with the dry signal removed""" return reverb(x, secs, damp, 0.05) * g def kicks_of(name, t0, t1, thr=0.3): from scipy import signal x = S.load(name)[int(t0 * SR):int(t1 * SR)].mean(1); lo = lp(x[:, None], 110, 4)[:, 0]; hop = 441 e = np.array([np.sqrt((lo[i:i + hop] ** 2).mean()) for i in range(0, len(lo) - hop, hop)]) d = np.maximum(np.diff(e, prepend=0), 0); pk, _ = signal.find_peaks(d, height=d.max() * thr, distance=15) return pk * hop / SR def place_kicks(m, name, t_src0, P, t_dst, reps): ks = kicks_of(name, t_src0, t_src0 + P) m.kick_at([t_dst + r * P + k for r in range(reps) for k in ks]) # ====================================================================== def takumi(): """1956 commercial sunshine: two-bar phrases from the first 29 s, re-cut like a channel flipping, then slowed into tape warmth""" n = 'takumi'; P = 4.82; m = Mix(96) A, B, C = snap(n, 0.21), snap(n, 8.93), snap(n, 18.35) m.put('bed', freeze(cut(n, 9, 13), 9) * 0.5 * np.linspace(0, 1, 9 * SR)[:, None], 0, 0.5, n, 9, 'freeze') m.put('bed', crackle(84, 30, 0.05), 0, 1.0, None, None, 'crackle') t = 4.0 m.put('loop', sweep(loop(n, A, P, 2), 300, 16000), t, 1.0, n, A, 'loopA'); t += 2 * P m.put('loop', loop(n, B, P, 2), t, 1.0, n, B, 'loopB'); t += 2 * P # channel flip: eighth-note slices switching between the three phrases sa, sb, sc = steps(n, A, P, 16), steps(n, B, P, 16), steps(n, C, P, 16) st = P / 16 for bar in range(4): for i in range(16): j = (i // (2 if bar < 2 else 1) + bar) % 3; src = [sa, sb, sc][j]; k = i if bar % 2 == 0 else (i * 5) % 16 m.put('flip', src[k], t + (bar * 16 + i) * st, 1.0, n, round([A, B, C][j] + k * st, 3), 'flip') t += 4 * P # the slow room: phrase C at three-quarter speed, pitched with it, into a long tail slow = resample(loop(n, C, P, 2), 0.75) m.put('slow', slow, t, 0.9, n, C, 'slow'); m.put('ember', ember(slow[-SR * 2:], 12, 0.5), t + len(slow) / SR - 2, 1.0, n, C, 'tail') t += len(slow) / SR m.put('loop', loop(n, A, P, 2), t, 1.0, n, A, 'loopA'); m.put('loop', echo(cut(n, B, B + P / 2), P / 4, 0.55, 8), t + 2 * P - P / 2, 0.8, n, B, 'echo') t += 2 * P m.put('loop', tapestop(loop(n, B, P, 1), 1.2), t, 1.0, n, B, 'tapestop') m.put('ember', ember(cut(n, B + P - 1.5, B + P), 16, 0.7), t + P - 1.5, 1.0, n, B, 'tail') return m # ====================================================================== def phosphor(): """the after-43-second loop, pumped by its own kick, with dark mid-song textures held under it""" n = 'phosphor'; P = 3.303; m = Mix(92); L0 = snap(n, 46.41) dark = freeze(cut(n, 120, 127), 20) ; m.put('bed', dark * np.r_[np.linspace(0, 1, 6 * SR), np.ones(14 * SR)][:, None], 0, 0.45, n, 120, 'freeze') m.put('bed', crackle(92, 22, 0.05), 0, 1, None, None, 'crackle') t = 6.0 m.put('loop', sweep(loop(n, L0, P, 4), 200, 14000), t, 1.0, n, L0, 'loop'); place_kicks(m, n, L0, P, t, 4); t += 4 * P m.put('loop', loop(n, L0, P, 8), t, 1.0, n, L0, 'loop'); place_kicks(m, n, L0, P, t, 8) sl = steps(n, L0 + P, P, 8) play_pattern(m, 'chop', sl, [0, None, 2, 2, None, 5, 6, 6], t + 4 * P, P / 8, 0.7, n, L0 + P, P, reps=4) t += 8 * P # breakdown: the loop drops; one bar echoes away over the frozen dark m.put('bed', freeze(cut(n, 160, 166), 12), t, 0.5, n, 160, 'freeze') m.put('echo', echo(cut(n, L0, L0 + P / 2), P / 2, 0.6, 9, 0.85), t, 0.8, n, L0, 'echo') m.put('loop', hp(loop(n, L0, P, 2), 900), t + 4 * P - 2 * P, 0.5, n, L0, 'hp') t += 4 * P m.put('loop', loop(n, L0, P, 8), t, 1.0, n, L0, 'loop'); place_kicks(m, n, L0, P, t, 8) play_pattern(m, 'chop', steps(n, L0 + 2 * P, P, 16), [0, 0, None, 3, 4, None, 6, 7, 8, 8, None, 11, 12, 12, 14, None], t, P / 16, 0.6, n, L0 + 2 * P, P, reps=8) t += 8 * P m.put('loop', tapestop(loop(n, L0, P, 1), 1.5), t, 1.0, n, L0, 'tapestop') m.put('ember', ember(cut(n, L0, L0 + P), 18, 0.8), t, 1.0, n, L0, 'tail') m.duck(['loop', 'bed', 'chop'], 0.55, release=0.3) return m # ====================================================================== def beckon(): """only the first 49 s: the rising tones as grains, scattered across the stereo field, with one soft pulse from the end of that stretch""" n = 'beckon'; m = Mix(80) for i, (a, b, at, g) in enumerate([(2, 8, 0, 0.6), (14, 20, 8, 0.5), (24, 30, 18, 0.55), (31, 36, 30, 0.5), (8, 14, 44, 0.5)]): fz = freeze(cut(n, a, b), 18, grain=0.12 + 0.05 * i, dens=36) env = np.minimum(np.linspace(0, 1, len(fz)) * 3, 1) * np.minimum(np.linspace(1, 0, len(fz)) * 3, 1) m.put('grains', fz * env[:, None], at, g, n, a, 'grain') # splats: single short grains (80-250 ms) thrown across the field at irregular times r = np.random.default_rng(3) for k in range(70): a = r.uniform(1, 47); L = r.uniform(0.08, 0.25); at = r.uniform(4, 74) x = cut(n, a, a + L, 0.01, 0.05); p = r.uniform(-1, 1); x = x * np.array([np.sqrt(0.5 * (1 - p)), np.sqrt(0.5 * (1 + p))]) * 1.4 m.put('splat', reverb(x, r.uniform(2, 7), 3000, 0.01, 0.6), at, 0.9, n, round(a, 2), 'splat') P = period(n, 37.59, 95.0, 8); L0 = snap(n, 37.59) pulse = lp(loop(n, L0, P, 6), 1800); pulse *= np.minimum(np.linspace(0, 1, len(pulse)) * 2, 1)[:, None] * np.minimum(np.linspace(1, 0, len(pulse)) * 3, 1)[:, None] m.put('pulse', pulse, 30, 0.8, n, L0, 'pulse') m.put('ember', ember(cut(n, 44, 49), 20, 0.6), 62, 1.0, n, 44, 'tail') return m # ====================================================================== def circle(): """from one minute on: the slow two-step, a circle of its own echoes, the sustained tones frozen into a ring""" n = 'circle'; P = 3.175; L0 = snap(n, 60.96); m = Mix(82) m.put('ring', freeze(cut(n, 66, 72), 82, grain=0.3, dens=20), 0, 0.35, n, 66, 'freeze') t = 3.0 m.put('loop', sweep(loop(n, L0, P, 4), 250, 12000), t, 1.0, n, L0, 'loop'); place_kicks(m, n, L0, P, t, 4); t += 4 * P for k in range(4): a = L0 + P * (2 + k); m.put('loop', loop(n, a, P, 2), t, 1.0, n, a, 'loop'); place_kicks(m, n, a, P, t, 2) m.put('echo', echo(cut(n, a + P * 0.75, a + P), P / 3, 0.6, 7), t + 2 * P - P / 4, 0.7, n, a + P * 0.75, 'echo'); t += 2 * P # the circle: one bar played forward, then its slices in a rotating order sl = steps(n, L0 + 4 * P, P, 8) for rot in range(4): play_pattern(m, 'chop', sl, [(i + rot * 3) % 8 for i in range(8)], t + rot * P, P / 8, 0.9, n, L0 + 4 * P, P) place_kicks(m, n, L0 + 4 * P, P, t, 4); t += 4 * P m.put('loop', loop(n, snap(n, 90), P, 4), t, 1.0, n, 90, 'loop'); place_kicks(m, n, snap(n, 90), P, t, 4); t += 4 * P m.put('ember', ember(cut(n, 100, 104), 16, 0.8), t, 1.0, n, 100, 'tail') m.duck(['ring', 'chop'], 0.4, release=0.4) return m # ====================================================================== def warmth(): """the textures and the places where the song ducks out: a groove that keeps stepping aside""" n = 'warmth'; P = 2.030; m = Mix(90) G1, G2, G3 = snap(n, 14.44), snap(n, 29.98), snap(n, 44.3) m.put('bed', freeze(cut(n, 60, 66), 14), 0, 0.5, n, 60, 'freeze') t = 5.0 m.put('loop', sweep(loop(n, G1, P, 4), 200, 15000), t, 1.0, n, G1, 'loop'); place_kicks(m, n, G1, P, t, 4); t += 4 * P m.put('loop', loop(n, G2, P, 8), t, 1.0, n, G2, 'loop'); place_kicks(m, n, G2, P, t, 8); t += 8 * P # duck out: two bars of the quiet texture region, one held chord, the groove's last hit echoing m.put('duck', cut(n, 88, 88 + 2 * P), t, 1.0, n, 88, 'duckout') m.put('echo', echo(cut(n, G2 + 7 * P, G2 + 7 * P + P / 4), P / 4, 0.62, 10), t, 0.7, n, G2 + 7 * P, 'echo'); t += 2 * P sl = steps(n, G3, P, 16) m.put('loop', loop(n, G3, P, 8), t, 1.0, n, G3, 'loop'); place_kicks(m, n, G3, P, t, 8) play_pattern(m, 'chop', sl, [0, None, None, 3, None, None, 6, None, 8, None, 10, 10, None, 13, None, 15], t + 4 * P, P / 16, 0.8, n, G3, P, reps=4) t += 8 * P m.put('duck', freeze(cut(n, 96, 100), 5), t, 0.8, n, 96, 'freeze'); t += 2 * P m.put('loop', loop(n, G1, P, 6), t, 1.0, n, G1, 'loop'); place_kicks(m, n, G1, P, t, 6); t += 6 * P m.put('loop', tapestop(loop(n, G2, P, 1), 1.0), t, 1.0, n, G2, 'tapestop') m.put('ember', ember(cut(n, 108, 116), 16, 0.7), t, 1.0, n, 108, 'tail') m.duck(['loop', 'bed'], 0.45, release=0.25) return m # ====================================================================== def dawn(): """made listenable by slowing it: the swells at half speed and an octave down, smeared, with the bright moments let through""" n = 'dawn'; m = Mix(80) slow = resample(cut(n, 30, 62), 0.5) # 32 s -> 64 s, an octave lower slow = lp(slow, 5000) * np.minimum(np.linspace(0, 1, len(slow)) * 4, 1)[:, None] m.put('slow', slow, 2, 0.8, n, 30, 'halfspeed') m.put('freeze', freeze(cut(n, 12, 16), 30, grain=0.4, dens=16), 0, 0.5, n, 12, 'freeze') P = 2.564 for k, a in enumerate([33.2, 45.0, 55.5, 70.4, 82.0]): a = snap(n, a); x = cut(n, a, a + P) m.put('swell', x, 12 + k * 12.5, 0.7, n, a, 'swell'); m.put('ember', ember(x, 12, 0.6), 12 + k * 12.5, 1.0, n, a, 'tail') m.put('ember', ember(cut(n, 86, 92), 18, 0.7), 66, 1.0, n, 86, 'tail') return m # ====================================================================== def picture(): """thus the picture is reassembled: one-shots cut from the whole track and rebuilt into a new beat""" n = 'picture'; m = Mix(84); P = 2.0 # 120 bpm bar hits = {} for nm, a in [('low', 3.24), ('note1', 0.5), ('note2', 1.6), ('stab', 8.1), ('pluck', 22.6), ('bell', 29.1), ('blip', 33.8), ('chord', 38.5), ('arp', 55.0), ('end', 79.6)]: a = snap(n, a, 0.2); hits[nm] = (a, cut(n, a, a + (0.9 if nm not in ('chord', 'end') else 2.2), 0.002, 0.08)) def hit(layer, nm, t, g=1.0): a, x = hits[nm]; m.put(layer, x, t, g, n, round(a, 2), nm) # the picture arrives a piece at a time for i, nm in enumerate(['note1', 'note2', 'bell', 'pluck', 'blip', 'chord']): hit('pieces', nm, 1 + i * 1.5, 0.8); m.put('ember', ember(hits[nm][1], 6, 0.4), 1 + i * 1.5, 1, n, hits[nm][0], 'tail') t = 10.0; st = P / 8 beat = {'low': [0, 3, 8, 11], 'stab': [4, 12], 'blip': [2, 6, 10, 14], 'pluck': [7, 15], 'bell': [5]} for bar in range(24): for nm, pos in beat.items(): for p in pos: if bar % 8 == 7 and nm != 'low': continue hit('beat', nm, t + bar * P + p * st, 0.9 if nm == 'low' else 0.6) if bar % 4 == 0: hit('chords', 'chord', t + bar * P, 0.5) if bar % 2 == 1: hit('arp', 'arp', t + bar * P + 4 * st, 0.5) if bar >= 16: hit('arp', 'note2', t + bar * P + 6 * st, 0.4) m.kick_at([t + b * P + p * st for b in range(24) for p in beat['low']]) # a long stretch of the track's quiet passage under the second half m.put('bed', freeze(cut(n, 58, 64), 26), t + 8 * P, 0.35, n, 58, 'freeze') t += 24 * P m.put('end', echo(hits['end'][1], P / 4, 0.6, 10), t, 0.9, n, hits['end'][0], 'echo') m.put('ember', ember(hits['chord'][1], 16, 0.8), t, 1, n, hits['chord'][0], 'tail') m.duck(['chords', 'bed', 'arp'], 0.5, release=0.2) return m # ====================================================================== def sun(): """the opening twelve seconds, its kick at 9.3 s, built out into a slow-burning groove""" n = 'sun'; m = Mix(84); P = 2.890; K = snap(n, 9.30) intro = cut(n, 0.0, 9.3) m.put('intro', intro, 0, 1.0, n, 0.0, 'intro'); m.put('ember', ember(intro, 10, 0.4), 0, 1, n, 0, 'tail') t = 9.3 m.put('loop', loop(n, K, P, 4), t, 1.0, n, K, 'loop'); place_kicks(m, n, K, P, t, 4); t += 4 * P stabs = [cut(n, a, a + 1.2) for a in (0.03, 2.35, 4.65, 6.95)] for bar in range(8): m.put('loop', loop(n, K, P, 1), t + bar * P, 1.0, n, K, 'loop') m.put('stab', stabs[bar % 4], t + bar * P + P / 2, 0.6, n, round((0.03, 2.35, 4.65, 6.95)[bar % 4], 2), 'stab') place_kicks(m, n, K, P, t, 8); t += 8 * P # the second section of the song, cut into the same grid D = snap(n, 20.0); PD = period(n, D, 83.0, 4) m.put('loop', loop(n, D, PD, 4), t, 1.0, n, D, 'loopD'); place_kicks(m, n, D, PD, t, 4) m.put('intro', stretch(intro, 0.5)[:int(4 * PD * SR)] * 0.6, t, 0.6, n, 0, 'stretched'); t += 4 * PD m.put('loop', loop(n, K, P, 4), t, 1.0, n, K, 'loop'); place_kicks(m, n, K, P, t, 4); t += 4 * P m.put('echo', echo(stabs[0], P / 2, 0.6, 8), t, 0.8, n, 0.03, 'echo') m.put('ember', ember(intro[-3 * SR:], 18, 0.8), t, 1, n, 6, 'tail') m.duck(['intro', 'stab'], 0.5) return m # ====================================================================== def copper(): """the only voice: 'light-years in a second-hand song', 'carry the glow', 'the room goes gold', 'golden record', chopped over the song's own groove""" n = 'copper'; P = 3.75; m = Mix(100) V1 = cut(n, 0.0, 7.5) # Light-years in a second-hand song / Light-years and I'm still holding on V2 = cut(n, 46.0, 60.0) # Oh-oh-oh carry the glow / light-years to go V3 = cut(n, 67.0, 81.0) # Neighbors ... room goes gold GR = cut(n, 137.0, 139.4) # golden record # opening: the first line slowed and lowered, alone, in a long room low = resample(V1, 0.8) m.put('vox', low, 0.5, 0.8, n, 0.0, 'lightyears-slow'); m.put('ember', ember(low, 10, 0.6), 0.5, 1, n, 0, 'tail') t = 0.5 + len(low) / SR G = snap(n, 85.0); PG = period(n, G, 128.0, 8) m.put('loop', sweep(loop(n, G, PG, 2), 300, 15000), t, 1.0, n, G, 'groove'); place_kicks(m, n, G, PG, t, 2); t += 2 * PG m.put('vox', V1, t, 1.0, n, 0.0, 'lightyears'); m.put('loop', loop(n, G, PG, 2), t, 1.0, n, G, 'groove'); place_kicks(m, n, G, PG, t, 2); t += 2 * PG # stutter: 'carry the glow' cut into sixteenths over the groove m.put('loop', loop(n, G, PG, 4), t, 1.0, n, G, 'groove'); place_kicks(m, n, G, PG, t, 4) cg = steps(n, 46.0, PG, 16) play_pattern(m, 'chop', cg, [0, 1, 0, 1, 4, 5, 4, 5, 8, 9, 10, 11, 8, 8, 8, 8], t, PG / 16, 0.9, n, 46.0, PG, reps=2) m.put('vox', V2[:int(2 * PG * SR)], t + 2 * PG, 1.0, n, 46.0, 'carrytheglow'); t += 4 * PG m.put('loop', loop(n, G, PG, 4), t, 1.0, n, G, 'groove'); place_kicks(m, n, G, PG, t, 4) m.put('vox', V3, t, 1.0, n, 67.0, 'roomgoesgold'); t += 4 * PG # break: the groove leaves; 'golden record' echoes into the dark m.put('echo', echo(GR, PG / 4, 0.62, 10, 0.85), t, 0.9, n, 137.0, 'goldenrecord') m.put('bed', freeze(cut(n, 60, 66), 8), t, 0.5, n, 60, 'freeze'); t += 2 * PG m.put('loop', loop(n, G, PG, 4), t, 1.0, n, G, 'groove'); place_kicks(m, n, G, PG, t, 4) m.put('vox', V2[int(2 * PG * SR):], t, 1.0, n, 46.0 + 2 * PG, 'lightyearstogo') play_pattern(m, 'chop', steps(n, 0.0, PG, 16), [0, None, 0, None, 2, 2, None, 3, 0, None, 0, 1, 2, 3, None, None], t + 2 * PG, PG / 16, 0.6, n, 0.0, PG, reps=2) t += 4 * PG m.put('loop', tapestop(loop(n, G, PG, 1), 1.4), t, 1.0, n, G, 'tapestop') m.put('vox', resample(V2[-int(3 * SR):], 0.85), t + 0.4, 0.8, n, 57.0, 'outro') m.put('ember', ember(V2[-int(3 * SR):], 20, 0.9), t + 0.4, 1, n, 57.0, 'tail') m.duck(['loop', 'bed'], 0.5, release=0.22) return m ALL = dict(takumi=takumi, phosphor=phosphor, beckon=beckon, circle=circle, warmth=warmth, dawn=dawn, picture=picture, sun=sun, copper=copper) if __name__ == '__main__': for nm in (sys.argv[1:] or ALL): m = ALL[nm](); out = m.render(); end = len(out) # trim trailing silence, fade e = np.abs(out).max(1); nz = np.nonzero(e > 0.003)[0]; end = min(len(out), nz[-1] + SR // 2) if len(nz) else len(out) out = out[:end]; out[-SR:] *= np.linspace(1, 0, min(SR, len(out)))[:, None] m.n = end; m.dur = end / SR for k in m.layers: m.layers[k] = m.layers[k][:end] m.save(OUT + nm + '.wav', out); print(nm, round(end / SR, 1), 's', len(m.events), 'events', 'kicks', len(m.kicks), flush=True)