import argparse import os import wave import numpy as np from code_sinth import parse, build_graph, Engine def write_wav(path, samples, sr): pcm = np.clip(samples, -1.0, 1.0) pcm = (pcm * 32767.0).astype(np.int16) with wave.open(path, 'wb') as w: w.setnchannels(1) w.setsampwidth(2) w.setframerate(sr) w.writeframes(pcm.tobytes()) def main(): ap = argparse.ArgumentParser(description='code-sinth: tiny modular synth from .patch files') ap.add_argument('patch', help='Path to .patch file') ap.add_argument('--duration', type=float, default=6.0, help='Seconds to render/play (default 6)') ap.add_argument('--wav', help='Render to WAV file instead of playing live') ap.add_argument('--live', action='store_true', help='Play indefinitely and hot-reload the patch on file changes') ap.add_argument('--viz', action='store_true', help='Start the inline-waveform visualizer (web UI). Implies --live.') ap.add_argument('--sr', type=int, default=48000) ap.add_argument('--block', type=int, default=512) args = ap.parse_args() def load(path): with open(path, 'r', encoding='utf-8') as f: src = f.read() return build_graph(parse(src)), src graph, src_text = load(args.patch) engine = Engine(graph, sr=args.sr, block_size=args.block) engine.current_patch_text = src_text if args.wav: print(f'Rendering {args.duration}s to {args.wav}...') samples = engine.render_offline(args.duration) write_wav(args.wav, samples, args.sr) print('Done.') elif args.viz or args.live: if args.viz: from code_sinth import viz viz_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'viz') viz.serve(engine, viz_dir) engine.run_live(args.patch, lambda src: build_graph(parse(src))) else: print(f'Playing {args.patch} for {args.duration}s...') engine.run(args.duration) if __name__ == '__main__': main()