/* * sound.c: raw test sound synthesis and raw sound storage to AU file. */ /* Written by Matt Harang * Mathematics and some bugfixes by Andy Goth * * Link with -lm for fmod, sin, ... * * This program makes no attempt to be efficient */ #include #include #include #include #include #define E_OK 0 #define E_PARAM 3 #define E_MEMORY 4 void swap_bytes(void* a, size_t elem_size, int num_elem) { char foo; char* sp; char* dp; sp = a; dp = (char*)a + elem_size - 1; while (num_elem) { /* for each elem: */ while (sp < dp) { /* for each byte: */ /* swap bytes */ foo = *sp; *sp = *dp; *dp = foo; sp++; dp--; } /* update ptrs to the end and start of next elem, * respectively */ dp += elem_size / 2 + elem_size; sp += elem_size / 2; num_elem--; } } void die(char* s) { if (s) fprintf(stderr, "%s\n", s); exit(1); } void perror_die(char* s) { perror(s); exit(1); } void nfloat_to_s16(float* src, signed short* dest, int n) { int i; for (i = 0; i < n; i++) /* There's a little bit of error in this calculation, but * who cares? :-) */ dest[i] = (signed short)(src[i] * (float)32767.0); } /* * Generate a periodic wave that ramps from start_freq to end_freq on an * exponential curve, by evaluating (*func) for the first n sample points, and * storing the results in buf. If this function is to be called repeatedly, or * in conjunction with other periodic wave-generating functions, the resulting * waves can be made continuous by saving the ending phase after each call and * passing it as the starting phase of the next call, with the phase * parameter. */ int gen_periodic_wave_ramp_freq( float* buf, /* buffer */ int n, /* buffer length (floats) */ int samp_rate, /* buffer sampling rate */ double (*func)(double), /* periodic func to run on buf */ double func_period, /* period of func() */ float start_freq, float end_freq, float* phase /* func starting/ending phase */ ) { int i; /* sample index into buf */ double x; /* function input phase counter */ double p; /* period */ double dp; /* period change */ /* avoid division by zero... */ if (n == 0) return E_PARAM; if (start_freq == 0.0) return E_PARAM; /* dp = nth root of (end_freq / start_freq), for exp. curve */ dp = pow(end_freq / start_freq, 1.0 / (float)n); /* fill table */ p = func_period * start_freq / samp_rate; if (phase != NULL) x = *phase; else x = 0.0; for (i = 0; i < n; i++) { buf[i] = func(x); x += p; p *= dp; } /* maybe calculate and save ending phase */ if (phase != NULL) { while (x > func_period) x -= func_period; *phase = x; } return E_OK; } /* * Do a linear crossfade between buf1 and buf2, by taking weighted averages of * samples from each and storing the results in dest. pan_start and pan_end * are the starting and ending 'pans' (0.0 = 100% of buf1, 0% of buf2; 1.0 = * 0% of buf1, 100% of buf2). */ int crossfade(float* buf1, float* buf2, float* dest, int n, double pan_start, double pan_end) { int i; double p; /* pan position */ double dp; /* pan change */ /* avoid division by zero... */ if (n == 0) return E_PARAM; p = pan_start; dp = (pan_end - pan_start) / (double)n; for (i = 0; i < n; i++) { dest[i] = buf1[i] * (1.0 - p) + buf2[i] * p; p += dp; } return E_OK; } /* * Do a linear fade between two amplitutes (must be between 0.0 and 1.0). */ int fade(float* src, float* dest, int n, double amp_start, double amp_end) { int i; double a; /* amplitude */ double ap; /* amp. change */ /* avoid division by zero... */ if (n == 0) return E_PARAM; a = amp_start; ap = (amp_end - amp_start) / (double)n; for (i = 0; i < n; i++) { dest[i] = src[i] * a; a += ap; } return E_OK; } /* * Clip the first n samples of buf, by making sure none are greater than 1 or * less than -1. */ int clip(float* buf, int n) { int i; for (i = 0; i < n; i++) { if (buf[i] > 1.0) buf[i] = 1.0; else if (buf[i] < -1.0) buf[i] = -1.0; } return E_OK; } double saw(double x) { return fmod(x, 2.0) - 1.0; } double triangle(double x) { double y = fmod(x, 2.0); if (y < 1) return y * 2 - 1.0; else return 3.0 - 2 * y; } double square(double x) { if (fmod(x, 2.0) < 1) return -1.0; else return 1.0; } /* The ugliest main() I've ever written... */ int main(void) { FILE* file; char* header; float* float_buf; char* buf; int length; /* length of buffers, in samples */ float phase = 0.0; int n; int i; int samp_rate = 44100; /* 44.1 KHz (leave this value alone) */ /* set these as you like */ float duration = 10; /* Ten seconds */ float start_freq = 440; /* Start with middle A */ float end_freq = 44100 * 3; /* Let's totally blast the Nyquist */ /* Just in case we want to generate noise...? */ srand(time(NULL)); /* allocate buffers */ length = samp_rate * duration; if ((float_buf = malloc(sizeof(float) * length)) == NULL) die("malloc(): error allocating float buffer"); if ((buf = malloc(sizeof(short) * length)) == NULL) die("malloc(): error allocating integer buffer"); if ((header = malloc(28)) == NULL) die("malloc(): error allocating header buffer"); /* open output file */ if ((file = fopen("sound.au", "wb")) == NULL) perror_die("fopen()"); /* build AU header */ memcpy(header, ".snd", 4); *(unsigned long*)(header + 4) = 28UL; /* data offset */ *(unsigned long*)(header + 8) = ~0UL; *(unsigned long*)(header + 12) = 3UL; /* 16-bit signed PCM */ *(unsigned long*)(header + 16) = 44100UL; /* sampling rate */ *(unsigned long*)(header + 20) = 1UL; /* channels */ *(unsigned long*)(header + 24) = 0UL; /* write header to disk */ swap_bytes(header + 4, 4, 5); fwrite(header, 4, 6, file); /* generate sound */ gen_periodic_wave_ramp_freq( float_buf, length, samp_rate, sin, 2 * M_PI, start_freq, end_freq, NULL ); /* convert format from normalized floating-point samples to 16-bit * signed linear PCM samples, and reverse endianness */ nfloat_to_s16(float_buf, (short*)buf, length); swap_bytes(buf, 2, length); /* write PCM samples */ n = fwrite(buf, sizeof(short), length, file); printf("%d samples written\n", n); /* cleanup */ fclose(file); free(buf); free(float_buf); free(header); return EXIT_SUCCESS; } /* EOF */