/* * sound: audio and mic playground, every sound is made in code so it needs no files * * build: ./build.sh -d -m audio,mic,debug examples * run: obj/sound * * top down view, you are the green dot looking up the screen, the orange one circles around you * and hums, so you can hear it go past. arrow keys move you, click puts a thump in the world * space beeps, 1 and 2 crossfade the music, 0 stops it, m mutes the mic, r plays back the last 3 seconds of the mic * the console (`) knows music, bus, and the variables master sfxvol musicvol pitch orbit micgain */ #include #include #include #include #include "konprogram.h" #include "konaudio.h" #include "konmic.h" #if !defined(KON_MODULE_AUDIO) || !defined(KON_MODULE_MIC) || !defined(KON_MODULE_DEBUG) #error "build with: ./build.sh -d -m audio,mic,debug examples" #endif #define RATE 44100 #define SCALE 12.0f typedef struct state { kon_program_t *program; kon_audio_t *audio; kon_mic_t *mic; kon_sound_t beep, thump, hum, musicA, musicB; kon_audioHandle_t humHandle, echoHandle; kon_sound_t *echo; float listenerX, listenerZ; float angle; int moveX, moveZ; float masterVolume, sfxVolume, musicVolume, pitch, orbit, micGain; float pings[8][3]; } state_t; /*** sounds made in code ***/ static int makeSound(kon_sound_t *sound, int frames) { sound->frames = calloc((size_t)frames, sizeof(int16_t)); sound->frameCount = (uint64_t)frames; sound->channels = 1; sound->sampleRate = RATE; return sound->frames != NULL; } static int16_t toSample(float x) { if (x > 1.0f) x = 1.0f; if (x < -1.0f) x = -1.0f; return (int16_t)(x * 32000.0f); } static void makeBeep(kon_sound_t *sound) { int i, n = RATE / 4; makeSound(sound, n); for (i = 0; i < n; i++) { float t = (float)i / RATE; sound->frames[i] = toSample(0.6f * sinf(6.2831853f * 880.0f * t) * expf(-t * 14.0f)); } } static void makeThump(kon_sound_t *sound) { int i, n = RATE / 2; float low = 0.0f; unsigned seed = 12345; makeSound(sound, n); for (i = 0; i < n; i++) { float t = (float)i / RATE; float noise; seed = seed * 1664525u + 1013904223u; noise = (float)(seed >> 8) / 8388608.0f - 1.0f; low += (noise - low) * 0.08f; sound->frames[i] = toSample((low * 3.0f + sinf(6.2831853f * 60.0f * t) * 0.8f) * expf(-t * 9.0f)); } } /* one second that loops cleanly, every wave has a whole number of cycles in it */ static void makeHum(kon_sound_t *sound) { int i, n = RATE; makeSound(sound, n); for (i = 0; i < n; i++) { float t = (float)i / RATE; float pulse = 0.6f + 0.4f * sinf(6.2831853f * 4.0f * t); sound->frames[i] = toSample(0.3f * pulse * (sinf(6.2831853f * 220.0f * t) + 0.5f * sinf(6.2831853f * 330.0f * t))); } } /* four seconds of an arpeggio over a chord, rootA and rootB are the frequency of the first note */ static void makeMusic(kon_sound_t *sound, float root, const float *ratios) { int i, n = RATE * 4; makeSound(sound, n); for (i = 0; i < n; i++) { float t = (float)i / RATE; int step = (int)(t * 4.0f); float local = t * 4.0f - (float)step; float freq = root * ratios[step % 4]; float pad = sinf(6.2831853f * root * 0.5f * t) * 0.15f; float note = sinf(6.2831853f * freq * t) * expf(-local * 3.0f) * 0.35f; sound->frames[i] = toSample(pad + note); } } /*** commands ***/ static void cmdMusic(kon_debug_t *debug, void *user, int argc, char **argv) { state_t *state = user; if (argc < 2) { kon_debugLog(debug, "music a|b|off [fade seconds]"); return; } if (strcmp(argv[1], "a") == 0) { kon_audioMusic(state->audio, &state->musicA, NULL, argc > 2 ? (float)atof(argv[2]) : 1.5f); } else if (strcmp(argv[1], "b") == 0) { kon_audioMusic(state->audio, &state->musicB, NULL, argc > 2 ? (float)atof(argv[2]) : 1.5f); } else if (strcmp(argv[1], "off") == 0) { kon_audioMusicStop(state->audio, argc > 2 ? (float)atof(argv[2]) : 1.5f); } else { kon_debugLog(debug, "music a|b|off [fade seconds]"); } } static void cmdBus(kon_debug_t *debug, void *user, int argc, char **argv) { static const char *names[KON_AUDIO_BUSES] = { "sfx", "music", "voice", "ui" }; state_t *state = user; int i; if (argc < 3) { kon_debugLog(debug, "bus sfx|music|voice|ui "); return; } for (i = 0; i < KON_AUDIO_BUSES; i++) { if (strcmp(argv[1], names[i]) == 0) { kon_audioSetBusVolume(state->audio, i, (float)atof(argv[2])); return; } } kon_debugLog(debug, "no bus called %s", argv[1]); } /*** program ***/ static void toWorld(int screenX, int screenY, float *x, float *z) { *x = ((float)screenX - 320.0f) / SCALE; *z = ((float)screenY - 180.0f) / SCALE; } static int event(kon_program_t *program, const kon_event_t *ev, void *user) { state_t *state = user; if (ev->type == KON_EVENT_MOUSE_DOWN && ev->mouseButton == KON_MOUSE_LEFT) { int gx, gy; if (kon_windowToGame(program->display, ev->mouseX, ev->mouseY, &gx, &gy)) { float x, z; int i; toWorld(gx, gy, &x, &z); x += state->listenerX; z += state->listenerZ; kon_audioPlayAt(state->audio, &state->thump, 1.0f, x, 0.0f, z); for (i = 7; i > 0; i--) memcpy(state->pings[i], state->pings[i - 1], sizeof state->pings[i]); state->pings[0][0] = x; state->pings[0][1] = z; state->pings[0][2] = 1.0f; } } else if (ev->type == KON_EVENT_KEY_DOWN || ev->type == KON_EVENT_KEY_UP) { int down = ev->type == KON_EVENT_KEY_DOWN; switch (ev->key) { case KON_KEY_LEFT: state->moveX = down ? -1 : (state->moveX < 0 ? 0 : state->moveX); break; case KON_KEY_RIGHT: state->moveX = down ? 1 : (state->moveX > 0 ? 0 : state->moveX); break; case KON_KEY_UP: state->moveZ = down ? -1 : (state->moveZ < 0 ? 0 : state->moveZ); break; case KON_KEY_DOWN: state->moveZ = down ? 1 : (state->moveZ > 0 ? 0 : state->moveZ); break; default: break; } if (!down) return 1; if (ev->key == KON_KEY_SPACE) { kon_audioParams_t p = kon_audioParamsDefault(); p.bus = KON_BUS_UI; p.pan = ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f; kon_audioPlayEx(state->audio, &state->beep, &p); } else if (ev->key == KON_KEY_1) { kon_audioMusic(state->audio, &state->musicA, NULL, 1.5f); } else if (ev->key == KON_KEY_2) { kon_audioMusic(state->audio, &state->musicB, NULL, 1.5f); } else if (ev->key == KON_KEY_0) { kon_audioMusicStop(state->audio, 1.5f); } else if (ev->key == KON_KEY_M && state->mic) { static int muted; muted = !muted; kon_micMute(state->mic, muted); kon_debugLog(program->debug, muted ? "mic muted" : "mic live"); } else if (ev->key == KON_KEY_R && state->mic) { kon_sound_t *heard = kon_micSnapshot(state->mic, 3.0f); if (heard) { /* the sound has to stay alive while it plays, so the old one goes only when we are done with it */ if (state->echo) { kon_audioStop(state->audio, state->echoHandle); free(state->echo); } state->echo = heard; state->echoHandle = kon_audioPlayAt(state->audio, heard, 1.0f, state->listenerX + 6.0f, 0.0f, state->listenerZ - 6.0f); } else { kon_debugLog(program->debug, "the mic has not heard anything yet"); } } } else { return 0; } return 1; } static void update(kon_program_t *program, double dt, void *user) { state_t *state = user; float ex, ez; int i; (void)program; state->angle += (float)dt * state->orbit; state->listenerX += (float)state->moveX * 8.0f * (float)dt; state->listenerZ += (float)state->moveZ * 8.0f * (float)dt; ex = state->listenerX + cosf(state->angle) * 10.0f; ez = state->listenerZ + sinf(state->angle) * 10.0f; kon_audioSetListener(state->audio, state->listenerX, 0.0f, state->listenerZ, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); kon_audioHandleSetPosition(state->audio, state->humHandle, ex, 0.0f, ez); kon_audioHandleSetPitch(state->audio, state->humHandle, state->pitch); kon_audioSetVolume(state->audio, state->masterVolume); kon_audioSetBusVolume(state->audio, KON_BUS_SFX, state->sfxVolume); kon_audioSetBusVolume(state->audio, KON_BUS_MUSIC, state->musicVolume); if (state->mic) kon_micSetGain(state->mic, state->micGain); for (i = 0; i < 8; i++) { state->pings[i][2] -= (float)dt; if (state->pings[i][2] < 0.0f) state->pings[i][2] = 0.0f; } } static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user) { state_t *state = user; char text[96]; int i, ex, ez, bar; kon_clearFramebuffer(game, 0xFF1B1826); /* the range of the hum, full volume inside the small ring and nothing outside the big one */ kon_drawCircle(game, 320, 180, (int)(2.0f * SCALE), 0xFF3A3558); kon_drawCircle(game, 320, 180, (int)(22.0f * SCALE), 0xFF3A3558); for (i = 0; i < 8; i++) { if (state->pings[i][2] <= 0.0f) continue; kon_drawCircle(game, 320 + (int)((state->pings[i][0] - state->listenerX) * SCALE), 180 + (int)((state->pings[i][1] - state->listenerZ) * SCALE), (int)((1.0f - state->pings[i][2]) * 40.0f), 0xFFF3DDA0); } ex = 320 + (int)(cosf(state->angle) * 10.0f * SCALE); ez = 180 + (int)(sinf(state->angle) * 10.0f * SCALE); kon_fillCircle(game, ex, ez, 6, 0xFFF2A7C3); kon_fillCircle(game, 320, 180, 5, 0xFF8FD3C8); kon_drawLine(game, 320, 180, 320, 164, 0xFF8FD3C8); if (state->mic) { bar = (int)(kon_micLevel(state->mic) * 4.0f * 200.0f); if (bar > 200) bar = 200; kon_drawRectangle(game, 10, 340, 201, 9, 0xFF9A93B3); kon_fillRectangle(game, 11, 341, bar, 7, kon_micTalking(state->mic) ? 0xFF8FD3C8 : 0xFFF3DDA0); } if (program->font) { snprintf(text, sizeof text, "playing %d peak %.2f", kon_audioPlayingCount(state->audio), kon_audioPeak(state->audio)); kon_drawText(game, program->font, 10, 20, text, 0xFFE4DDF0); kon_drawText(game, program->font, 10, 36, "arrows move, click thump, space beep, 1 2 0 music, m r mic", 0xFF9A93B3); if (state->mic) kon_drawText(game, program->font, 220, 348, kon_micTalking(state->mic) ? "talking" : "quiet", 0xFFE4DDF0); else kon_drawText(game, program->font, 10, 348, "no mic", 0xFF9A93B3); } } static int init(kon_program_t *program, void *user) { state_t *state = user; kon_audioParams_t p = kon_audioParamsDefault(); static const float majorRatios[4] = { 1.0f, 1.25f, 1.5f, 2.0f }; static const float minorRatios[4] = { 1.0f, 1.2f, 1.5f, 1.8f }; state->program = program; state->audio = kon_audioCreate(0); if (!state->audio) { fprintf(stderr, "sound: could not open an audio device\n"); return 0; } state->mic = kon_micCreate(0, 5.0f); if (!state->mic) kon_debugLog(program->debug, "no mic, the mic parts are off"); makeBeep(&state->beep); makeThump(&state->thump); makeHum(&state->hum); makeMusic(&state->musicA, 220.0f, majorRatios); makeMusic(&state->musicB, 196.0f, minorRatios); state->masterVolume = state->sfxVolume = state->musicVolume = state->pitch = state->micGain = 1.0f; state->orbit = 0.7f; state->angle = 0.0f; p.loop = 1; p.positional = 1; p.x = 10.0f; p.minDistance = 2.0f; p.maxDistance = 22.0f; state->humHandle = kon_audioPlayEx(state->audio, &state->hum, &p); kon_debugCommand(program->debug, "music", "music a|b|off [fade seconds]", cmdMusic, state); kon_debugCommand(program->debug, "bus", "bus sfx|music|voice|ui ", cmdBus, state); kon_debugVar(program->debug, "master", KON_VAR_FLOAT, &state->masterVolume); kon_debugVar(program->debug, "sfxvol", KON_VAR_FLOAT, &state->sfxVolume); kon_debugVar(program->debug, "musicvol", KON_VAR_FLOAT, &state->musicVolume); kon_debugVar(program->debug, "pitch", KON_VAR_FLOAT, &state->pitch); kon_debugVar(program->debug, "orbit", KON_VAR_FLOAT, &state->orbit); kon_debugVar(program->debug, "micgain", KON_VAR_FLOAT, &state->micGain); return 1; } static void shutdown(kon_program_t *program, void *user) { state_t *state = user; (void)program; if (state->mic) kon_micDestroy(state->mic); if (state->audio) kon_audioDestroy(state->audio); free(state->echo); free(state->beep.frames); free(state->thump.frames); free(state->hum.frames); free(state->musicA.frames); free(state->musicB.frames); } int main(void) { state_t state = {0}; kon_programConfig_t config = {0}; config.title = "sound"; config.gameWidth = 640; config.gameHeight = 360; config.windowWidth = 960; config.windowHeight = 540; config.user = &state; config.init = init; config.event = event; config.update = update; config.draw = draw; config.shutdown = shutdown; return kon_programRun(&config); }