/* Sol's listening room: integer pulse coupling and PCM audio, C89/AIX. */ #include #include #include #include #include #define AGENTS 16 #define CIRCLE 65536L #define RATE 22050L #define VOICE_SAMPLES 1984 typedef struct { int tick, agent; } Event; static long phase[AGENTS], rate[AGENTS]; static unsigned long rng; static int pitch[AGENTS] = { 110, 131, 147, 165, 196, 220, 262, 294, 330, 392, 440, 524, 588, 660, 784, 880 }; static unsigned long random32(void) { rng ^= (rng << 13) & 0xffffffffUL; rng ^= rng >> 17; rng ^= (rng << 5) & 0xffffffffUL; rng &= 0xffffffffUL; return rng; } static int number(const char *s, unsigned long *out) { char *end; if (!*s || *s == '-') return 0; errno = 0; *out = strtoul(s, &end, 10); return !errno && !*end; } static void little16(FILE *f, unsigned long v) { fputc((int)(v & 255UL), f); fputc((int)((v >> 8) & 255UL), f); } static void little32(FILE *f, unsigned long v) { little16(f, v & 65535UL); little16(f, (v >> 16) & 65535UL); } static int render(const char *path, Event *events, long count, int ticks) { FILE *f; long frames, sample, next, left, right, osc[AGENTS], wave, envelope; long age[AGENTS], increment[AGENTS], pan, bytes; int i; f = fopen(path, "wb"); if (!f) { perror(path); return 0; } frames = (long)ticks * RATE / 100; bytes = frames * 4; fwrite("RIFF", 1, 4, f); little32(f, (unsigned long)bytes + 36UL); fwrite("WAVEfmt ", 1, 8, f); little32(f, 16); little16(f, 1); little16(f, 2); little32(f, RATE); little32(f, RATE * 4); little16(f, 4); little16(f, 16); fwrite("data", 1, 4, f); little32(f, (unsigned long)bytes); for (i = 0; i < AGENTS; i++) { age[i] = VOICE_SAMPLES; osc[i] = 0; increment[i] = (long)pitch[i] * CIRCLE / RATE; } next = 0; for (sample = 0; sample < frames; sample++) { while (next < count && (long)events[next].tick * RATE / 100 <= sample) { i = events[next].agent; age[i] = 0; osc[i] = 0; next++; } left = right = 0; for (i = 0; i < AGENTS; i++) { if (age[i] >= VOICE_SAMPLES) continue; /* Soft attack and linear decay, integer triangle oscillator. */ envelope = age[i] < 64 ? age[i] * 16 : (VOICE_SAMPLES - age[i]) * 1024 / (VOICE_SAMPLES - 64); wave = osc[i] < CIRCLE / 2 ? osc[i] * 2 - CIRCLE / 2 : CIRCLE * 3 / 2 - osc[i] * 2; wave = wave * envelope / 1024 / 24; pan = (long)i * 256 / (AGENTS - 1); left += wave * (256 - pan) / 256; right += wave * pan / 256; osc[i] = (osc[i] + increment[i]) % CIRCLE; age[i]++; } if (left < -32768 || left > 32767 || right < -32768 || right > 32767) { fclose(f); fputs("Audio amplitude invariant failed\n", stderr); return 0; } little16(f, (unsigned long)left); little16(f, (unsigned long)right); } if (ferror(f)) { fclose(f); return 0; } return fclose(f) == 0; } int main(int argc, char **argv) { unsigned long seed, n; int ticks, gain, i, tick, fired[AGENTS], heard, a, b; long nextphase[AGENTS], error, correction, count, capacity; const char *dir; char path[1024]; FILE *csv, *states, *initial, *meta; Event *events; seed = 604; ticks = 3200; gain = 192; dir = "coupled-604"; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--help")) { puts("Usage: sound-world [--seed 1..4294967295] [--ticks 1..12000]" " [--gain 0..256] [--output NEW_DIRECTORY]"); return 0; } if (i + 1 >= argc) { fputs("Missing option value\n", stderr); return 1; } if (!strcmp(argv[i], "--output")) { dir = argv[++i]; continue; } if (!number(argv[i + 1], &n)) { fputs("Invalid integer\n", stderr); return 1; } if (!strcmp(argv[i], "--seed") && n && n <= 0xffffffffUL) seed = n; else if (!strcmp(argv[i], "--ticks") && n && n <= 12000UL) ticks = (int)n; else if (!strcmp(argv[i], "--gain") && n <= 256UL) gain = (int)n; else { fputs("Unknown option or out-of-range value\n", stderr); return 1; } i++; } if (!*dir || strlen(dir) > 900) { fputs("Invalid output path\n", stderr); return 1; } if (mkdir(dir, 0755) != 0) { perror(dir); return 1; } capacity = (long)ticks * AGENTS; events = (Event *)calloc((size_t)capacity, sizeof(Event)); if (!events) { fputs("Allocation failed\n", stderr); return 1; } sprintf(path, "%s/events.csv", dir); csv = fopen(path, "w"); sprintf(path, "%s/phases.csv", dir); states = fopen(path, "w"); sprintf(path, "%s/initial.csv", dir); initial = fopen(path, "w"); sprintf(path, "%s/run.txt", dir); meta = fopen(path, "w"); if (!csv || !states || !initial || !meta) { perror("Opening output"); free(events); return 1; } fprintf(meta, "Sound world v1\nauthor=Sol\nseed=%lu\nticks=%d\n" "tick_ms=10\ngain=%d\nagents=16\nsample_rate=22050\n", seed, ticks, gain); fprintf(csv, "tick,agent\n"); fprintf(initial, "agent,phase,rate,pitch_hz\n"); fprintf(states, "tick"); rng = seed; for (i = 0; i < AGENTS; i++) { phase[i] = (long)(random32() % (unsigned long)CIRCLE); rate[i] = CIRCLE / (72 + (long)(random32() % 17UL)); fired[i] = 0; fprintf(initial, "%d,%ld,%ld,%d\n", i, phase[i], rate[i], pitch[i]); fprintf(states, ",phase%d", i); } fputc('\n', states); count = 0; for (tick = 0; tick < ticks; tick++) { if (tick % 10 == 0) { fprintf(states, "%d", tick); for (i = 0; i < AGENTS; i++) fprintf(states, ",%ld", phase[i]); fputc('\n', states); } for (i = 0; i < AGENTS; i++) { a = (i + AGENTS - 1) % AGENTS; b = (i + 1) % AGENTS; heard = fired[a] + fired[b]; /* Hear only previous-tick pulses. Move toward the nearest phase-zero * alignment, normalized by two neighbors, without changing rate. */ error = phase[i] < CIRCLE / 2 ? -phase[i] : CIRCLE - phase[i]; correction = error * gain * heard / 512; nextphase[i] = phase[i] + rate[i] + correction; } for (i = 0; i < AGENTS; i++) { fired[i] = nextphase[i] >= CIRCLE; phase[i] = nextphase[i] % CIRCLE; if (phase[i] < 0 || phase[i] >= CIRCLE) { fputs("Phase invariant failed\n", stderr); free(events); return 2; } if (fired[i]) { if (count >= capacity) { free(events); return 2; } events[count].tick = tick; events[count].agent = i; count++; fprintf(csv, "%d,%d\n", tick, i); } } } fprintf(meta, "pulses=%ld\n", count); a = fclose(csv); b = fclose(states); a |= fclose(initial); b |= fclose(meta); if (a || b) { free(events); fputs("Log write failed\n", stderr); return 1; } sprintf(path, "%s/listen.wav", dir); if (!render(path, events, count, ticks)) { free(events); return 1; } free(events); printf("Seed %lu gain %d: %d ticks, %ld pulses, %.2f seconds stereo PCM\n", seed, gain, ticks, count, (double)ticks / 100.0); return 0; }