// raylib-zig (c) Nikolas Wipper 2023

const rl = @import("raylib");

const MAX_SAMPLES = 512;
const MAX_SAMPLES_PER_UPDATE = 4096;

const pi = @import("std").math.pi;
var frequency: f32 = 440;
var audioFrequency: f32 = 440;
var oldFrequency: f32 = 1;
var sineIdx: f32 = 0;

fn audioInputCallback(buffer: ?*anyopaque, frames: c_uint) callconv(.C) void {
    audioFrequency = frequency + (audioFrequency - frequency) * 0.95;

    const incr = audioFrequency / 44100;
    const d: [*]i16 = @alignCast(@ptrCast(buffer orelse return));

    for (0..frames) |i| {
        d[i] = @intFromFloat(32000 * @sin(2 * pi * sineIdx));
        sineIdx += incr;
        if (sineIdx > 1) {
            sineIdx -= 1;
        }
    }
}

pub fn main() anyerror!void {
    // Initialization
    //--------------------------------------------------------------------------------------
    const screenWidth = 800;
    const screenHeight = 450;

    rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - raw audio streaming");
    defer rl.closeWindow(); // Close window and OpenGL context

    rl.initAudioDevice();        // Initialize audio device
    defer rl.closeAudioDevice(); // Close audio device (music streaming is automatically stopped)

    rl.setAudioStreamBufferSizeDefault(MAX_SAMPLES_PER_UPDATE);

    // Init raw audio stream (sample rate: 44100, sample size: 16bit-short, channels: 1-mono)
    const stream = try rl.loadAudioStream(44100, 16, 1);
    defer rl.unloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM

    rl.setAudioStreamCallback(stream, &audioInputCallback);

    // Buffer for the single cycle waveform we are synthesizing
    const data = try rl.mem.alloc(i16, MAX_SAMPLES);
    defer rl.mem.free(data); // Unload sine wave data

    // Frame buffer, describing the waveform when repeated over the course of a frame
    const writeBuf = try rl.mem.alloc(i16, MAX_SAMPLES_PER_UPDATE);
    defer rl.mem.free(writeBuf); // Unload write buffer

    rl.playAudioStream(stream); // Start processing stream buffer (no data loaded currently)

    // Computed size in samples of the sine wave
    var waveLength: i32 = 1;

    var position = rl.Vector2{ .x = 0, .y = 0 };

    rl.setTargetFPS(30); // Set our game to run at 30 frames-per-second
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        //----------------------------------------------------------------------------------

        if (rl.isMouseButtonDown(.left)) {
            // Sample mouse input.
            const mousePosition = rl.getMousePosition();

            const fp = (screenHeight - mousePosition.y);
            frequency = 40 + fp;

            const pan = (screenWidth - mousePosition.x) / screenWidth;
            rl.setAudioStreamPan(stream, pan);
        }

        // Rewrite the sine wave
        // Compute two cycles to allow buffer padding, simplifying any modulation, resampling, etc.
        if (frequency != oldFrequency) {
            // Compute wavelength. Limit size in both directions.
            //int oldWavelength = waveLength;
            waveLength = @intFromFloat(22050 / frequency);
            waveLength = @min(@max(1, waveLength), MAX_SAMPLES / 2);

            // Write sine wave
            const n: u32 = @intCast(waveLength * 2);
            for (0..n) |i| {
                const wlen: f32 = @floatFromInt(waveLength);
                const idx: f32 = @floatFromInt(i);
                data[i] = @intFromFloat(@sin((2 * pi * idx / wlen)) * 32000);
            }
            // Make sure the rest of the line is flat
            for (n..MAX_SAMPLES) |i| {
                data[i] = 0;
            }

            // Scale read cursor's position to minimize transition artifacts
            //readCursor = (int)(readCursor * ((float)waveLength / (float)oldWavelength));
            oldFrequency = frequency;
        }
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(rl.Color.ray_white);

        rl.drawText(rl.textFormat("sine frequency: %i", .{@as(i32, @intFromFloat(frequency))}),
            rl.getScreenWidth() - 220, 10, 20, rl.Color.red);
        rl.drawText("click mouse button to change frequency or pan",
            10, 10, 20, rl.Color.dark_gray);

        // Draw the current buffer state proportionate to the screen
        for (0..screenWidth) |i| {
            position.x = @floatFromInt(i);
            const y: f32 = @floatFromInt(data[@divFloor(i * MAX_SAMPLES, screenWidth)]);
            position.y = 250 + 50 * y / 32000;

            rl.drawPixelV(position, rl.Color.red);
        }
        //----------------------------------------------------------------------------------
    }
}
