diff --git a/.vscode/Fumofumotris.code-workspace b/.vscode/Fumofumotris.code-workspace index c551b90..e103b86 100644 --- a/.vscode/Fumofumotris.code-workspace +++ b/.vscode/Fumofumotris.code-workspace @@ -63,6 +63,10 @@ "fumocommon.h": "c", "terminal.h": "c", "ios": "cpp" + }, + "workbench.colorCustomizations": { + "minimap.background": "#00000000", + "scrollbar.shadow": "#00000000" } } } \ No newline at end of file diff --git a/build.zig b/build.zig index 09d3ef3..1cb2ad1 100644 --- a/build.zig +++ b/build.zig @@ -3,13 +3,26 @@ const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + + const i_audio = b.addModule("audio_interface", .{ + .root_source_file = b.path("libs/audio_interface.zig"), + }); + const i_minimal_input = b.addModule("minimal_input_interface", .{ + .root_source_file = b.path("libs/minimal_input_interface.zig"), + }); + const exe = b.addExecutable(.{ .name = "Fumofumotris", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); + + exe.root_module.addImport("audio_interface", i_audio); + exe.root_module.addImport("minimal_input_interface", i_minimal_input); + exe.linkLibC(); + exe.linkSystemLibrary("ole32"); b.installArtifact(exe); } diff --git a/libs/audio_interface.zig b/libs/audio_interface.zig new file mode 100644 index 0000000..14c19df --- /dev/null +++ b/libs/audio_interface.zig @@ -0,0 +1,12 @@ +const std = @import("std"); + +pub const IAdapter = struct { + getOutputCount: *const fn (ptr: *anyopaque) usize, + getOutputName: *const fn (ptr: *anyopaque, index: usize) []u8, + setOutput: *const fn (output: usize) void, +}; + +pub const IUser = struct { + setFormat: *const fn (ptr: *anyopaque, channels: u32, sample_rate: u32) void, + render: *const fn (ptr: *anyopaque, buf: []f32) void, +}; diff --git a/libs/minimal_input_interface.zig b/libs/minimal_input_interface.zig new file mode 100644 index 0000000..8ff51bd --- /dev/null +++ b/libs/minimal_input_interface.zig @@ -0,0 +1,22 @@ +const std = @import("std"); + +pub const Joystick = struct { + x: i32, + y: i32, +}; + +pub const Keyboard = struct { + state: u128 = 0, + last_pressed: [128]u32 = std.mem.zeroes(), + last_released: [128]u32 = std.mem.zeroes(), +}; + +pub const Mouse = struct { + pos: Joystick, + wheel: Joystick, +}; + +pub const Client = struct { + keyboard: Keyboard, + mouse: Mouse, +}; diff --git a/src/OLD input/terminal_input.zig b/src/OLD input/terminal_input.zig new file mode 100644 index 0000000..ba36389 --- /dev/null +++ b/src/OLD input/terminal_input.zig @@ -0,0 +1,16 @@ +const Controller = struct { + keyboard: u128, + mouse_x: i16, + mouse_y: i16, + wheel_v: i8, + wheel_h: i8, + + pub inline fn keyboardSet(this: *Controller, code: u8, value: u1) void { + this.keyboard &= ~(1 << code); + this.keyboard |= value << code; + } + + pub inline fn keyboardGet(this: *Controller, code: u8) bool { + return (this.keyboard >> code) & 1 == 1; + } +}; diff --git a/src/audio.zig b/src/audio.zig new file mode 100644 index 0000000..5159a1a --- /dev/null +++ b/src/audio.zig @@ -0,0 +1,65 @@ +const std = @import("std"); +const AtomicOrder = std.builtin.AtomicOrder; + +const audio_interface = @import("audio_interface"); + +const platform = @import("platform.zig"); + +pub const Engine = struct { + const Error = error{PlatformInitializationFailed}; + + const adapter_user_vt: audio_interface.IUser = .{ + .setFormat = &setFormat, + .render = &render, + }; + + adapter: platform.audio.Adapter = undefined, + channels: u32 = 0, + sample_rate: u32 = 0, + + theta: f32 = 0, + + pub fn init(engine: *Engine) Error!void { + engine.* = .{}; + if (!engine.adapter.init(&adapter_user_vt, engine)) + return Error.PlatformInitializationFailed; + } + + fn setFormat(ptr: *anyopaque, channels: u32, sample_rate: u32) void { + const engine: *Engine = @alignCast(@ptrCast(ptr)); + + @atomicStore(u32, &engine.channels, channels, AtomicOrder.unordered); + @atomicStore(u32, &engine.sample_rate, sample_rate, AtomicOrder.unordered); + } + + fn render(ptr: *anyopaque, buf: []f32) void { + const engine: *Engine = @alignCast(@ptrCast(ptr)); + + var i: usize = 0; + while (i < buf.len) : (i += 2) { + buf[i] = testSin(engine.theta) * 0.25; + buf[i + 1] = testSin(engine.theta) * 0.25; + engine.theta += (1.0 / @as(f32, @floatFromInt(engine.sample_rate))) * 440; + } + } +}; + +pub const PluginMetadata = struct { + name: []u8, + author: []u8, + version: u64, +}; + +pub const PluginVTable = struct { + init: *const fn (engine: *Engine) anyerror!void, + deinit: *const fn () void, + render: *const fn (buf_in: []f32, buf_out: []f32) void, +}; + +fn testSin(x: f32) f32 { + const sign: f32 = if (x - @floor(x) > 0.5) -1 else 1; + const mod: f32 = (2 * x - @floor(2 * x)) / 2 - 0.25; + const abs: f32 = 1.25 * (1 / (mod * mod + 0.25)) - 4; + + return abs * sign; +} diff --git a/src/dictionary.zig b/src/dictionary.zig deleted file mode 100644 index d6ea864..0000000 --- a/src/dictionary.zig +++ /dev/null @@ -1,81 +0,0 @@ -const Allocator = @import("std").mem.Allocator; - -pub const DictionaryError = error{Full}; - -pub fn Dictionary(comptime K: type, comptime V: type) type { - return struct { - pub const Bucket = struct { - key: K = 0, - value: V = undefined, - }; - - buckets: []Bucket, - filled: usize = 0, - - pub fn init(alloc: Allocator, n: usize) !@This() { - const dict = @This(){ - .buckets = try alloc.alloc(Bucket, n), - }; - - @memset(dict.buckets, Bucket{}); - - return dict; - } - - pub fn find(dict: @This(), key: K) ?*V { - const index: usize = key % dict.buckets.len; - - const probe: ?*Bucket = dict.linearProbe(index, key); - - if (probe) |bucket| { - return &bucket.value; - } else { - return null; - } - } - - pub fn set(hashMap: *@This(), key: K, value: V) !void { - const index: usize = key % hashMap.buckets.len; - - const probe: ?*Bucket = hashMap.linProbeEmpty(index, key); - - if (probe) |bucket| { - bucket.* = Bucket{ .key = key, .value = value }; - hashMap.filled += 1; - } else { - return DictionaryError.Full; - } - } - - fn linearProbe(hashMap: @This(), start: usize, hash: usize) ?*Bucket { - var i: usize = 0; - var index: usize = start; - - while (i < hashMap.buckets.len) : ({ - i += 1; - index = (start + i) % hashMap.buckets.len; - }) { - if (hashMap.buckets[index].key == hash) - return &hashMap.buckets[index]; - } - - return null; - } - - fn linProbeEmpty(hashMap: @This(), start: usize, hash: usize) ?*Bucket { - var i: usize = 0; - var index: usize = start; - - while (i < hashMap.buckets.len) : ({ - i += 1; - index = (start + i) % hashMap.buckets.len; - }) { - const cur: usize = hashMap.buckets[index].key; - if (cur == 0 or cur == hash) - return &hashMap.buckets[index]; - } - - return null; - } - }; -} diff --git a/src/fuck.exe b/src/fuck.exe deleted file mode 100644 index 27087c2..0000000 Binary files a/src/fuck.exe and /dev/null differ diff --git a/src/imsuffering.cpp b/src/imsuffering.cpp deleted file mode 100644 index 63f87b9..0000000 --- a/src/imsuffering.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include -#include -#include -using namespace std; - -int main() -{ - GUID sex = __uuidof(IAudioClient3); - cout << ".{ "; - cout << ".Data1 = 0x" << hex << sex.Data1 << ", "; - cout << ".Data2 = 0x" << hex << sex.Data2 << ", "; - cout << ".Data3 = 0x" << hex << sex.Data3 << ", "; - - cout << ".Data4 = .{ "; - for (int i = 0; i < 8; i++) - cout << "0x" << hex << (int)sex.Data4[i] << ", "; - - cout << "}}"; - - return 0; -} \ No newline at end of file diff --git a/src/input.zig b/src/input.zig index 8c268ed..e424a03 100644 --- a/src/input.zig +++ b/src/input.zig @@ -1,9 +1,20 @@ const std = @import("std"); const Allocator = @import("std").mem.Allocator; -const time = @import("time.zig"); const platform = @import("platform.zig"); +pub const Manager = struct { + adapter: platform.input.Adapter = undefined, + keyboard: Keyboard, + mouse: Mouse, + + pub fn init(manager: *Manager) !void { + manager.adapter.init(); + } + + pub fn update(manager: *Manager) void {} +}; + pub const EventBuffer = struct { pub const buf_size = std.atomic.cache_line / 5; @@ -17,17 +28,12 @@ pub const Joystick = struct { }; pub const Keyboard = struct { - state: u128, - last_pressed: [128]time.mysec32, - last_released: [128]time.mysec32, + state: u128 = 0, + last_pressed: [128]u32 = std.mem.zeroes(), + last_released: [128]u32 = std.mem.zeroes(), }; pub const Mouse = struct { pos: Joystick, wheel: Joystick, }; - -pub const Manager = struct { - keyboard: Keyboard, - mouse: Mouse, -}; diff --git a/src/main.zig b/src/main.zig index c53564f..f62e139 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,18 @@ -const os = @import("os.zig"); +const std = @import("std"); + +const audio = @import("audio.zig"); +// const input = @import("input.zig"); pub fn main() !void { - var platform: os.Platform = try os.Platform.init(); + var audio_engine: audio.Engine = undefined; + try audio_engine.init(); + + // var input_manager: input.Manager = undefined; + // try input_manager.init(); + + // while (true) { + // input_manager.update(); + // } + + audio_engine.adapter.deinit(); } diff --git a/src/music.zig b/src/music.zig deleted file mode 100644 index f79b242..0000000 --- a/src/music.zig +++ /dev/null @@ -1,30 +0,0 @@ -const std = @import("std"); - -fn fastSin(x: f32) f32 { - const sign: f32 = @as(f32, @floatFromInt(@intFromBool(x < 0.5))) * 2 - 1; - - const restricted: f32 = @mod(x, 0.5) - 0.25; - const absolute: f32 = 1.25 * (1 / (restricted * restricted + 0.25)) - 4; - - return absolute * sign; -} - -const AudioOutput = struct { - sample_rate: f32 = 48000, - buf: [256]f32 = undefined, - - sinTheta: f32 = 0, - - fn generate(this: *AudioOutput) !void { - var i: usize = 0; - while (i < 128) : (i += 1) { - const sample = fastSin(this.sinTheta); - this.sinTheta += 440 / this.sample_rate; - - this.buf[i * 2] = sample; - this.buf[i * 2 + 1] = sample; - } - } -}; - -fn main() !void {} diff --git a/src/os.zig b/src/os.zig deleted file mode 100644 index 11c83b7..0000000 --- a/src/os.zig +++ /dev/null @@ -1,5 +0,0 @@ -pub usingnamespace switch (@import("builtin").os.tag) { - .windows => @import("windows.zig"), - .linux => @import("linux.zig"), - else => @compileError("Unsupported operating system"), -}; diff --git a/src/platform.zig b/src/platform.zig new file mode 100644 index 0000000..0f499d6 --- /dev/null +++ b/src/platform.zig @@ -0,0 +1,11 @@ +const tag = @import("builtin").os.tag; + +const os = switch (tag) { + .windows => @import("platform/windows.zig"), + .linux => @import("platform/linux.zig"), + else => {}, +}; + +pub const audio = os.audio; +pub const input = os.input; +pub const display = os.display; diff --git a/src/linux.zig b/src/platform/linux.zig similarity index 100% rename from src/linux.zig rename to src/platform/linux.zig diff --git a/src/lintest.c b/src/platform/linux/x11test.c similarity index 100% rename from src/lintest.c rename to src/platform/linux/x11test.c diff --git a/src/platform/windows.zig b/src/platform/windows.zig new file mode 100644 index 0000000..9ea9e8a --- /dev/null +++ b/src/platform/windows.zig @@ -0,0 +1,2 @@ +pub const audio = @import("windows/wasapi.zig"); +pub const input = @import("windows/wincon.zig"); diff --git a/src/platform/windows/notification_client.zig b/src/platform/windows/notification_client.zig new file mode 100644 index 0000000..4c7d3a3 --- /dev/null +++ b/src/platform/windows/notification_client.zig @@ -0,0 +1,105 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const win = @import("win_common.zig"); +const api = win.api; + +pub const Notifier = extern struct { + const c_alloc: Allocator = std.heap.c_allocator; + const vt: api.IMMNotificationClientVtbl = .{ + .QueryInterface = &QueryInterface, + .AddRef = &AddRef, + .Release = &Release, + .OnDeviceStateChanged = &OnDeviceStateChanged, + .OnDeviceAdded = &OnDeviceAdded, + .OnDeviceRemoved = &OnDeviceRemoved, + .OnDefaultDeviceChanged = &OnDefaultDeviceChanged, + .OnPropertyValueChanged = &OnPropertyValueChanged, + }; + + client: api.IMMNotificationClient, + ref_count: u32, + + pub fn init() !*Notifier { + var notif: *Notifier = try c_alloc.create(api.IMMNotificationClient); + errdefer c_alloc.free(notif); + + notif.client.lpVtbl = try c_alloc.create(api.IMMNotificationClientVtbl); + notif.client.lpVtbl.* = vt; + + notif.ref_count = 0; + + return notif; + } + + fn QueryInterface( + client: *api.IMMNotificationClient, + iid: *api.IID, + object_out: **anyopaque, + ) callconv(.C) api.HRESULT { + if (api.IsEqualIID(iid, &api.IID_IUnknown) || api.IsEqualIID(iid, &api.IID_IMMNotificationClient)) { + _ = client.AddRef(); + object_out.* = client; + + return api.S_OK; + } else { + object_out.* = null; + + return api.E_NOINTERFACE; + } + } + + fn AddRef(client: *api.IMMNotificationClient) callconv(.C) u32 { + const notif: *Notifier = @alignCast(@ptrCast(client)); + + return api.InterlockedIncrement(¬if.ref_count); + } + + fn Release(client: *api.IMMNotificationClient) callconv(.C) u32 { + const notif: *Notifier = @alignCast(@ptrCast(client)); + + const dec: u32 = api.InterlockedDecrement(¬if.ref_count); + if (dec == 0) + c_alloc.free(notif); + + return dec; + } + + fn OnDeviceStateChanged(_: *api.IMMNotificationClient, _: ?[*:0]u16, _: u32) callconv(.C) api.HRESULT { + return api.S_OK; + } + + fn OnDeviceAdded(_: *api.IMMNotificationClient, _: ?[*:0]u16) callconv(.C) api.HRESULT { + return api.S_OK; + } + + fn OnDeviceRemoved(_: *api.IMMNotificationClient, _: ?[*:0]u16) callconv(.C) api.HRESULT { + return api.S_OK; + } + + fn OnDefaultDeviceChanged( + client: *api.IMMNotificationClient, + flow: api.EDataFlow, + role: api.ERole, + id: ?[*:0]u16, + ) callconv(.C) api.HRESULT { + if ((flow != api.eRender) || (role != api.eConsole)) + return api.S_OK; + + const notif: *Notifier = @alignCast(@ptrCast(client)); + @atomicStore + + return api.S_OK; + } + + fn OnPropertyValueChanged( + client: *api.IMMNotificationClient, + id: ?[*:0]u16, + key: api.PROPERTYKEY, + ) callconv(.C) api.HRESULT { + if (api.IsEqualPropertyKey(key, api.PKEY_AudioEngine_DeviceFormat)) { + + } + return api.S_OK; + } +}; diff --git a/src/platform/windows/wasapi.zig b/src/platform/windows/wasapi.zig new file mode 100644 index 0000000..51509f0 --- /dev/null +++ b/src/platform/windows/wasapi.zig @@ -0,0 +1,100 @@ +const std = @import("std"); + +const audio_interface = @import("audio_interface"); + +const Notifier = @import("notification_client.zig").Notifier; +const win = @import("win_common.zig"); +const api = win.api; + +pub const Adapter = struct { + const Flags = packed struct(u8) { + on: bool = false, + failed: bool = false, + use_default_output: bool = true, + signal_off: bool = false, + signal_output_changed: bool = false, + signal_format_changed: bool = false, + }; + + user_vt: *const audio_interface.IUser, + user_ptr: *anyopaque, + + thread: api.HANDLE = undefined, + output_id: [*:0]u16 = undefined, + output_index: u32 = undefined, + flags: Flags = .{}, + + pub fn init( + adapter: *Adapter, + user_vt: *const audio_interface.IUser, + user_ptr: *anyopaque, + ) bool { + adapter.* = Adapter{ + .user_vt = user_vt, + .user_ptr = user_ptr, + .thread = api.CreateThread(null, 4096, &threadEntry, adapter, 0, null) orelse + return false, + }; + + return true; + } + + pub fn deinit(adapter: *Adapter) void { + @atomicStore(bool, &adapter.flags.signal_off, true, .seq_cst); + _ = api.WaitForSingleObject(adapter.thread, api.INFINITE); + } + + pub fn setOutputIndex(adapter: *Adapter, index: u32) void { + @atomicStore(u32, &adapter.output_index, index, .seq_cst); + @atomicStore(bool, &adapter.flags.signal_output_changed, true, .seq_cst); + } + + pub fn setUseDefaultOutput(adapter: *Adapter, use_default_output: bool) void { + @atomicStore(bool, &adapter.flags.use_default_output, use_default_output, .seq_cst); + @atomicStore(bool, &adapter.flags.signal_output_changed, true, .seq_cst); + } + + fn threadEntry(ptr: ?*anyopaque) callconv(.C) api.DWORD { + const adapter: *Adapter = @alignCast(@ptrCast(ptr)); + @atomicStore(bool, &adapter.flags.on, true, .release); + + var hr: api.HRESULT = api.S_OK; + thread(adapter, &hr) catch {}; + + if (api.FAILED(hr)) { + @atomicStore(bool, &adapter.flags.failed, true, .release); + } + + @atomicStore(bool, &adapter.flags.on, false, .release); + return @bitCast(hr); + } + + fn thread(adapter: *Adapter, hr: *api.HRESULT) !void { + const client: facade.Client = try facade.Client.init(); + + // var output: Output = try Output.init( + // hr, + // &facade, + // @atomicLoad(bool, &adapter.use_default_out, .acquire), + // @atomicLoad(u32, &adapter.out_index, .acquire), + // ); + _ = hr; + + while (@atomicLoad(Request, &adapter.request, .acquire) != Request.turn_off) {} + } +}; + +const facade = struct { + pub const Client = struct { + hr: api.HRESULT, + device_enumerator: *api.IMMDeviceEnumerator, + notif_client: *Notifier, + buf_event: *anyopaque, + }; + + pub const Output = struct { + audio_device: *api.IMMDevice, + audio_client: *api.IAudioClient3, + render_client: *api.IAudioRenderClient, + }; +}; diff --git a/src/platform/windows/wasapiold.zig b/src/platform/windows/wasapiold.zig new file mode 100644 index 0000000..3f8f622 --- /dev/null +++ b/src/platform/windows/wasapiold.zig @@ -0,0 +1,388 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const AtomicOrder = std.builtin.AtomicOrder; + +const audio_interface = @import("audio_interface"); + +const win = @import("win_common.zig"); +const api = win.api; + +const Error = error{ + CreateEvent, + CoInitialize, + CreateDeviceEnumerator, + GetDefaultAudioEndpoint, + RegisterEndpointNotificationCallback, + EnumAudioEndpoints, + DeviceCollectionGetCount, + InvalidOutputDevice, + DeviceCollectionItem, + AudioDeviceGetId, + ActivateAudioClient, + GetMixFormat, + GetSharedModeEnginePeriod, + InitializeSharedAudioStream, +}; + +pub const Adapter = struct { + const Status = enum(u8) { off, on, failed }; + + user_vt: *const audio_interface.IUser, + user_ptr: *anyopaque, + + thread: api.HANDLE = undefined, + status: Status = .off, + + output_id: [*]u16 = undefined, + output_index: u32 = undefined, + use_default_output: bool = true, + + pub fn init(adapter: *Adapter, user_vt: *const audio_interface.IUser, user_ptr: *anyopaque) bool { + adapter.* = .{ + .user_vt = user_vt, + .user_ptr = user_ptr, + .thread = api.CreateThread(null, 4096, &threadEntry, adapter, 0, null) orelse + return false, + }; + + return true; + } + + pub fn deinit(adapter: *Adapter) void { + @atomicStore(bool, &adapter.is_active, false, AtomicOrder.unordered); + _ = api.WaitForSingleObject(adapter.thread, 0); + } + + fn threadEntry(ptr: ?*anyopaque) callconv(.C) api.DWORD { + const adapter: *Adapter = @alignCast(@ptrCast(ptr)); + @atomicStore(Status, &adapter.status, .on, AtomicOrder.unordered); + + const hr: api.HRESULT = thread(adapter); + if (api.FAILED(hr)) { + @atomicStore(Status, &adapter.status, .failed, AtomicOrder.unordered); + return @bitCast(hr); + } + + return 0; + } + + fn thread(adapter: *Adapter, hr: *api.HRESULT) !void { + hr = api.CoInitialize(null); + defer api.CoUninitialize(); + if (api.FAILED(hr)) + return Error.CoInitialize; + + const client: WasapiClient = try WasapiClient.init(hr); + + const output: WasapiOutput = try WasapiOutput.init(hr, &client, adapter); + + // get id of audio device + hr = audio_device.lpVtbl.*.GetId.?(audio_device, &id); + if (api.FAILED(hr)) return Error.AudioDeviceGetId; + errdefer api.CoTaskMemFree(id); + + // get audio client + hr = audio_device.lpVtbl.*.Activate.?(audio_device, @ptrCast(&api.IID_IAudioClient3), api.CLSCTX_ALL, null, @ptrCast(&audio_client)); + if (api.FAILED(hr)) return Error.ActivateAudioClient; + errdefer _ = audio_client.lpVtbl.*.Release(audio_client); + + // negotiate format + var negotiated_fmt: api.WAVEFORMATEX = undefined; + { + var device_fmt: *api.WAVEFORMATEX = undefined; + hr = audio_client.lpVtbl.*.GetMixFormat.?(audio_client, @ptrCast(&device_fmt)); + if (api.FAILED(hr)) return Error.GetMixFormat; + + negotiated_fmt = .{ + .wFormatTag = api.WAVE_FORMAT_IEEE_FLOAT, + .nChannels = device_fmt.nChannels, + .nSamplesPerSec = device_fmt.nSamplesPerSec, + .nAvgBytesPerSec = device_fmt.nSamplesPerSec * device_fmt.nChannels * @sizeOf(f32), + .nBlockAlign = device_fmt.nChannels * @sizeOf(f32), + .wBitsPerSample = @bitSizeOf(f32), + .cbSize = 0, + }; + + api.CoTaskMemFree(device_fmt); + } + + // get default period (the others aren't used) + var default_period: u32 = undefined; + var fundamental_period: u32 = undefined; + var min_period: u32 = undefined; + var max_period: u32 = undefined; + + hr = audio_client.lpVtbl.*.GetSharedModeEnginePeriod.?(audio_client, &negotiated_fmt, &default_period, &fundamental_period, &min_period, &max_period); + if (api.FAILED(hr)) return Error.GetSharedModeEnginePeriod; + + // FINALLY we initialize the audio stream + hr = audio_client.lpVtbl.*.InitializeSharedAudioStream.?(audio_client, api.AUDCLNT_STREAMFLAGS_EVENTCALLBACK, default_period, &negotiated_fmt, null); + if (api.FAILED(hr)) return Error.InitializeSharedAudioStream; + + hr = audio_client.lpVtbl.*.SetEventHandle.?(audio_client, buf_event); + if (api.FAILED(hr)) return hr; + + // Get render client + hr = audio_client.lpVtbl.*.GetService.?(audio_client, @ptrCast(&api.IID_IAudioRenderClient), @ptrCast(&render_client)); + if (api.FAILED(hr)) return hr; + + // Start audio client + hr = audio_client.lpVtbl.*.Start.?(audio_client); + if (api.FAILED(hr)) return hr; + + var buf_frames: u32 = undefined; + hr = audio_client.lpVtbl.*.GetBufferSize.?(audio_client, &buf_frames); + if (api.FAILED(hr)) return hr; + + adapter.channels = negotiated_fmt.nChannels; + adapter.sample_rate = negotiated_fmt.nSamplesPerSec; + + while (true) : ({ + if (@atomicLoad(bool, &adapter.is_active, AtomicOrder.unordered) == false) + break; + }) { + var pad_frames: u32 = undefined; + var buf: [*]f32 = undefined; + + if (api.WaitForSingleObject(buf_event, api.INFINITE) != api.STATUS_WAIT_0) + return win.getLastHresult(); + + hr = audio_client.lpVtbl.*.GetCurrentPadding.?(audio_client, &pad_frames); + if (api.FAILED(hr)) return hr; + + const avail_frames: u32 = buf_frames - pad_frames; + + if (avail_frames == 0) + continue; + + hr = render_client.lpVtbl.*.GetBuffer.?(render_client, avail_frames, @ptrCast(&buf)); + if (api.FAILED(hr)) return hr; + + adapter.vt.render(adapter.ptr, buf[0 .. avail_frames * negotiated_fmt.nChannels]); + + hr = render_client.lpVtbl.*.ReleaseBuffer.?(render_client, avail_frames, 0); + if (api.FAILED(hr)) return hr; + } + + _ = device_enumerator.lpVtbl.*.Release.?(device_enumerator); + + _ = audio_client.lpVtbl.*.Release.?(audio_client); + _ = render_client.lpVtbl.*.Release.?(render_client); + _ = api.CoUninitialize(); + return api.S_OK; + } +}; + +const WasapiClient = struct { + device_enumerator: *api.IMMDeviceEnumerator, + notif_client: *NotifClient, + buf_event: *anyopaque, + + pub fn init(hr: *api.HRESULT) Error!WasapiClient { + var client: WasapiClient = undefined; + + hr = api.CoCreateInstance( + &api.CLSID_MMDeviceEnumerator, + null, + api.CLSCTX_ALL, + &api.IID_IMMDeviceEnumerator, + &client.device_enumerator, + ); + if (api.FAILED(hr)) + return Error.CreateDeviceEnumerator; + + client.notif_client = try NotifClient.init(); + hr = client.device_enumerator.lpVtbl.*.RegisterEndpointNotificationCallback( + client.device_enumerator, + @ptrCast(client.notif_client), + ); + if (api.FAILED(hr)) + return Error.RegisterEndpointNotificationCallback; + + client.buf_event = api.CreateEventW(null, 0, 0, null) orelse + return Error.CreateEvent; + + return client; + } +}; + +const WasapiOutput = struct { + audio_device: *api.IMMDevice, + id: [*:0]u16, + audio_client: *api.IAudioClient3, + render_client: *api.IAudioRenderClient, + + pub fn init(hr: *api.HRESULT, client: *WasapiClient, adapter: *Adapter) Error!WasapiOutput { + var output: WasapiOutput = undefined; + + var use_default_output = @atomicLoad(bool, &adapter.use_default_output, AtomicOrder.acquire); + if (!use_default_output) { + const index = @atomicLoad(u32, &adapter.output_index, AtomicOrder.acquire); + output.audio_device = getNumberedAudioDevice(hr, index) catch |err| switch (err) {}; + } + + if (use_default_output) {} + } + + fn getNumberedAudioDevice(hr: *api.HRESULT, index: u32) Error!*api.IMMDevice { + var device_collection: api.IMMDeviceCollection = undefined; + + hr = client.device_enumerator.lpVtbl.*.EnumAudioEndpoints.?( + client.device_enumerator, + api.eRender, + api.DEVICE_STATE_ACTIVE, + &device_collection, + ); + if (api.FAILED(hr)) + return Error.EnumAudioEndpoints; + + var device_count: u32 = undefined; + + hr = device_collection.lpVtbl.*.GetCount.?( + device_collection, + &device_count, + ); + if (api.FAILED(hr)) + return Error.DeviceCollectionGetCount; + + if (index >= device_count) + return Error.InvalidOutputDevice; + + hr = device_collection.lpVtbl.*.Item.?( + device_collection, + index, + @ptrCast(&output.audio_client), + ); + if (api.FAILED(hr)) + return Error.DeviceCollectionItem; + } + + fn getDefaultAudioDevice( + hr: *api.HRESULT, + device_enumerator: *api.IMMDeviceEnumerator, + ) Error!*api.IMMDevice { + var audio_device: *api.IMMDevice = undefined; + + hr = device_enumerator.lpVtbl.*.GetDefaultAudioEndpoint.?( + device_enumerator, + api.eRender, + api.eConsole, + @ptrCast(&audio_device), + ); + if (api.FAILED(hr)) + return Error.GetDefaultAudioEndpoint; + + return audio_device; + } +}; + +const NotifClient = extern struct { + const c_alloc: Allocator = std.heap.c_allocator; + const imm_notif_client_vt: api.IMMNotificationClientVtbl = .{ + .QueryInterface = &QueryInterface, + .AddRef = &AddRef, + .Release = &Release, + .OnDeviceStateChanged = &OnDeviceStateChanged, + .OnDeviceAdded = &OnDeviceAdded, + .OnDeviceRemoved = &OnDeviceRemoved, + .OnDefaultDeviceChanged = &OnDefaultDeviceChanged, + .OnPropertyValueChanged = &OnPropertyValueChanged, + }; + + imm_client: api.IMMNotificationClient = undefined, + ref_count: u32 = 0, + + pub fn init() !*NotifClient { + var notif: *NotifClient = try c_alloc.create(api.IMMNotificationClient); + errdefer c_alloc.free(notif); + + notif.* = NotifClient{}; + notif.imm_client.lpVtbl = try c_alloc.create(api.IMMNotificationClientVtbl); + notif.imm_client.lpVtbl.* = imm_notif_client_vt; + + return notif; + } + + fn QueryInterface( + ptr: *api.IMMNotificationClient, + iid: *api.IID, + obj: **anyopaque, + ) callconv(.C) api.HRESULT { + if (api.IsEqualIID(iid, &api.IID_IUnknown) || + api.IsEqualIID(iid, &api.IID_IMMNotificationClient)) + { + _ = ptr.AddRef(); + obj.* = ptr; + return api.S_OK; + } else { + obj.* = null; + return api.E_NOINTERFACE; + } + } + + fn AddRef(ptr: *api.IMMNotificationClient) callconv(.C) u32 { + const notif: *NotifClient = @alignCast(@ptrCast(ptr)); + + return api.InterlockedIncrement(¬if.ref_count); + } + + fn Release(ptr: *api.IMMNotificationClient) callconv(.C) u32 { + const notif: *NotifClient = @alignCast(@ptrCast(ptr)); + + const dec: u32 = api.InterlockedDecrement(¬if.ref_count); + if (dec == 0) + c_alloc.free(notif); + + return dec; + } + + fn OnDeviceStateChanged( + ptr: *api.IMMNotificationClient, + id: [*]u16, + state: u32, + ) callconv(.C) api.HRESULT { + // recheck sample rate and channels + _ = ptr; + _ = id; + _ = state; + return api.S_OK; + } + + fn OnDeviceAdded( + _: *api.IMMNotificationClient, + _: [*]u16, + ) callconv(.C) api.HRESULT { + return api.S_OK; + } + + fn OnDeviceRemoved( + _: *api.IMMNotificationClient, + _: [*]u16, + ) callconv(.C) api.HRESULT { + return api.S_OK; + } + + fn OnDefaultDeviceChanged( + client: *api.IMMNotificationClient, + flow: api.EDataFlow, + role: api.ERole, + id: [*]u16, + ) callconv(.C) api.HRESULT { + _ = client; + _ = flow; + _ = role; + _ = id; + return 0; + } + + fn OnPropertyValueChanged( + client: *api.IMMNotificationClient, + id: [*]u16, + key: api.PROPERTYKEY, + ) callconv(.C) api.HRESULT { + _ = client; + _ = id; + _ = key; + return 0; + } +}; diff --git a/src/platform/windows/win_common.zig b/src/platform/windows/win_common.zig new file mode 100644 index 0000000..faf0958 --- /dev/null +++ b/src/platform/windows/win_common.zig @@ -0,0 +1,43 @@ +const std = @import("std"); +const W = std.unicode.utf8ToUtf16LeStringLiteral; + +pub const api = @cImport({ + @cInclude("initguid.h"); + @cInclude("windows.h"); + @cInclude("mmdeviceapi.h"); + @cInclude("audioclient.h"); + @cInclude("strsafe.h"); +}); + +pub fn panic(err: api.DWORD) void { + var buf: [256]u16 = undefined; + + var end: [*]u16 = undefined; + var remaining: usize = undefined; + + _ = api.StringCchPrintfExW( + &buf, + buf.len, + @ptrCast(&end), + &remaining, + 0, + W("Windows error 0x%x: "), + err, + ); + + _ = api.FormatMessageW( + api.FORMAT_MESSAGE_FROM_SYSTEM | api.FORMAT_MESSAGE_IGNORE_INSERTS, + null, + err, + api.MAKELANGID(api.LANG_NEUTRAL, api.SUBLANG_DEFAULT), + end, + @intCast(remaining), + null, + ); + + api.FatalAppExitW(0, &buf); +} + +pub inline fn getLastHresult() api.HRESULT { + return @bitCast(api.GetLastError() | (api.FACILITY_WIN32 << 16) | 0x80000000); +} diff --git a/src/platform/windows/wincon.zig b/src/platform/windows/wincon.zig new file mode 100644 index 0000000..cbff08e --- /dev/null +++ b/src/platform/windows/wincon.zig @@ -0,0 +1,88 @@ +// const std = @import("std"); + +// const win = @import("win_common.zig"); +// const api = win.api; + +// pub const Joystick = struct { +// x: i32, +// y: i32, +// }; + +// pub const Keyboard = struct { +// state: u128 = 0, +// last_pressed: [128]u32 = std.mem.zeroes(), +// last_released: [128]u32 = std.mem.zeroes(), +// }; + +// pub const Mouse = struct { +// pos: Joystick, +// wheel: Joystick, +// }; + +// pub const Adapter = struct { +// stdin_handle: *anyopaque, + +// pub fn init(adapter: *Adapter) bool { +// const console_mode_flags: win.DWORD = +// win.ENABLE_EXTENDED_FLAGS | +// win.ENABLE_PROCESSED_INPUT | +// win.ENABLE_PROCESSED_OUTPUT | +// win.ENABLE_MOUSE_INPUT | +// win.ENABLE_WINDOW_INPUT; + +// adapter.stdin_handle = api.GetStdHandle(api.STD_INPUT_HANDLE) orelse +// return false; + +// if (api.SetConsoleMode(adapter.stdin_handle, console_mode_flags) == 0) +// return false; + +// return true; +// } + +// pub fn readInput(adapter: *Adapter) bool { + +// } +// }; + +// fn inputWorker(platform_void: *anyopaque) callconv(.C) win.DWORD { +// const platform: *Windows = @ptrCast(platform_void); + +// while (true) { +// var event_buf: [6]win.INPUT_RECORD = undefined; +// var events_read: c_ulong = undefined; + +// if (win.ReadConsoleInputW( +// platform.std_handle, +// &event_buf, +// event_buf.len, +// &events_read, +// ) == 0) { +// return hresultFromWin32(win.GetLastError()); +// } + +// var uhh: @import("terminal_input.zig").Controller = 0; + +// for (event_buf[0..events_read]) |*record| { +// switch (record.EventType) { +// win.KEY_EVENT => uhh.keyboardSet(@intCast(record.Event.KeyEvent.wVirtualKeyCode), record.Event.KeyEvent.bKeyDown), +// win.MOUSE_EVENT => switch (record.Event.MouseEvent.dwEventFlags) { +// win.MOUSE_MOVED => { +// uhh.mouse_x = record.Event.MouseEvent.dwMousePosition.X; +// uhh.mouse_y = record.Event.MouseEvent.dwMousePosition.Y; +// }, +// win.MOUSE_WHEELED => { +// uhh.wheel_v = @intCast(@as(i16, (@bitCast(@as(u16, @intCast(record.Event.MouseEvent.dwButtonState >> 16)))))); +// }, +// win.MOUSE_HWHEELED => { +// uhh.wheel_h = @intCast(@as(i16, (@bitCast(@as(u16, @intCast(record.Event.MouseEvent.dwButtonState >> 16)))))); +// }, +// else => {}, +// }, +// else => {}, // implement this! +// } +// } +// } + +// return 0; +// } +// }; diff --git a/src/plugintest.zig b/src/plugintest.zig new file mode 100644 index 0000000..0bfab3a --- /dev/null +++ b/src/plugintest.zig @@ -0,0 +1,4 @@ +const std = @import("std"); +const audio = @import("audio.zig"); + +export fn load() audio.PluginMetadata {} diff --git a/src/time.zig b/src/time.zig deleted file mode 100644 index affa552..0000000 --- a/src/time.zig +++ /dev/null @@ -1,2 +0,0 @@ -pub const mysec32 = u32; -pub const mysec64 = u64; diff --git a/src/wavreader.zig b/src/wavreader.zig deleted file mode 100644 index 8eb820d..0000000 --- a/src/wavreader.zig +++ /dev/null @@ -1,30 +0,0 @@ -struct WavFileHeader { // Offset Size Type Description -// ---------------------------------------------------------------------------------------------------------------- -unsigned RiffChunk; // 0 4 FourCC 'RIFF' -unsigned ChunkSize; // 4 4 DWord size of the riff chunk (should be always filesize - 8) -unsigned FileFormat; // 8 4 FourCC 'WAVE' -unsigned FormatChunk; // 12 4 FourCC 'fmt ' -unsigned FormatSize; // 16 4 DWord size of Format structure (should be always 16 byte) -unsigned short PcmFlags; // 20 2 Word bit 1: Signed data, bit 2: Float data.. -unsigned short Channels; // 22 2 Word samples per frame (example: one stereo frame consist from 2 samples) -unsigned SampleRate; // 24 4 DWord frames per second (example: 44100 stereo-frames are played back per seccond) -unsigned ByteRate; // 28 4 DWord bytes per second (example: one second float32 stereo-track data: 44100frames * 2channels * 4bytes ) -unsigned short BlockAlign; // 32 2 Word byte per frame (example: each float32 stero frame is 8 byte in size - one float is 4byte - 2 channels are 2 floats, each 4byte) -unsigned short BitDepth; // 34 2 Word bits per sample (example: one float32 is 4 byte where each byte has 8 bit... so: 32 bit per sample ) */ -unsigned DataChunk; // 36 4 FourCC 'data' -unsigned DataSize; // 40 4 DWord size of of payload data (should be the total file size minus this headers size of 44 byte) -}; - -const WavHeader = packed struct { - riff_id: [4]u8, // "RIFF" - riff_size: u32, - riff_fmt: [4]u8, // "WAVE" - - fmt_id: [4]u8, // "fmt " - fmt_size: u32, - - audio_format: u16, - channels: u16, - sample_rate: u32, - -}; \ No newline at end of file diff --git a/src/wincon.zig b/src/wincon.zig deleted file mode 100644 index c34baff..0000000 --- a/src/wincon.zig +++ /dev/null @@ -1,103 +0,0 @@ -const win = @cImport(@cInclude("windows.h")); - -const input = @import("input.zig"); - -pub const Platform = struct { - pub const WindowsError = error{ - GetStdHandle, - CreateWaitableTimerW, - ReadConsoleInputW, - SetConsoleMode, - }; - - const max_events = input.EventBuffer.buf_size / 2; - - const console_mode_flags = - win.ENABLE_EXTENDED_FLAGS | - win.ENABLE_PROCESSED_INPUT | - win.ENABLE_PROCESSED_OUTPUT | - win.ENABLE_MOUSE_INPUT | - win.ENABLE_WINDOW_INPUT; - - std_handle: *anyopaque, - timer_handle: *anyopaque, - - pub fn init() WindowsError!Platform { - var platform: Platform = undefined; - - platform.std_handle = win.GetStdHandle(win.STD_INPUT_HANDLE) orelse return WindowsError.GetStdHandle; - platform.timer_handle = win.CreateWaitableTimerW(null, 1, null) orelse return WindowsError.CreateWaitableTimerW; - - if (win.SetConsoleMode(platform.std_handle, console_mode_flags) == 0) { - return WindowsError.SetConsoleMode; - } - - return platform; - } - - pub fn readInput(platform: *Platform, buf: *input.EventBuffer) WindowsError!usize { - var event_buf: [max_events]win.INPUT_RECORD = undefined; - var events_read: c_ulong = undefined; - - if (win.ReadConsoleInputW( - platform.std_handle, - &event_buf, - max_events, - &events_read, - ) == 0) { - return WindowsError.ReadConsoleInputW; - } - - var game_events: usize = 0; - for (event_buf[0..events_read]) |*record| { - game_events += translateEvent(record, buf); - } - - return game_events; - } - - // ugh - fn translateEvent(record: *win.INPUT_RECORD, buf: *input.EventBuffer) usize { - return switch (record.EventType) { - win.KEY_EVENT => { - buf.codes[n] = record.Event.KeyEvent.wVirtualKeyCode; - - buf[0] = input.Event{ - record.Event.KeyEvent.wVirtualKeyCode, - @intCast(record.Event.KeyEvent.bKeyDown), - }; - return 1; - }, - win.MOUSE_EVENT => switch (record.Event.MouseEvent.dwEventFlags) { - win.MOUSE_MOVED => { - buf[0] = input.Event{ - @intFromEnum(VkCode.mouse_x), - record.Event.MouseEvent.dwMousePosition.X, - }; - buf[1] = input.Event{ - @intFromEnum(VkCode.mouse_y), - record.Event.MouseEvent.dwMousePosition.Y, - }; - return 2; - }, - win.MOUSE_WHEELED => { - buf[0] = input.Event{ - @intFromEnum(VkCode.mouse_vwheel), - @as(i16, @bitCast(@as(u16, @intCast(record.Event.MouseEvent.dwButtonState >> 16)))), - }; - return 1; - }, - win.MOUSE_HWHEELED => { - buf[0] = input.Event{ - @intFromEnum(VkCode.mouse_hwheel), - @intCast(record.Event.MouseEvent.dwButtonState), // this is broken, haven't tested - }; - return 1; - }, - else => 0, - }, - win.WINDOW_BUFFER_SIZE_EVENT => unreachable, // implement this!!! - else => 0, - }; - } -}; diff --git a/src/windows.exe b/src/windows.exe deleted file mode 100644 index 3a7e393..0000000 Binary files a/src/windows.exe and /dev/null differ diff --git a/src/windows.exe.obj b/src/windows.exe.obj deleted file mode 100644 index 04b12fa..0000000 Binary files a/src/windows.exe.obj and /dev/null differ diff --git a/src/windows.pdb b/src/windows.pdb deleted file mode 100644 index 42caea2..0000000 Binary files a/src/windows.pdb and /dev/null differ diff --git a/src/windows.zig b/src/windows.zig deleted file mode 100644 index 984c24d..0000000 --- a/src/windows.zig +++ /dev/null @@ -1,231 +0,0 @@ -const std = @import("std"); -const W = std.unicode.utf8ToUtf16LeStringLiteral; - -const win = @cImport({ - @cInclude("windows.h"); - @cInclude("mmdeviceapi.h"); -}); -const wasapi = @cImport(@cInclude("audioclient.h")); - -const CLSID_MMDeviceEnumerator: win.CLSID = .{ .Data1 = 0xbcde0395, .Data2 = 0xe52f, .Data3 = 0x467c, .Data4 = .{ 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } }; -const IID_IMMDeviceEnumerator: win.IID = .{ .Data1 = 0xa95664d2, .Data2 = 0x9614, .Data3 = 0x4f35, .Data4 = .{ 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } }; -const IID_IAudioClient: win.IID = .{ .Data1 = 0x1cb9ad4c, .Data2 = 0xdbfa, .Data3 = 0x4c32, .Data4 = .{ 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x3, 0xb2 } }; -const IID_IAudioClient3: win.IID = .{ .Data1 = 0x7ed4ee07, .Data2 = 0x8e67, .Data3 = 0x4cd4, .Data4 = .{ 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } }; -const IID_IAudioRenderClient: win.IID = .{ .Data1 = 0xf294acfc, .Data2 = 0x3146, .Data3 = 0x4483, .Data4 = .{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; - -pub export fn WindowProc(window: win.HWND, msg: c_uint, w_param: win.WPARAM, l_param: win.LPARAM) callconv(.C) win.LRESULT { - switch (msg) { - win.WM_KEYDOWN => { - std.debug.print("keydown: {}\n", .{w_param}); - }, - else => {}, - } - - return win.DefWindowProcW(window, msg, w_param, l_param); -} - -pub fn main() !void { - var rid: [1]win.RAWINPUTDEVICE = undefined; - rid[0].usUsagePage = 0x01; - rid[0].usUsage = 0x06; - rid[0].dwFlags = 0; // win.RIDEV_INPUTSINK; - rid[0].hwndTarget = null; - - if (win.RegisterRawInputDevices(@ptrCast(&rid), 1, @sizeOf(win.RAWINPUTDEVICE)) == 0) { - std.debug.print("FUCK {}\n", .{win.GetLastError()}); - return; - } - - var message: win.MSG = undefined; - while (win.GetMessageW(&message, null, 0, 0) != 0) { - std.debug.print("meow\n", .{}); - _ = win.TranslateMessage(&message); - _ = win.DispatchMessageW(&message); - } -} - -pub const WindowsAudio = struct { - pub fn init() i32 {} - - fn audioWorker() i32 { - if (failed(win.CoInitialize(null))) |err| return err; - - const buf_event: *anyopaque = win.CreateEventW( - null, - win.FALSE, - win.FALSE, - null, - ) orelse return hresultFromWin32(win.GetLastError()); - - // Get default audio device - var device_enumerator: *win.IMMDeviceEnumerator = undefined; - var audio_device: *win.IMMDevice = undefined; - - if (failed(win.CoCreateInstance( - @ptrCast(&CLSID_MMDeviceEnumerator), - null, - win.CLSCTX_ALL, - @ptrCast(&IID_IMMDeviceEnumerator), - @ptrCast(&device_enumerator), - ))) |err| return err; - - if (failed(device_enumerator.lpVtbl.*.GetDefaultAudioEndpoint.?( - device_enumerator, - win.eRender, - win.eConsole, - @ptrCast(&audio_device), - ))) |err| return err; - - // Initialize audio client - var wasapi_audio_client: *wasapi.IAudioClient3 = undefined; - var device_format: *wasapi.WAVEFORMATEX = undefined; - var default_period: u32 = undefined; - var fundamental_period: u32 = undefined; - var min_period: u32 = undefined; - var max_period: u32 = undefined; - - if (failed(audio_device.lpVtbl.*.Activate.?( - audio_device, - @ptrCast(&IID_IAudioClient3), - win.CLSCTX_ALL, - null, - @ptrCast(&wasapi_audio_client), - ))) |err| return err; - - if (failed(wasapi_audio_client.lpVtbl.*.GetMixFormat.?( - wasapi_audio_client, - @ptrCast(&device_format), - ))) |err| return err; - - const negotiated_format: wasapi.WAVEFORMATEX = .{ - .wFormatTag = wasapi.WAVE_FORMAT_IEEE_FLOAT, - .nChannels = 2, - .nSamplesPerSec = device_format.nSamplesPerSec, - .nAvgBytesPerSec = device_format.nSamplesPerSec * 2 * @sizeOf(f32), - .nBlockAlign = 2 * @sizeOf(f32), - .wBitsPerSample = @bitSizeOf(f32), - .cbSize = 0, - }; - - win.CoTaskMemFree(device_format); - - if (failed(wasapi_audio_client.lpVtbl.*.GetSharedModeEnginePeriod.?( - wasapi_audio_client, - &negotiated_format, - &default_period, - &fundamental_period, - &min_period, - &max_period, - ))) |err| return err; - - if (failed(wasapi_audio_client.lpVtbl.*.InitializeSharedAudioStream.?( - wasapi_audio_client, - wasapi.AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - default_period, - &negotiated_format, - null, - ))) |err| return err; - - if (failed(wasapi_audio_client.lpVtbl.*.SetEventHandle.?( - wasapi_audio_client, - buf_event, - ))) |err| return err; - - // Get render client - var wasapi_render_client: *wasapi.IAudioRenderClient = undefined; - - if (failed(wasapi_audio_client.lpVtbl.*.GetService.?( - wasapi_audio_client, - @ptrCast(&IID_IAudioRenderClient), - @ptrCast(&wasapi_render_client), - ))) |err| return err; - - // Start audio client - var buf_frame_size: u32 = undefined; - - if (failed(wasapi_audio_client.lpVtbl.*.Start.?( - wasapi_audio_client, - ))) |err| return err; - - if (failed(wasapi_audio_client.lpVtbl.*.GetBufferSize.?( - wasapi_audio_client, - &buf_frame_size, - ))) |err| return err; - - // Audio worker loop - var theta: f32 = 0; - - var i: usize = 0; - while (i < (48000 * 5) / buf_frame_size) : (i += 1) { - if (win.WaitForSingleObject(buf_event, win.INFINITE) != win.WAIT_OBJECT_0) { - return hresultFromWin32(win.GetLastError()); - } - - var pad_frame_size: u32 = undefined; - if (failed(wasapi_audio_client.lpVtbl.*.GetCurrentPadding.?( - wasapi_audio_client, - &pad_frame_size, - ))) |err| return err; - - const available_frame_size: u32 = buf_frame_size - pad_frame_size; - - std.debug.print("available: {}\n", .{available_frame_size}); - - if (available_frame_size == 0) - continue; - - var buf: [*]f32 = undefined; - if (failed(wasapi_render_client.lpVtbl.*.GetBuffer.?( - wasapi_render_client, - available_frame_size, - @ptrCast(&buf), - ))) |err| return err; - - var j: usize = 0; - while (j < available_frame_size) : (j += 1) { - theta += 1.0 / 48000.0; - - const sample: f32 = (fastSin(theta * 261.63) + fastSin(theta * 329.63) + fastSin(theta * 390)) * 0.25; - - buf[j * 2] = sample; - buf[j * 2 + 1] = sample; - } - - if (failed(wasapi_render_client.lpVtbl.*.ReleaseBuffer.?( - wasapi_render_client, - available_frame_size, - 0, - ))) |err| return err; - } - - return 0; - } - - inline fn failed(hresult: win.HRESULT) ?i32 { - if (win.FAILED(hresult)) { - return hresult; - } else { - return null; - } - } - - inline fn hresultFromWin32(x: u32) i32 { - if (x == 0) { - return 0; - } else { - return @bitCast((x & 0x0000ffff) | (win.FACILITY_WIN32 << 16) | 0x80000000); - } - } -}; - -fn fastSin(x: f32) f32 { - const sign: f32 = if (x - @floor(x) > 0.5) -1 else 1; - const mod: f32 = (2 * x - @floor(2 * x)) / 2 - 0.25; - const abs: f32 = 1.25 * (1 / (mod * mod + 0.25)) - 4; - - return abs * sign; -} - -// pub fn main() !void { -// std.debug.print("exiting with code: {x}\n", .{@as(u32, @bitCast(WindowsAudio.audioWorker()))}); -// } diff --git a/zig-out/bin/Fumofumotris.exe b/zig-out/bin/Fumofumotris.exe new file mode 100644 index 0000000..f4bf929 Binary files /dev/null and b/zig-out/bin/Fumofumotris.exe differ diff --git a/zig-out/bin/Fumofumotris.pdb b/zig-out/bin/Fumofumotris.pdb new file mode 100644 index 0000000..7c393d8 Binary files /dev/null and b/zig-out/bin/Fumofumotris.pdb differ