#!/usr/bin/env python3 """ make-sounds.py - sintetiza os 17 efeitos sonoros do Domino Chain para o fredygames.com. O pacote do jogo traz 17 efeitos em .ogg sem informacao de autoria nem de licenca, e o historico do projeto (que comeca com o jogo lendo os arquivos do original comercial de 1992) nao diz de onde vieram. Por isso o site nao os usa: este script gera outros, do zero, com os mesmos nomes de arquivo, que o jogo carrega sem mudanca. Sao sons simples (senoides, ruido filtrado, envelopes), todos criados pelo FredyGames; GNU GPL versao 3 ou posterior, como o jogo. A saida e deterministica (o ruido vem de um gerador com semente fixa). python3 -I make-sounds.py # escreve 17 arquivos .wav (44,1 kHz, 16 bits, estereo) O build-domino-chain.sh codifica cada .wav em Ogg Vorbis com o wav2ogg (qualidade 0.3). """ import math, random, struct, sys, wave, os RATE = 44100 def n(sec): return int(sec * RATE) def env_exp(i, total, decay): return math.exp(-decay * i / total) def sine(freq_fn, sec, amp_fn): out, ph = [], 0.0 for i in range(n(sec)): f = freq_fn(i / RATE) ph += 2 * math.pi * f / RATE out.append(math.sin(ph) * amp_fn(i / RATE)) return out def noise(sec, seed, amp_fn, lowpass=1.0, highpass=False): rng = random.Random(seed) out, y = [], 0.0 for i in range(n(sec)): x = rng.uniform(-1, 1) y += lowpass * (x - y) out.append((x - y if highpass else y) * amp_fn(i / RATE)) return out def mix(*parts): length = max(len(p) for p in parts) out = [0.0] * length for p in parts: for i, v in enumerate(p): out[i] += v return out def delay(sig, sec): return [0.0] * n(sec) + sig def decay(rate): return lambda t: math.exp(-rate * t) def ping(freq, sec, rate, amp=1.0): return sine(lambda t: freq, sec, lambda t: amp * math.exp(-rate * t)) def thump(freq0, freq1, sec, rate, amp=1.0): return sine(lambda t: freq1 + (freq0 - freq1) * math.exp(-t * 25), sec, lambda t: amp * math.exp(-rate * t)) def click(seed, amp=1.0, sec=0.02): return noise(sec, seed, lambda t: amp * math.exp(-t * 300), 0.6, True) def sounds(): s = {} s['01_StandardFalling'] = mix(thump(220, 90, 0.3, 14), click(1, 0.5)) s['02_StopperHit'] = mix(ping(820, 0.4, 9, 0.5), ping(1290, 0.4, 11, 0.35), ping(2110, 0.3, 14, 0.2), click(2, 0.4)) s['03_Splitter'] = mix(click(3, 0.9, 0.05), delay(click(4, 0.7, 0.04), 0.06), delay(ping(1500, 0.15, 25, 0.3), 0.02)) s['04_Exploder'] = mix(noise(0.9, 5, decay(4.5), 0.12), thump(110, 40, 0.9, 4.0, 0.9)) s['05_Delay'] = mix(*[delay(mix(ping(1100, 0.06, 60, 0.6), click(6 + k, 0.3)), 0.12 * k) for k in range(4)], delay(thump(160, 80, 0.25, 12, 0.7), 0.55)) s['06_TumblerFalling'] = mix( sine(lambda t: 180 + 40 * math.sin(2 * math.pi * 9 * t), 0.7, lambda t: 0.5 * math.exp(-3 * t) * (0.6 + 0.4 * math.sin(2 * math.pi * 12 * t))), delay(thump(200, 90, 0.2, 14, 0.8), 0.25), delay(thump(200, 90, 0.2, 14, 0.8), 0.5)) s['07_BridgerFalling'] = mix(noise(0.35, 7, lambda t: 0.5 * math.sin(math.pi * t / 0.35), 0.25), delay(thump(150, 70, 0.25, 12, 0.7), 0.3)) s['09_TriggerFalling'] = mix(thump(200, 85, 0.3, 12, 0.8), delay(ping(1760, 0.5, 7, 0.35), 0.08), delay(ping(2217, 0.5, 8, 0.25), 0.12)) s['0A_Ascender'] = sine(lambda t: 200 + 700 * (t / 0.6) ** 1.5, 0.65, lambda t: 0.5 * math.sin(math.pi * min(t / 0.65, 1))) s['0B_Falling'] = sine(lambda t: 950 - 750 * (t / 0.8), 0.8, lambda t: 0.45 * math.sin(math.pi * min(t / 0.8, 1)) ** 0.5) s['0C_Landing'] = mix(thump(130, 45, 0.3, 11, 1.0), noise(0.15, 8, decay(25), 0.2, False)) s['0D_PickUpDomino'] = sine(lambda t: 380 + 520 * min(t / 0.09, 1), 0.14, lambda t: 0.7 * math.exp(-18 * t)) s['0E_Tapping'] = mix(*[delay(mix(ping(950, 0.08, 45, 0.8), click(9 + k, 0.3)), 0.16 * k) for k in range(3)]) s['0F_Schrugging'] = mix(sine(lambda t: 330, 0.18, lambda t: 0.5 * math.sin(math.pi * t / 0.18)), delay(sine(lambda t: 250, 0.26, lambda t: 0.5 * math.sin(math.pi * t / 0.26)), 0.2)) s['10_DoorClose'] = mix(thump(170, 70, 0.3, 13, 0.9), delay(mix(ping(1600, 0.1, 40, 0.4), click(12, 0.6)), 0.12)) s['11_DoorOpen'] = mix(sine(lambda t: 140 + 90 * t / 0.4, 0.4, lambda t: 0.25 * math.sin(math.pi * t / 0.4)), delay(mix(ping(1400, 0.1, 40, 0.4), click(13, 0.6)), 0.38)) fan = [] for k, f in enumerate((523.25, 659.25, 783.99, 1046.5)): fan = mix(fan, delay(mix(ping(f, 0.5, 5, 0.4), ping(2 * f, 0.4, 8, 0.12)), 0.14 * k)) s['13_Victory'] = fan return s def write(path, sig): peak = max(abs(v) for v in sig) or 1.0 scale = 0.7 / peak fade = n(0.005) frames = bytearray() for i, v in enumerate(sig): g = min(1.0, i / fade, (len(sig) - i) / fade) x = int(max(-1.0, min(1.0, v * scale * g)) * 32767) frames += struct.pack('