#!/usr/bin/env python3
"""Composes the music and the sound effects of the game (assets/music/*.ogg, assets/sfx/*.wav):
python3 tools/sound.py   (numpy; ffmpeg for the Ogg files). Original tunes, written here as notes."""
import os, subprocess, wave
import numpy as np

ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'assets')
SR = 32000
NOTES = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
rng = np.random.default_rng(3)


def hz(name):
    n = NOTES[name[0]]
    rest = name[1:]
    if rest[0] in '#b':
        n += 1 if rest[0] == '#' else -1
        rest = rest[1:]
    return 440.0 * 2 ** ((n + (int(rest) + 1) * 12 - 69) / 12)


def osc(kind, f, n, duty=0.5, vib=0.0, vibhz=5.5):
    t = np.arange(n) / SR
    ph = f * t + (vib * f / (2 * np.pi * vibhz)) * np.sin(2 * np.pi * vibhz * t) if vib else f * t
    if kind == 'pulse':
        return np.where((ph % 1) < duty, 1.0, -1.0)
    if kind == 'tri':
        return 2 * np.abs(2 * (ph % 1) - 1) - 1
    if kind == 'saw':
        return 2 * (ph % 1) - 1
    if kind == 'bell':
        return np.sin(2 * np.pi * ph) + 0.5 * np.sin(2 * np.pi * ph * 3.01) * np.exp(-t * 9) + 0.25 * np.sin(2 * np.pi * ph * 5.4) * np.exp(-t * 14)
    return np.sin(2 * np.pi * ph)


def env(n, a=0.005, d=0.08, s=0.6, r=0.05, hold=None):
    e = np.ones(n) * s
    na, nd, nr = int(a * SR), int(d * SR), int(r * SR)
    na, nd = min(na, n), min(nd, max(0, n - na))
    e[:na] = np.linspace(0, 1, na, endpoint=False)
    e[na:na + nd] = np.linspace(1, s, nd)
    nr = min(nr, n)
    e[n - nr:] *= np.linspace(1, 0, nr)
    return e


def lowpass(x, k):
    if k <= 1:
        return x
    return np.convolve(x, np.ones(k) / k, mode='same')


class Song:
    def __init__(self, bpm, bars, step=0.5):
        self.beat = 60 / bpm
        self.step = self.beat * step          # one token = an eighth note by default
        self.n = int(bars * 4 * self.beat * SR)
        self.l = np.zeros(self.n + SR * 3)
        self.r = np.zeros(self.n + SR * 3)

    def add(self, sig, t, vol=1.0, pan=0.0):
        i = int(t * SR)
        sig = sig[:len(self.l) - i]
        self.l[i:i + len(sig)] += sig * vol * (1 - max(0, pan))
        self.r[i:i + len(sig)] += sig * vol * (1 + min(0, pan))

    def line(self, tokens, kind='pulse', vol=0.2, pan=0.0, step=None, duty=0.5, vib=0.0, a=0.005, d=0.08, s=0.6, r=0.05, gate=0.92, lp=1,
             echo=0.0, start=0.0, octave=0):
        step = step or self.step
        toks = tokens.split()
        i = 0
        while i < len(toks):
            tok = toks[i]
            j = i + 1
            while j < len(toks) and toks[j] == '-':
                j += 1
            if tok != '.':
                dur = (j - i) * step * gate
                n = int((dur + r) * SR)
                f = hz(tok) * 2 ** octave
                sig = lowpass(osc(kind, f, n, duty, vib) * env(n, a, d, s, r), lp)
                t = start + i * step
                self.add(sig, t, vol, pan)
                if echo:
                    self.add(sig, t + self.beat * 0.75, vol * echo, -pan if pan else 0.4)
                    self.add(sig, t + self.beat * 1.5, vol * echo * 0.5, pan if pan else -0.4)
            i = j

    def chord(self, names, t, dur, vol=0.08, lp=14):
        n = int((dur + 0.3) * SR)
        sig = np.zeros(n)
        for k, name in enumerate(names):
            for det in (0.996, 1.004):
                sig += osc('saw', hz(name) * det, n)
        sig = lowpass(sig, lp) * env(n, 0.12, 0.2, 0.8, 0.3)
        self.add(sig, t, vol, 0.0)

    def kick(self, t, vol=0.5):
        n = int(0.16 * SR)
        tt = np.arange(n) / SR
        self.add(np.sin(2 * np.pi * (48 * tt + 70 * (1 - np.exp(-tt * 28)) / 28)) * np.exp(-tt * 22), t, vol)

    def snare(self, t, vol=0.25):
        n = int(0.14 * SR)
        tt = np.arange(n) / SR
        self.add((rng.uniform(-1, 1, n) * 0.8 + np.sin(2 * np.pi * 190 * tt) * 0.5) * np.exp(-tt * 26), t, vol)

    def hat(self, t, vol=0.07, long=False):
        n = int((0.09 if long else 0.03) * SR)
        x = rng.uniform(-1, 1, n)
        x = x - lowpass(x, 5)
        self.add(x * np.exp(-np.arange(n) / SR * (30 if long else 90)), t, vol, 0.3)

    def drums(self, bars, pattern):
        """pattern: 16 characters per bar: k kick, s snare, h hat, H open hat, . nothing; several rows are layered"""
        s16 = self.beat / 4
        for b in range(bars):
            for row in pattern:
                row = row[(b * 16) % len(row):][:16]
                for i, ch in enumerate(row):
                    t = (b * 16 + i) * s16
                    if ch == 'k': self.kick(t)
                    elif ch == 's': self.snare(t)
                    elif ch == 'h': self.hat(t)
                    elif ch == 'H': self.hat(t, 0.08, True)

    def save(self, name, gain=0.9):
        l, r = self.l.copy(), self.r.copy()
        l[:len(l) - self.n] += l[self.n:]      # the tail wraps around: a seamless loop
        r[:len(r) - self.n] += r[self.n:]
        st = np.stack([l[:self.n], r[:self.n]], axis=1)
        st = np.tanh(st * 1.4) / np.max(np.abs(np.tanh(st * 1.4))) * gain
        wav = os.path.join(ROOT, 'music', name + '.wav')
        os.makedirs(os.path.dirname(wav), exist_ok=True)
        with wave.open(wav, 'wb') as w:
            w.setnchannels(2); w.setsampwidth(2); w.setframerate(SR)
            w.writeframes((st * 32767).astype('<i2').tobytes())
        subprocess.run(['ffmpeg', '-y', '-loglevel', 'error', '-i', wav, '-c:a', 'libvorbis', '-q:a', '3', os.path.join(ROOT, 'music', name + '.ogg')], check=True)
        os.remove(wav)


def title():
    """Lantern Lullaby: slow, warm, a music box over soft chords (A minor)."""
    s = Song(88, 16)
    bar = 4 * s.beat
    chords = [('A3', 'C4', 'E4'), ('F3', 'A3', 'C4'), ('C3', 'E4', 'G4'), ('G3', 'B3', 'D4'),
              ('A3', 'C4', 'E4'), ('F3', 'A3', 'C4'), ('D3', 'F4', 'A4'), ('E3', 'G#3', 'B3')]
    for rep in range(2):
        for i, c in enumerate(chords):
            s.chord(c, (rep * 8 + i) * bar, bar, 0.05)
    box = ('E5 . A5 . C6 - B5 A5  A5 . C6 . F5 - - .  G5 . C6 . E6 - D6 C6  B5 . D6 . G5 - - . '
           'E5 . A5 . C6 - E6 D6  C6 . A5 . F5 - A5 .  A5 . F5 . D5 - F5 A5  B5 - G#5 - E5 - - . ')
    box2 = ('E6 - C6 A5 E6 - C6 A5  F6 - C6 A5 F6 - C6 .  E6 - C6 G5 E6 - D6 C6  D6 - B5 G5 D6 - - . '
            'E6 - C6 A5 C6 - E6 A6  A6 - F6 C6 A5 - C6 .  A5 - D6 F6 A6 - F6 D6  E6 - B5 G#5 E5 - - . ')
    s.line(box, 'bell', 0.2, -0.15, d=0.4, s=0.0, r=0.5, gate=1.0, echo=0.35)
    s.line(box2, 'bell', 0.17, 0.2, d=0.4, s=0.0, r=0.5, gate=1.0, echo=0.35, start=8 * bar)
    bass = 'A2 - - - A2 - E3 -  F2 - - - F2 - C3 -  C3 - - - C3 - G2 -  G2 - - - G2 - D3 - '
    bass2 = 'A2 - - - A2 - E3 -  F2 - - - F2 - C3 -  D3 - - - D3 - A2 -  E2 - - - E2 - B2 - '
    for rep in range(2):
        s.line(bass + bass2, 'tri', 0.3, 0, a=0.02, s=0.8, r=0.15, start=rep * 8 * bar)
    arp = ('A4 E5 C5 E5 ' * 4 + 'A4 F5 C5 F5 ' * 4 + 'G4 E5 C5 E5 ' * 4 + 'G4 D5 B4 D5 ' * 4 +
           'A4 E5 C5 E5 ' * 4 + 'A4 F5 C5 F5 ' * 4 + 'A4 F5 D5 F5 ' * 4 + 'G#4 E5 B4 E5 ' * 4)
    s.line(arp, 'sine', 0.06, 0.35, step=s.beat / 4, d=0.1, s=0.2, r=0.08, start=8 * bar)
    s.save('title', 0.8)


def play():
    """Follow the Light: the marching tune of the game, bright and bouncy (D minor, 132 bpm)."""
    s = Song(132, 16)
    bar = 4 * s.beat
    lead_a = ('D5 . F5 A5 . A5 G5 F5  D5 . F5 Bb5 . A5 . F5  C5 . F5 A5 . C6 A5 F5  G5 - E5 . C5 E5 G5 . '
              'A5 A5 . G5 F5 . D5 .  Bb5 . A5 G5 F5 . D5 F5  A5 . C6 . A5 F5 A5 .  G5 - . E5 G5 . C6 - ')
    lead_b = ('D6 . A5 D6 . F6 E6 D6  D6 . Bb5 D6 . F6 . D6  C6 . A5 C6 . F6 E6 C6  E6 - C6 . G5 C6 E6 . '
              'F6 F6 . E6 D6 . A5 .  D6 . C6 Bb5 A5 . F5 A5  C6 . A5 . F5 A5 C6 E6  D6 - - . D5 . D6 . ')
    s.line(lead_a, 'pulse', 0.15, -0.1, duty=0.25, vib=0.012, s=0.7, echo=0.2)
    s.line(lead_b, 'pulse', 0.15, 0.1, duty=0.5, vib=0.012, s=0.7, echo=0.2, start=8 * bar)
    s.line(lead_b, 'tri', 0.1, -0.3, start=8 * bar, octave=-1)
    bass_bar = {'D': 'D2 D3 D2 D3 D2 D3 A2 D3 ', 'Bb': 'Bb1 Bb2 Bb1 Bb2 Bb1 Bb2 F2 Bb2 ', 'F': 'F2 F3 F2 F3 F2 F3 C3 F3 ',
                'C': 'C2 C3 C2 C3 C2 C3 G2 E3 ', 'A': 'A1 A2 A1 A2 A1 A2 E2 C#3 '}
    prog = ['D', 'Bb', 'F', 'C', 'D', 'Bb', 'F', 'C', 'D', 'Bb', 'F', 'C', 'D', 'Bb', 'F', 'A']
    s.line(''.join(bass_bar[c] for c in prog), 'tri', 0.34, 0, a=0.004, d=0.1, s=0.5, r=0.04, gate=0.8)
    arps = {'D': 'D4 F4 A4 D5 A4 F4 ', 'Bb': 'D4 F4 Bb4 D5 Bb4 F4 ', 'F': 'C4 F4 A4 C5 A4 F4 ', 'C': 'C4 E4 G4 C5 G4 E4 ', 'A': 'C#4 E4 A4 C#5 A4 E4 '}
    arp = ''
    for c in prog:
        seq = arps[c].split()
        arp += ' '.join(seq[i % 6] for i in range(16)) + ' '
    s.line(arp, 'pulse', 0.05, 0.4, step=s.beat / 4, duty=0.125, d=0.05, s=0.3, r=0.03, gate=0.7)
    chords = {'D': ('D4', 'F4', 'A4'), 'Bb': ('D4', 'F4', 'Bb4'), 'F': ('C4', 'F4', 'A4'), 'C': ('C4', 'E4', 'G4'), 'A': ('C#4', 'E4', 'A4')}
    for i, c in enumerate(prog):
        s.chord(chords[c], i * bar, bar, 0.028, 10)
    s.drums(16, ['k.....k.k.......' 'k.....k.k.....k.', '....s.......s...' '....s.......s.ss', 'h.h.h.h.h.h.h.H.'])
    s.save('play', 0.82)


def play2():
    """Deep Glow: for caves and forges, darker and driving (E minor, 120 bpm)."""
    s = Song(120, 16)
    bar = 4 * s.beat
    lead_a = ('E5 - G5 B5 - A5 G5 .  E5 - G5 C6 - B5 G5 .  D5 - G5 B5 - D6 B5 .  A5 - F#5 D5 - F#5 A5 . '
              'B5 . B5 A5 G5 . E5 .  C6 . B5 G5 E5 . G5 .  B5 - D6 - B5 G5 B5 .  A5 - - F#5 D5 . F#5 . ')
    lead_b = ('E6 . B5 E6 . G6 F#6 E6  E6 . C6 E6 . G6 . E6  D6 . B5 D6 . G6 F#6 D6  F#6 - D6 . A5 D6 F#6 . '
              'G6 . F#6 E6 . B5 . E6  E6 . D6 C6 B5 . G5 B5  D6 . B5 . G5 B5 D6 F#6  E6 - - - B5 . E5 . ')
    s.line(lead_a, 'tri', 0.22, -0.1, vib=0.01, s=0.8, echo=0.25)
    s.line(lead_b, 'pulse', 0.13, 0.1, duty=0.25, vib=0.012, s=0.7, echo=0.25, start=8 * bar)
    bass_bar = {'E': 'E2 . E2 E3 . E2 B2 E3 ', 'C': 'C2 . C2 C3 . C2 G2 C3 ', 'G': 'G1 . G1 G2 . G1 D2 G2 ', 'D': 'D2 . D2 D3 . D2 A2 F#2 ',
                'B': 'B1 . B1 B2 . B1 F#2 D#3 '}
    prog = ['E', 'C', 'G', 'D', 'E', 'C', 'G', 'D', 'E', 'C', 'G', 'D', 'E', 'C', 'G', 'B']
    s.line(''.join(bass_bar[c] for c in prog), 'pulse', 0.2, 0, duty=0.5, a=0.004, d=0.12, s=0.4, r=0.04, gate=0.85, lp=6)
    arps = {'E': 'E4 G4 B4 E5 ', 'C': 'E4 G4 C5 E5 ', 'G': 'D4 G4 B4 D5 ', 'D': 'D4 F#4 A4 D5 ', 'B': 'D#4 F#4 B4 D#5 '}
    s.line(''.join(arps[c] * 4 for c in prog), 'bell', 0.06, 0.4, step=s.beat / 4, d=0.12, s=0.0, r=0.1, gate=1.0)
    chords = {'E': ('E3', 'G3', 'B3'), 'C': ('E3', 'G3', 'C4'), 'G': ('D3', 'G3', 'B3'), 'D': ('D3', 'F#3', 'A3'), 'B': ('D#3', 'F#3', 'B3')}
    for i, c in enumerate(prog):
        s.chord(chords[c], i * bar, bar, 0.04, 12)
    s.drums(16, ['k..k..k...k.k...', '....s.......s...' '....s.....s.s...', 'hhh.hhh.hhh.hhH.'])
    s.save('play2', 0.82)


def bars(text, per=8, count=None):
    """Checks a line written bar by bar ('|' between bars): `per` tokens in each bar, `count` bars; returns the tokens."""
    parts = [b.split() for b in text.split('|') if b.strip()]
    for i, b in enumerate(parts):
        assert len(b) == per, 'bar %d has %d tokens instead of %d: %s' % (i + 1, len(b), per, ' '.join(b))
    assert count is None or len(parts) == count, '%d bars instead of %d' % (len(parts), count)
    return ' '.join(' '.join(b) for b in parts) + ' '


def backing(s, prog, bass, arps, chords, bass_opts, arp_opts, chord_vol=0.03, chord_lp=10, arp_per=16, bass_per=8):
    """The parts that follow the chord progression: one bass bar, one arpeggio bar and one pad per chord name."""
    bar = 4 * s.beat
    s.line(bars('|'.join(bass[c] for c in prog), bass_per, len(prog)), **bass_opts)
    s.line(bars('|'.join(arps[c] for c in prog), arp_per, len(prog)), step=bar / arp_per, **arp_opts)
    for i, c in enumerate(prog):
        s.chord(chords[c], i * bar, bar, chord_vol, chord_lp)


def play3():
    """Hammer and Ember: the forge and the lava, a stern march on anvils (C minor, 126 bpm)."""
    s = Song(126, 16)
    bar = 4 * s.beat
    lead_a = bars('C5 . C5 Eb5 G5 - Eb5 C5 | D5 . D5 Eb5 C5 - - . | Ab5 . Ab5 G5 Eb5 - C5 Eb5 | F5 - D5 . Bb4 D5 F5 . |'
                  'C5 . C5 Eb5 G5 - Bb5 G5 | C6 . Bb5 G5 Eb5 - G5 . | Ab5 . F5 Ab5 C6 - Ab5 F5 | G5 - B4 D5 G5 . G5 . ', 8, 8)
    lead_b = bars('G5 C6 . C6 Eb6 . D6 C6 | Bb5 G5 . G5 C6 - - . | Ab5 C6 . C6 Eb6 . C6 Ab5 | Bb5 D6 . D6 F6 - D6 . |'
                  'G6 . Eb6 C6 G6 . Eb6 C6 | F6 . Eb6 D6 C6 - G5 . | Ab5 C6 F6 . Eb6 . C6 Ab5 | G5 B5 D6 . G6 - - . ', 8, 8)
    s.line(lead_a, 'saw', 0.13, -0.1, vib=0.008, s=0.75, lp=5, echo=0.15)
    s.line(lead_a, 'pulse', 0.07, 0.15, duty=0.25, s=0.6, octave=-1)
    s.line(lead_b, 'pulse', 0.14, 0.1, duty=0.25, vib=0.01, s=0.7, echo=0.2, start=8 * bar)
    s.line(lead_b, 'saw', 0.07, -0.25, s=0.6, lp=6, start=8 * bar, octave=-1)
    prog = ['Cm', 'Cm', 'Ab', 'Bb', 'Cm', 'Cm', 'Fm', 'G'] * 2
    bass = {'Cm': 'C2 C2 . C2 C3 . C2 G2', 'Ab': 'Ab1 Ab1 . Ab1 Ab2 . Ab1 Eb2', 'Bb': 'Bb1 Bb1 . Bb1 Bb2 . Bb1 F2',
            'Fm': 'F2 F2 . F2 F3 . F2 C3', 'G': 'G1 G1 . G1 G2 . G2 B2'}
    # the anvil: a bell struck on the off-beats, two notes of the chord
    arps = {'Cm': '. . G5 . . . C6 . . . G5 . . . Eb6 .', 'Ab': '. . Ab5 . . . C6 . . . Ab5 . . . Eb6 .',
            'Bb': '. . Bb5 . . . D6 . . . Bb5 . . . F6 .', 'Fm': '. . Ab5 . . . C6 . . . Ab5 . . . F6 .',
            'G': '. . G5 . . . B5 . . . G5 . . . D6 D6'}
    chords = {'Cm': ('C3', 'Eb3', 'G3'), 'Ab': ('C3', 'Eb3', 'Ab3'), 'Bb': ('D3', 'F3', 'Bb3'), 'Fm': ('C3', 'F3', 'Ab3'), 'G': ('D3', 'G3', 'B3')}
    backing(s, prog, bass, arps, chords,
            dict(kind='pulse', vol=0.22, duty=0.5, a=0.004, d=0.1, s=0.45, r=0.04, gate=0.8, lp=6),
            dict(kind='bell', vol=0.07, pan=0.4, d=0.1, s=0.0, r=0.12, gate=1.0), 0.04, 12)
    s.drums(16, ['k...k.k.k...k.kk', '....s.......s...' '....s.......ssss', 'h.hhh.hhh.hhh.H.'])
    s.save('play3', 0.82)


def play4():
    """Aurora Drift: night under the northern lights, dreamy but walking (F sharp minor, 110 bpm)."""
    s = Song(110, 16)
    bar = 4 * s.beat
    lead_a = bars('C#5 - F#5 - A5 G#5 F#5 - | A5 - - F#5 D5 - F#5 . | E5 - A5 - C#6 B5 A5 - | B5 - - G#5 E5 - - . |'
                  'C#5 - F#5 - A5 G#5 A5 C#6 | D6 - C#6 A5 F#5 - A5 . | B5 - D6 - F#6 E6 D6 B5 | C#6 - - G#5 F5 - G#5 . ', 8, 8)
    lead_b = bars('F#6 . C#6 F#6 - E6 C#6 A5 | A5 . D6 F#6 - E6 D6 . | E6 . C#6 E6 - A6 G#6 E6 | G#6 - E6 B5 - E6 G#6 . |'
                  'A6 . F#6 C#6 - F#6 A6 G#6 | F#6 . D6 A5 - D6 F#6 . | D6 . B5 F#5 B5 D6 F#6 D6 | C#6 - - F5 G#5 . C#6 . ', 8, 8)
    s.line(lead_a, 'tri', 0.22, -0.1, vib=0.012, a=0.02, s=0.85, r=0.12, echo=0.35)
    s.line(lead_b, 'sine', 0.2, 0.1, vib=0.012, a=0.01, s=0.8, r=0.12, echo=0.35, start=8 * bar)
    s.line(lead_b, 'pulse', 0.05, -0.3, duty=0.125, s=0.5, start=8 * bar, octave=-1)
    prog = ['F#m', 'D', 'A', 'E', 'F#m', 'D', 'Bm', 'C#'] * 2
    bass = {'F#m': 'F#2 - . F#2 . F#3 C#3 -', 'D': 'D2 - . D2 . D3 A2 -', 'A': 'A2 - . A2 . A3 E3 -', 'E': 'E2 - . E2 . E3 B2 -',
            'Bm': 'B1 - . B1 . B2 F#2 -', 'C#': 'C#2 - . C#2 . C#3 G#2 F2'}
    arps = {'F#m': 'F#4 A4 C#5 F#5 C#5 A4 ' * 2 + 'F#4 A4 C#5 F#5', 'D': 'F#4 A4 D5 F#5 D5 A4 ' * 2 + 'F#4 A4 D5 F#5',
            'A': 'E4 A4 C#5 E5 C#5 A4 ' * 2 + 'E4 A4 C#5 E5', 'E': 'E4 G#4 B4 E5 B4 G#4 ' * 2 + 'E4 G#4 B4 E5',
            'Bm': 'F#4 B4 D5 F#5 D5 B4 ' * 2 + 'F#4 B4 D5 F#5', 'C#': 'F4 G#4 C#5 F5 C#5 G#4 ' * 2 + 'F4 G#4 C#5 F5'}
    chords = {'F#m': ('F#3', 'A3', 'C#4'), 'D': ('F#3', 'A3', 'D4'), 'A': ('E3', 'A3', 'C#4'), 'E': ('E3', 'G#3', 'B3'),
              'Bm': ('F#3', 'B3', 'D4'), 'C#': ('F3', 'G#3', 'C#4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='tri', vol=0.32, a=0.01, d=0.15, s=0.7, r=0.1, gate=0.9),
            dict(kind='bell', vol=0.05, pan=0.4, d=0.14, s=0.0, r=0.12, gate=1.0), 0.05, 14)
    s.drums(16, ['k.....k...k.....' 'k.....k...k...k.', '....s.......s...', 'h.hhh.hhh.hhh.H.'])
    s.save('play4', 0.82)


def play5():
    """Dune Lanterns: the desert at sunset, a caravan tune (A Phrygian dominant, 118 bpm)."""
    s = Song(118, 16)
    bar = 4 * s.beat
    lead_a = bars('A4 Bb4 C#5 D5 E5 - D5 C#5 | Bb4 - D5 . F5 E5 D5 . | E5 F5 E5 C#5 Bb4 C#5 A4 - | G4 - Bb4 D5 . Bb4 G4 . |'
                  'A4 Bb4 C#5 D5 E5 - G5 F5 | F5 - E5 D5 Bb4 - D5 . | D5 E5 F5 A5 . G5 F5 E5 | C#5 - Bb4 C#5 A4 - - . ', 8, 8)
    lead_b = bars('E5 A5 . A5 Bb5 A5 G5 A5 | Bb5 - A5 F5 D5 F5 Bb5 . | C#6 . Bb5 A5 G5 F5 E5 - | D5 G5 . Bb5 D6 - Bb5 G5 |'
                  'A5 Bb5 C#6 D6 E6 - D6 C#6 | D6 - Bb5 F5 Bb5 D6 F6 . | F6 E6 D6 . A5 D6 F6 E6 | E6 - C#6 Bb5 A5 - - . ', 8, 8)
    s.line(lead_a, 'pulse', 0.15, -0.1, duty=0.125, vib=0.02, s=0.75, echo=0.2, octave=1)
    s.line(lead_a, 'tri', 0.12, 0.2, s=0.7)
    s.line(lead_b, 'pulse', 0.15, 0.1, duty=0.25, vib=0.02, s=0.75, echo=0.2, start=8 * bar)
    s.line(lead_b, 'tri', 0.1, -0.3, s=0.7, start=8 * bar, octave=-1)
    prog = ['A', 'Bb', 'A', 'Gm', 'A', 'Bb', 'Dm', 'A'] * 2
    bass = {'A': 'A1 . A2 A1 . E2 A2 E2', 'Bb': 'Bb1 . Bb2 Bb1 . F2 Bb2 F2', 'Gm': 'G1 . G2 G1 . D2 G2 Bb2', 'Dm': 'D2 . D3 D2 . A2 D3 F2'}
    # a plucked drone figure: root and fifth, with the flat second as a grace note
    arps = {'A': 'A3 . E4 A4 . E4 Bb4 A4 A3 . E4 A4 . C#5 A4 E4', 'Bb': 'Bb3 . F4 Bb4 . F4 D5 Bb4 Bb3 . F4 Bb4 . D5 Bb4 F4',
            'Gm': 'G3 . D4 G4 . D4 Bb4 G4 G3 . D4 G4 . Bb4 G4 D4', 'Dm': 'D4 . A4 D5 . A4 F5 D5 D4 . A4 D5 . F5 D5 A4'}
    chords = {'A': ('A3', 'C#4', 'E4'), 'Bb': ('Bb3', 'D4', 'F4'), 'Gm': ('G3', 'Bb3', 'D4'), 'Dm': ('A3', 'D4', 'F4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='tri', vol=0.33, a=0.004, d=0.1, s=0.5, r=0.05, gate=0.8),
            dict(kind='saw', vol=0.045, pan=0.4, d=0.06, s=0.15, r=0.04, gate=0.8, lp=3), 0.03, 12)
    s.drums(16, ['k..k....k.k.....', '......s.......s.' '......s.....s.s.', 'h.h.hh..h.h.hh.H'])
    s.save('play5', 0.82)


def play6():
    """Glacier Chimes: ice and frozen lakes, bright bells over a skipping bass (E major, 124 bpm)."""
    s = Song(124, 16)
    bar = 4 * s.beat
    lead_a = bars('B5 . E6 . G#6 - F#6 E6 | D#6 . F#6 . B5 - - . | C#6 . E6 . G#6 - E6 C#6 | E6 - C#6 A5 E6 - - . |'
                  'B5 . E6 . G#6 - B6 G#6 | F#6 . D#6 . B5 D#6 F#6 . | A6 . G#6 F#6 E6 - C#6 E6 | F#6 - D#6 B5 F#6 . B5 . ', 8, 8)
    lead_b = bars('G#6 E6 B5 E6 G#6 E6 B6 - | F#6 D#6 B5 D#6 F#6 - B5 . | E6 C#6 G#5 C#6 E6 C#6 G#6 - | A6 - G#6 F#6 E6 - C#6 . |'
                  'G#6 E6 B5 E6 G#6 B6 E7 - | D#7 - B6 F#6 B6 - F#6 . | E6 F#6 G#6 A6 . G#6 F#6 E6 | F#6 - - D#6 B5 D#6 F#6 . ', 8, 8)
    s.line(lead_a, 'bell', 0.2, -0.1, d=0.35, s=0.15, r=0.3, gate=1.0, echo=0.3)
    s.line(lead_a, 'pulse', 0.06, 0.2, duty=0.25, s=0.6, octave=-1)
    s.line(lead_b, 'bell', 0.19, 0.1, d=0.35, s=0.15, r=0.3, gate=1.0, echo=0.3, start=8 * bar)
    s.line(lead_b, 'tri', 0.12, -0.25, vib=0.01, s=0.7, start=8 * bar, octave=-1)
    prog = ['E', 'B', 'C#m', 'A', 'E', 'B', 'A', 'B'] * 2
    bass = {'E': 'E2 E3 . E3 E2 E3 B2 E3', 'B': 'B1 B2 . B2 B1 B2 F#2 B2', 'C#m': 'C#2 C#3 . C#3 C#2 C#3 G#2 C#3', 'A': 'A1 A2 . A2 A1 A2 E2 A2'}
    arps = {'E': 'E5 G#5 B5 E6 ' * 4, 'B': 'D#5 F#5 B5 D#6 ' * 4, 'C#m': 'E5 G#5 C#6 E6 ' * 4, 'A': 'E5 A5 C#6 E6 ' * 4}
    chords = {'E': ('E3', 'G#3', 'B3'), 'B': ('D#3', 'F#3', 'B3'), 'C#m': ('E3', 'G#3', 'C#4'), 'A': ('E3', 'A3', 'C#4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='tri', vol=0.33, a=0.004, d=0.1, s=0.5, r=0.04, gate=0.75),
            dict(kind='sine', vol=0.05, pan=0.4, d=0.06, s=0.1, r=0.06, gate=0.9), 0.035, 8)
    s.drums(16, ['k.......k.....k.', '....s.......s...' '....s.......s.s.', 'hhh.hhh.hhh.hhH.'])
    s.save('play6', 0.82)


def play7():
    """Glowvine Groove: the bioluminescent jungle, off-beat and bouncy (G minor pentatonic, 112 bpm)."""
    s = Song(112, 16)
    bar = 4 * s.beat
    lead_a = bars('G4 . Bb4 C5 . D5 . F5 | . D5 . C5 Bb4 - G4 . | Bb4 . D5 F5 . G5 . F5 | . D5 C5 . F4 - C5 . |'
                  'G4 . Bb4 C5 . D5 . G5 | . F5 . D5 F5 - D5 . | C5 . G5 F5 . C5 . Bb4 | . D5 . F5 D5 - - . ', 8, 8)
    lead_b = bars('D5 G5 . G5 . Bb5 G5 F5 | . G5 D5 . F5 D5 C5 . | D5 F5 . Bb5 . C6 Bb5 F5 | . C6 . Bb5 F5 - C5 . |'
                  'D5 G5 . G5 . Bb5 C6 D6 | . D6 C6 . Bb5 G5 Bb5 . | C6 . Bb5 G5 . F5 G5 Bb5 | . D6 . C6 D6 - - . ', 8, 8)
    s.line(lead_a, 'pulse', 0.16, -0.1, duty=0.5, vib=0.01, d=0.1, s=0.6, gate=0.8, echo=0.2, octave=1)
    s.line(lead_a, 'tri', 0.1, 0.25, s=0.6, gate=0.8)
    s.line(lead_b, 'pulse', 0.15, 0.1, duty=0.25, vib=0.01, d=0.1, s=0.6, gate=0.8, echo=0.2, start=8 * bar)
    s.line(lead_b, 'tri', 0.1, -0.25, s=0.6, gate=0.8, start=8 * bar, octave=-1)
    prog = ['Gm', 'Gm', 'Bb', 'F', 'Gm', 'Gm', 'Cm', 'Dm'] * 2
    bass = {'Gm': 'G2 . . G2 . Bb2 C3 D3', 'Bb': 'Bb1 . . Bb1 . D2 F2 Bb2', 'F': 'F2 . . F2 . A2 C3 F2', 'Cm': 'C2 . . C2 . Eb2 G2 C3',
            'Dm': 'D2 . . D2 . F2 A2 D3'}
    # a wooden mallet pattern that answers the bass between its notes
    arps = {'Gm': '. . D5 . G5 . . D5 . . Bb5 . G5 . D5 .', 'Bb': '. . D5 . F5 . . D5 . . Bb5 . F5 . D5 .',
            'F': '. . C5 . F5 . . C5 . . A5 . F5 . C5 .', 'Cm': '. . Eb5 . G5 . . Eb5 . . C6 . G5 . Eb5 .',
            'Dm': '. . D5 . F5 . . D5 . . A5 . F5 . A5 D6'}
    chords = {'Gm': ('G3', 'Bb3', 'D4'), 'Bb': ('F3', 'Bb3', 'D4'), 'F': ('F3', 'A3', 'C4'), 'Cm': ('G3', 'C4', 'Eb4'), 'Dm': ('F3', 'A3', 'D4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='pulse', vol=0.22, duty=0.5, a=0.004, d=0.1, s=0.4, r=0.04, gate=0.7, lp=6),
            dict(kind='sine', vol=0.11, pan=0.4, d=0.07, s=0.0, r=0.08, gate=1.0), 0.03, 10)
    s.drums(16, ['k..k..k...k..k..', '....s..s.s..s...' '....s..s.s..s.ss', 'h.hh.hh.h.hh.hH.'])
    s.save('play7', 0.82)


def play8():
    """Skyward Bells: the temple in the clouds, the last world, a happy fanfare at a gallop (B flat major, 138 bpm)."""
    s = Song(138, 16)
    bar = 4 * s.beat
    lead_a = bars('Bb4 . D5 F5 - F5 G5 F5 | A5 - F5 . C5 F5 A5 . | Bb5 . A5 G5 - D5 G5 Bb5 | G5 - Eb5 . Bb4 Eb5 G5 . |'
                  'Bb4 . D5 F5 - F5 Bb5 D6 | C6 - A5 . F5 A5 C6 . | Bb5 . G5 Eb5 G5 Bb5 Eb6 - | D6 C6 A5 . F5 A5 C6 . ', 8, 8)
    lead_b = bars('D6 . D6 Bb5 - G5 Bb5 D6 | Eb6 . Eb6 Bb5 - G5 Bb5 . | F6 . D6 Bb5 - D6 F6 D6 | C6 - A5 F5 A5 C6 F6 . |'
                  'G6 . Eb6 Bb5 Eb6 G6 F6 Eb6 | F6 . C6 A5 C6 F6 Eb6 C6 | D6 - Bb5 F5 Bb5 D6 F6 Bb6 | A6 - F6 C6 F6 . F5 . ', 8, 8)
    s.line(lead_a, 'pulse', 0.15, -0.1, duty=0.25, vib=0.012, s=0.75, echo=0.2, octave=1)
    s.line(lead_a, 'saw', 0.07, 0.2, s=0.7, lp=5)
    s.line(lead_b, 'pulse', 0.15, 0.1, duty=0.5, vib=0.012, s=0.75, echo=0.2, start=8 * bar)
    s.line(lead_b, 'saw', 0.07, -0.25, s=0.7, lp=5, start=8 * bar, octave=-1)
    s.line(lead_b, 'bell', 0.07, 0.3, d=0.2, s=0.0, r=0.2, gate=1.0, start=8 * bar)
    prog = ['Bb', 'F', 'Gm', 'Eb', 'Bb', 'F', 'Eb', 'F', 'Gm', 'Eb', 'Bb', 'F', 'Eb', 'F', 'Bb', 'F']
    bass = {'Bb': 'Bb1 Bb2 Bb2 Bb1 Bb2 Bb2 F2 Bb2', 'F': 'F2 F3 F3 F2 F3 F3 C3 F3', 'Gm': 'G1 G2 G2 G1 G2 G2 D2 G2', 'Eb': 'Eb2 Eb3 Eb3 Eb2 Eb3 Eb3 Bb2 Eb3'}
    arps = {'Bb': 'Bb4 D5 F5 Bb5 F5 D5 ' * 2 + 'Bb4 D5 F5 Bb5', 'F': 'A4 C5 F5 A5 F5 C5 ' * 2 + 'A4 C5 F5 A5',
            'Gm': 'Bb4 D5 G5 Bb5 G5 D5 ' * 2 + 'Bb4 D5 G5 Bb5', 'Eb': 'Bb4 Eb5 G5 Bb5 G5 Eb5 ' * 2 + 'Bb4 Eb5 G5 Bb5'}
    chords = {'Bb': ('D4', 'F4', 'Bb4'), 'F': ('C4', 'F4', 'A4'), 'Gm': ('D4', 'G4', 'Bb4'), 'Eb': ('Eb4', 'G4', 'Bb4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='tri', vol=0.33, a=0.004, d=0.08, s=0.5, r=0.03, gate=0.75),
            dict(kind='pulse', vol=0.045, pan=0.4, duty=0.125, d=0.05, s=0.3, r=0.03, gate=0.7), 0.03, 10)
    s.drums(16, ['k..kk..kk..kk.k.', '....s.......s...' '....s.......s.ss', 'h.hhh.hhh.hhh.H.'])
    s.save('play8', 0.82)


def play9():
    """Embers in the Leaves: an autumn forest at dusk, a warm folk shuffle (B minor, 104 bpm, 12 triplet steps a bar)."""
    s = Song(104, 16)
    bar = 4 * s.beat
    t3 = s.beat / 3
    lead_a = bars('F#5 - D5 F#5 - B5 A5 - F#5 D5 - - | D6 - B5 G5 - B5 A5 - G5 B5 - - |'
                  'A5 - F#5 A5 - D6 F#6 - E6 D6 - - | C#6 - E6 C#6 - A5 B5 - C#6 E6 - . |'
                  'F#5 - D5 F#5 - B5 D6 - B5 F#6 - - | E6 - D6 B5 - G5 B5 - D6 E6 - . |'
                  'G5 - B5 E6 - D6 B5 - G5 A5 - B5 | A#5 - C#6 F#6 - E6 C#6 - A#5 F#5 - -', 12, 8)
    lead_b = bars('B5 - D6 F#6 - D6 B5 - D6 F#6 - B6 | G6 - D6 B5 - D6 G6 - F#6 D6 - - |'
                  'F#6 - A6 F#6 - D6 A5 - D6 F#6 - A6 | E6 - C#6 A5 - C#6 E6 - A6 E6 - - |'
                  'D6 - F#6 B6 - A6 F#6 - D6 B5 - D6 | B5 - D6 G6 - F#6 E6 - D6 B5 - G5 |'
                  'E6 - G6 B6 - A6 G6 - E6 B5 - G5 | C#6 - F#6 A#5 - C#6 F#5 - A#5 F#6 - -', 12, 8)
    s.line(lead_a, 'tri', 0.22, -0.1, step=t3, vib=0.012, a=0.02, s=0.85, r=0.1, echo=0.25)
    s.line(lead_a, 'pulse', 0.04, 0.2, step=t3, duty=0.25, s=0.5, lp=4, octave=-1)
    s.line(lead_b, 'pulse', 0.13, 0.1, step=t3, duty=0.25, vib=0.012, s=0.7, lp=3, echo=0.2, start=8 * bar)
    s.line(lead_b, 'tri', 0.12, -0.25, step=t3, s=0.7, start=8 * bar, octave=-1)
    prog = ['Bm', 'G', 'D', 'A', 'Bm', 'G', 'Em', 'F#'] * 2
    bass = {'Bm': 'B2 - . F#3 - . B2 - . F#3 - .', 'G': 'G2 - . D3 - . G2 - . D3 - .', 'D': 'D2 - . A2 - . D2 - . A2 - .',
            'A': 'A1 - . E2 - . A1 - . E2 - .', 'Em': 'E2 - . B2 - . E2 - . B2 - .', 'F#': 'F#2 - . C#3 - . F#2 - . C#3 - .'}
    # a picked guitar: the three notes of the chord, round and round
    arps = {'Bm': 'D4 F#4 B4 ' * 4, 'G': 'D4 G4 B4 ' * 4, 'D': 'D4 F#4 A4 ' * 4, 'A': 'E4 A4 C#5 ' * 4, 'Em': 'E4 G4 B4 ' * 4,
            'F#': 'C#4 F#4 A#4 ' * 4}
    chords = {'Bm': ('B3', 'D4', 'F#4'), 'G': ('B3', 'D4', 'G4'), 'D': ('A3', 'D4', 'F#4'), 'A': ('A3', 'C#4', 'E4'),
              'Em': ('G3', 'B3', 'E4'), 'F#': ('A#3', 'C#4', 'F#4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='tri', vol=0.33, step=t3, a=0.01, d=0.12, s=0.6, r=0.06, gate=0.85),
            dict(kind='tri', vol=0.09, pan=0.4, d=0.07, s=0.05, r=0.06, gate=0.9), 0.04, 12, 12, 12)
    for b in range(16):                       # shuffle: kick on 1 and 3, snare on 2 and 4, swung hats
        for i in range(12):
            t = (b * 12 + i) * t3
            if i in (0, 6): s.kick(t)
            if i in (3, 9): s.snare(t, 0.2)
            if i in (0, 2, 3, 5, 6, 8, 9, 11): s.hat(t, 0.06 if i in (2, 5, 8, 11) else 0.08)
    s.save('play9', 0.82)


def play10():
    """Lagoon Lights: a coral reef, crystal bells over a soft rolling groove (C Lydian, 100 bpm)."""
    s = Song(100, 16)
    bar = 4 * s.beat
    lead_a = bars('E5 . G5 B5 . G5 E5 . | F#5 . A5 D6 . A5 F#5 . | E5 . A5 C6 . B5 A5 . | D5 . G5 B5 - - . . |'
                  'G5 . C6 E6 . D6 C6 . | A5 . D6 F#6 . E6 D6 . | C6 . A5 F5 . A5 C6 . | D6 - B5 . G5 B5 D6 . ', 8, 8)
    lead_b = bars('E6 G6 E6 C6 G5 C6 E6 . | F#6 A6 F#6 D6 A5 D6 F#6 . | E6 C6 A5 C6 E6 A6 G6 E6 | D6 B5 G5 B5 D6 G6 - . |'
                  'C7 . G6 E6 . G6 C7 . | A6 . F#6 D6 . F#6 A6 . | A6 . F6 C6 . F6 A6 C7 | B6 - G6 D6 B5 D6 G6 . ', 8, 8)
    s.line(lead_a, 'bell', 0.2, -0.15, d=0.4, s=0.1, r=0.4, gate=1.0, echo=0.4)
    s.line(lead_a, 'sine', 0.07, 0.25, a=0.02, s=0.7, r=0.15, octave=-1)
    s.line(lead_b, 'bell', 0.18, 0.15, d=0.35, s=0.1, r=0.35, gate=1.0, echo=0.4, start=8 * bar)
    s.line(lead_b, 'tri', 0.1, -0.3, vib=0.01, a=0.02, s=0.7, r=0.12, start=8 * bar, octave=-1)
    prog = ['C', 'D', 'Am', 'G', 'C', 'D', 'F', 'G'] * 2
    bass = {'C': 'C2 - . G2 . C3 - G2', 'D': 'D2 - . A2 . D3 - A2', 'Am': 'A1 - . E2 . A2 - E2', 'G': 'G1 - . D2 . G2 - D2',
            'F': 'F2 - . C3 . F3 - C3'}
    arps = {'C': 'G4 C5 E5 G5 B5 G5 E5 C5 ' * 2, 'D': 'A4 D5 F#5 A5 C6 A5 F#5 D5 ' * 2, 'Am': 'A4 C5 E5 A5 C6 A5 E5 C5 ' * 2,
            'G': 'G4 B4 D5 G5 B5 G5 D5 B4 ' * 2, 'F': 'A4 C5 F5 A5 C6 A5 F5 C5 ' * 2}
    chords = {'C': ('E3', 'G3', 'B3'), 'D': ('F#3', 'A3', 'D4'), 'Am': ('E3', 'A3', 'C4'), 'G': ('D3', 'G3', 'B3'), 'F': ('A3', 'C4', 'F4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='tri', vol=0.34, a=0.02, d=0.15, s=0.7, r=0.1, gate=0.9),
            dict(kind='sine', vol=0.06, pan=0.4, d=0.1, s=0.05, r=0.1, gate=1.0), 0.05, 14)
    s.drums(16, ['k.....k...k.....', '....s.......s...', 'h.h.h.h.h.h.h.hH'])
    s.save('play10', 0.82)


def play11():
    """Brass Cogwheels: a clockwork workshop, precise and staccato, ticking along (D major, 132 bpm)."""
    s = Song(132, 16)
    bar = 4 * s.beat
    lead_a = bars('D5 F#5 A5 F#5 D5 F#5 A5 . | B5 A5 F#5 D5 F#5 B5 D6 . | G5 B5 D6 B5 G5 B5 D6 . | C#6 E6 A5 C#6 E6 . A5 . |'
                  'D6 . A5 D6 . F#6 E6 D6 | D6 . B5 D6 . F#6 D6 B5 | B5 D6 G6 . F#6 E6 D6 B5 | A5 C#6 E6 A6 . E6 C#6 . ', 8, 8)
    lead_b = bars('F#6 E6 D6 E6 F#6 A6 F#6 D6 | C#6 F#6 A6 F#6 C#6 F#6 A6 . | B5 E6 G6 E6 B5 E6 G6 . | E6 A6 C#7 A6 E6 C#6 E6 . |'
                  'D6 F#6 A6 D7 A6 F#6 D6 . | B5 D6 F#6 B6 F#6 D6 B5 . | G5 B5 D6 G6 B6 G6 D6 B5 | C#6 E6 A6 E6 C#6 E6 A5 . ', 8, 8)
    s.line(lead_a, 'pulse', 0.14, -0.1, duty=0.25, s=0.6, gate=0.55, echo=0.15)
    s.line(lead_a, 'bell', 0.07, 0.25, d=0.1, s=0.0, r=0.1, gate=1.0, octave=1)
    s.line(lead_b, 'pulse', 0.14, 0.1, duty=0.125, s=0.6, gate=0.55, echo=0.15, start=8 * bar)
    s.line(lead_b, 'pulse', 0.06, -0.3, duty=0.5, s=0.5, gate=0.5, start=8 * bar, octave=-1)
    prog = ['D', 'Bm', 'G', 'A', 'D', 'Bm', 'G', 'A', 'D', 'F#m', 'Em', 'A', 'D', 'Bm', 'G', 'A']
    bass = {'D': 'D2 . D3 D2 . D3 A2 .', 'Bm': 'B1 . B2 B1 . B2 F#2 .', 'G': 'G1 . G2 G1 . G2 D2 .', 'A': 'A1 . A2 A1 . A2 E2 .',
            'F#m': 'F#1 . F#2 F#1 . F#2 C#2 .', 'Em': 'E2 . E3 E2 . E3 B2 .'}
    # the escapement: two notes ticking and tocking
    arps = {'D': 'A5 D6 ' * 8, 'Bm': 'B5 D6 ' * 8, 'G': 'G5 D6 ' * 8, 'A': 'A5 E6 ' * 8, 'F#m': 'A5 C#6 ' * 8, 'Em': 'G5 B5 ' * 8}
    chords = {'D': ('D4', 'F#4', 'A4'), 'Bm': ('D4', 'F#4', 'B4'), 'G': ('D4', 'G4', 'B4'), 'A': ('C#4', 'E4', 'A4'),
              'F#m': ('C#4', 'F#4', 'A4'), 'Em': ('E4', 'G4', 'B4')}
    backing(s, prog, bass, arps, chords,
            dict(kind='pulse', vol=0.22, duty=0.5, a=0.003, d=0.06, s=0.4, r=0.03, gate=0.6, lp=6),
            dict(kind='pulse', vol=0.035, pan=0.4, duty=0.125, d=0.03, s=0.2, r=0.02, gate=0.5), 0.025, 8)
    s.drums(16, ['k.......k.....k.', '....s.......s...' '....s.......s.ss', 'h.h.h.h.h.h.hhhH'])
    s.save('play11', 0.82)


# --- sound effects ------------------------------------------------------------------------------------------
def sfx(name, sig, gain=0.8):
    sig = np.asarray(sig, dtype=float)
    sig = sig / max(1e-9, np.max(np.abs(sig))) * gain
    k = min(len(sig), int(0.004 * SR))
    sig[-k:] *= np.linspace(1, 0, k)
    path = os.path.join(ROOT, 'sfx', name + '.wav')
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with wave.open(path, 'wb') as w:
        w.setnchannels(1); w.setsampwidth(2); w.setframerate(SR)
        w.writeframes((sig * 32767).astype('<i2').tobytes())


def tone(kind, f0, f1, dur, decay=8.0, duty=0.5):
    n = int(dur * SR)
    t = np.arange(n) / SR
    f = f0 * (f1 / f0) ** (t / dur)
    ph = np.cumsum(f) / SR
    if kind == 'pulse':
        x = np.where((ph % 1) < duty, 1.0, -1.0)
    elif kind == 'tri':
        x = 2 * np.abs(2 * (ph % 1) - 1) - 1
    else:
        x = np.sin(2 * np.pi * ph)
    return x * np.exp(-t * decay)


def seq(notes, kind='bell', step=0.07, tail=0.35, decay=9.0):
    n = int((len(notes) * step + tail) * SR)
    out = np.zeros(n)
    for i, name in enumerate(notes):
        m = int(tail * SR)
        f = hz(name)
        x = osc(kind, f, m) * np.exp(-np.arange(m) / SR * decay)
        out[int(i * step * SR):int(i * step * SR) + m] += x[:n - int(i * step * SR)]
    return out


def pad(x, n):
    return np.concatenate([x, np.zeros(max(0, n - len(x)))])


def mix(*parts):
    n = max(len(p) for p in parts)
    return sum(pad(p, n) for p in parts)


def sounds():
    noise = lambda dur, decay: rng.uniform(-1, 1, int(dur * SR)) * np.exp(-np.arange(int(dur * SR)) / SR * decay)
    sfx('menu_move', tone('tri', 700, 760, 0.05, 30), 0.5)
    sfx('menu_confirm', seq(['A5', 'E6'], 'bell', 0.05, 0.25, 12), 0.6)
    sfx('select', mix(tone('pulse', 980, 980, 0.04, 50, 0.25), tone('sine', 1960, 1960, 0.03, 60) * 0.5), 0.45)
    sfx('assign', mix(tone('sine', 520, 1300, 0.09, 14), seq(['E6', 'A6'], 'bell', 0.045, 0.2, 16) * 0.6), 0.6)
    sfx('deny', mix(tone('pulse', 190, 150, 0.12, 9, 0.3), tone('pulse', 201, 158, 0.12, 9, 0.3)), 0.4)
    sfx('spawn', mix(tone('sine', 300, 900, 0.09, 12), tone('tri', 600, 1500, 0.07, 18) * 0.4), 0.5)
    sfx('saved', seq(['A5', 'C#6', 'E6', 'A6'], 'bell', 0.05, 0.45, 8), 0.6)
    boom = mix(lowpass(noise(0.7, 6), 9) * 1.3, tone('sine', 130, 34, 0.55, 5) * 1.2, noise(0.08, 40) * 0.8)
    sfx('boom', np.tanh(boom * 2.2), 0.95)
    sfx('fuse', mix(*[pad(np.concatenate([np.zeros(int(i * 0.09 * SR)), tone('pulse', 1200 - i * 90, 1100 - i * 90, 0.06, 20, 0.25)]), 0) for i in range(3)]), 0.5)
    sfx('splat', mix(lowpass(noise(0.16, 22), 6), tone('sine', 240, 60, 0.14, 16)), 0.7)
    t = np.arange(int(0.4 * SR)) / SR
    sfx('drown', mix(np.sin(2 * np.pi * (500 - 700 * t) * t * (1 + 0.3 * np.sin(2 * np.pi * 22 * t))) * np.exp(-t * 7), lowpass(noise(0.3, 10), 12) * 0.9), 0.65)
    sfx('brick', mix(tone('tri', 430, 380, 0.05, 40), lowpass(noise(0.03, 60), 4) * 0.5), 0.5)
    sfx('lastbrick', seq(['E6', 'E6'], 'bell', 0.06, 0.15, 20), 0.55)
    sfx('clink', mix(tone('sine', 2400, 2300, 0.14, 22), tone('sine', 3530, 3500, 0.1, 30) * 0.6, noise(0.01, 200) * 0.5), 0.5)
    sfx('win', mix(seq(['C5', 'E5', 'G5', 'C6', 'E6', 'G6', 'C7'], 'bell', 0.085, 0.9, 4), pad(np.concatenate([np.zeros(int(0.51 * SR)), osc('tri', hz('C4'), int(0.8 * SR)) * np.exp(-np.arange(int(0.8 * SR)) / SR * 3) * 0.7, ]), 0)), 0.75)
    sfx('lose', seq(['G4', 'F4', 'D#4', 'C4'], 'tri', 0.2, 0.6, 4), 0.6)


if __name__ == '__main__':
    title(); play(); play2(); play3(); play4(); play5(); play6(); play7(); play8(); play9(); play10(); play11(); sounds()
    print('music and sounds written')
