Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,212 changes: 2,212 additions & 0 deletions Inventory.zig

Large diffs are not rendered by default.

681 changes: 681 additions & 0 deletions assets.zig

Large diffs are not rendered by default.

318 changes: 318 additions & 0 deletions audio.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
const std = @import("std");

const main = @import("main");
const utils = main.utils;

const c = @cImport({
@cDefine("_BITS_STDIO2_H", ""); // TODO: Zig fails to include this header file
@cInclude("miniaudio.h");
@cDefine("STB_VORBIS_HEADER_ONLY", "");
@cInclude("stb/stb_vorbis.h");
});

fn handleError(miniaudioError: c.ma_result) !void {
if(miniaudioError != c.MA_SUCCESS) {
std.log.err("miniaudio error: {s}", .{c.ma_result_description(miniaudioError)});
return error.miniaudioError;
}
}

const AudioData = struct {
musicId: []const u8,
data: []f32 = &.{},

fn open_vorbis_file_by_id(id: []const u8) ?*c.stb_vorbis {
const colonIndex = std.mem.indexOfScalar(u8, id, ':') orelse {
std.log.err("Invalid music id: {s}. Must be addon:file_name", .{id});
return null;
};
const addon = id[0..colonIndex];
const fileName = id[colonIndex + 1 ..];
const path1 = std.fmt.allocPrintSentinel(main.stackAllocator.allocator, "assets/{s}/music/{s}.ogg", .{addon, fileName}, 0) catch unreachable;
defer main.stackAllocator.free(path1);
var err: c_int = 0;
if(c.stb_vorbis_open_filename(path1.ptr, &err, null)) |ogg_stream| return ogg_stream;
const path2 = std.fmt.allocPrintSentinel(main.stackAllocator.allocator, "{s}/serverAssets/{s}/music/{s}.ogg", .{main.files.cubyzDirStr(), addon, fileName}, 0) catch unreachable;
defer main.stackAllocator.free(path2);
if(c.stb_vorbis_open_filename(path2.ptr, &err, null)) |ogg_stream| return ogg_stream;
std.log.err("Couldn't find music with id \"{s}\". Searched path \"{s}\" and \"{s}\"", .{id, path1, path2});
return null;
}

fn init(musicId: []const u8) *AudioData {
const self = main.globalAllocator.create(AudioData);
self.* = .{.musicId = main.globalAllocator.dupe(u8, musicId)};

const channels = 2;
if(open_vorbis_file_by_id(musicId)) |ogg_stream| {
defer c.stb_vorbis_close(ogg_stream);
const ogg_info: c.stb_vorbis_info = c.stb_vorbis_get_info(ogg_stream);
const samples = c.stb_vorbis_stream_length_in_samples(ogg_stream);
if(sampleRate != @as(f32, @floatFromInt(ogg_info.sample_rate))) {
const tempData = main.stackAllocator.alloc(f32, samples*channels);
defer main.stackAllocator.free(tempData);
_ = c.stb_vorbis_get_samples_float_interleaved(ogg_stream, channels, tempData.ptr, @as(c_int, @intCast(samples))*ogg_info.channels);
var stepWidth = @as(f32, @floatFromInt(ogg_info.sample_rate))/sampleRate;
const newSamples: usize = @intFromFloat(@as(f32, @floatFromInt(tempData.len/2))/stepWidth);
stepWidth = @as(f32, @floatFromInt(samples))/@as(f32, @floatFromInt(newSamples));
self.data = main.globalAllocator.alloc(f32, newSamples*channels);
for(0..newSamples) |s| {
const samplePosition = @as(f32, @floatFromInt(s))*stepWidth;
const firstSample: usize = @intFromFloat(@floor(samplePosition));
const interpolation = samplePosition - @floor(samplePosition);
for(0..channels) |ch| {
if(firstSample >= samples - 1) {
self.data[s*channels + ch] = tempData[(samples - 1)*channels + ch];
} else {
self.data[s*channels + ch] = tempData[firstSample*channels + ch]*(1 - interpolation) + tempData[(firstSample + 1)*channels + ch]*interpolation;
}
}
}
} else {
self.data = main.globalAllocator.alloc(f32, samples*channels);
_ = c.stb_vorbis_get_samples_float_interleaved(ogg_stream, channels, self.data.ptr, @as(c_int, @intCast(samples))*ogg_info.channels);
}
} else {
self.data = main.globalAllocator.alloc(f32, channels);
@memset(self.data, 0);
}
return self;
}

fn deinit(self: *const AudioData) void {
main.globalAllocator.free(self.data);
main.globalAllocator.free(self.musicId);
main.globalAllocator.destroy(self);
}

pub fn hashCode(self: *const AudioData) u32 {
var result: u32 = 0;
for(self.musicId) |char| {
result = result + char;
}
return result;
}

pub fn equals(self: *const AudioData, _other: ?*const AudioData) bool {
if(_other) |other| {
return std.mem.eql(u8, self.musicId, other.musicId);
} else return false;
}
};

var activeTasks: main.ListUnmanaged([]const u8) = .{};
var taskMutex: std.Thread.Mutex = .{};

var musicCache: utils.Cache(AudioData, 4, 4, AudioData.deinit) = .{};

fn findMusic(musicId: []const u8) ?[]f32 {
{
taskMutex.lock();
defer taskMutex.unlock();
if(musicCache.find(AudioData{.musicId = musicId}, null)) |musicData| {
return musicData.data;
}
for(activeTasks.items) |taskFileName| {
if(std.mem.eql(u8, musicId, taskFileName)) {
return null;
}
}
}
MusicLoadTask.schedule(musicId);
return null;
}

const MusicLoadTask = struct {
musicId: []const u8,

const vtable = utils.ThreadPool.VTable{
.getPriority = main.meta.castFunctionSelfToAnyopaque(getPriority),
.isStillNeeded = main.meta.castFunctionSelfToAnyopaque(isStillNeeded),
.run = main.meta.castFunctionSelfToAnyopaque(run),
.clean = main.meta.castFunctionSelfToAnyopaque(clean),
.taskType = .misc,
};

pub fn schedule(musicId: []const u8) void {
const task = main.globalAllocator.create(MusicLoadTask);
task.* = MusicLoadTask{
.musicId = main.globalAllocator.dupe(u8, musicId),
};
main.threadPool.addTask(task, &vtable);
taskMutex.lock();
defer taskMutex.unlock();
activeTasks.append(main.globalAllocator, task.musicId);
}

pub fn getPriority(_: *MusicLoadTask) f32 {
return std.math.floatMax(f32);
}

pub fn isStillNeeded(_: *MusicLoadTask) bool {
return true;
}

pub fn run(self: *MusicLoadTask) void {
defer self.clean();
const data = AudioData.init(self.musicId);
const hasOld = musicCache.addToCache(data, data.hashCode());
if(hasOld) |old| {
old.deinit();
}
}

pub fn clean(self: *MusicLoadTask) void {
taskMutex.lock();
var index: usize = 0;
while(index < activeTasks.items.len) : (index += 1) {
if(activeTasks.items[index].ptr == self.musicId.ptr) break;
}
_ = activeTasks.swapRemove(index);
taskMutex.unlock();
main.globalAllocator.free(self.musicId);
main.globalAllocator.destroy(self);
}
};

// TODO: Proper sound and music system

var device: c.ma_device = undefined;

var sampleRate: f32 = 0;

pub fn init() error{miniaudioError}!void {
var config = c.ma_device_config_init(c.ma_device_type_playback);
config.playback.format = c.ma_format_f32;
config.playback.channels = 2;
config.sampleRate = 44100;
config.dataCallback = &miniaudioCallback;
config.pUserData = undefined;

try handleError(c.ma_device_init(null, &config, &device));

try handleError(c.ma_device_start(&device));

sampleRate = 44100;
}

pub fn deinit() void {
handleError(c.ma_device_stop(&device)) catch {};
c.ma_device_uninit(&device);
mutex.lock();
defer mutex.unlock();
main.threadPool.closeAllTasksOfType(&MusicLoadTask.vtable);
musicCache.clear();
activeTasks.deinit(main.globalAllocator);
main.globalAllocator.free(preferredMusic);
preferredMusic.len = 0;
main.globalAllocator.free(activeMusicId);
activeMusicId.len = 0;
}

const currentMusic = struct {
var buffer: []const f32 = undefined;
var animationAmplitude: f32 = undefined;
var animationVelocity: f32 = undefined;
var animationDecaying: bool = undefined;
var animationProgress: f32 = undefined;
var interpolationPolynomial: [4]f32 = undefined;
var pos: u32 = undefined;

fn init(musicBuffer: []const f32) void {
buffer = musicBuffer;
animationAmplitude = 0;
animationVelocity = 0;
animationDecaying = false;
animationProgress = 0;
interpolationPolynomial = utils.unitIntervalSpline(f32, animationAmplitude, animationVelocity, 1, 0);
pos = 0;
}

fn evaluatePolynomial() void {
const t = animationProgress;
const t2 = t*t;
const t3 = t2*t;
const a = interpolationPolynomial;
animationAmplitude = a[0] + a[1]*t + a[2]*t2 + a[3]*t3; // value
animationVelocity = a[1] + 2*a[2]*t + 3*a[3]*t2;
}
};

var activeMusicId: []const u8 = &.{};
var partialFrame: f32 = 0;
const animationLengthInSeconds = 5.0;

var curIndex: u16 = 0;
var curEndIndex: std.atomic.Value(u16) = .{.value = sampleRate/60 & ~@as(u16, 1)};

var mutex: std.Thread.Mutex = .{};
var preferredMusic: []const u8 = "";

pub fn setMusic(music: []const u8) void {
mutex.lock();
defer mutex.unlock();
if(std.mem.eql(u8, music, preferredMusic)) return;
main.globalAllocator.free(preferredMusic);
preferredMusic = main.globalAllocator.dupe(u8, music);
}

fn addMusic(buffer: []f32) void {
mutex.lock();
defer mutex.unlock();
if(!std.mem.eql(u8, preferredMusic, activeMusicId)) {
if(activeMusicId.len == 0) {
if(findMusic(preferredMusic)) |musicBuffer| {
currentMusic.init(musicBuffer);
main.globalAllocator.free(activeMusicId);
activeMusicId = main.globalAllocator.dupe(u8, preferredMusic);
}
} else if(!currentMusic.animationDecaying) {
_ = findMusic(preferredMusic); // Start loading the next music into the cache ahead of time.
currentMusic.animationDecaying = true;
currentMusic.animationProgress = 0;
currentMusic.interpolationPolynomial = utils.unitIntervalSpline(f32, currentMusic.animationAmplitude, currentMusic.animationVelocity, 0, 0);
}
} else if(currentMusic.animationDecaying) { // We returned to the biome before the music faded away.
currentMusic.animationDecaying = false;
currentMusic.animationProgress = 0;
currentMusic.interpolationPolynomial = utils.unitIntervalSpline(f32, currentMusic.animationAmplitude, currentMusic.animationVelocity, 1, 0);
}
if(activeMusicId.len == 0) return;

// Copy the music to the buffer.
var i: usize = 0;
while(i < buffer.len) : (i += 2) {
currentMusic.animationProgress += 1.0/(animationLengthInSeconds*sampleRate);
var amplitude: f32 = main.settings.musicVolume;
if(currentMusic.animationProgress > 1) {
if(currentMusic.animationDecaying) {
main.globalAllocator.free(activeMusicId);
activeMusicId = &.{};
amplitude = 0;
}
} else {
currentMusic.evaluatePolynomial();
amplitude *= currentMusic.animationAmplitude;
}
buffer[i] += amplitude*currentMusic.buffer[currentMusic.pos];
buffer[i + 1] += amplitude*currentMusic.buffer[currentMusic.pos + 1];
currentMusic.pos += 2;
if(currentMusic.pos >= currentMusic.buffer.len) {
currentMusic.pos = 0;
}
}
}

fn miniaudioCallback(
maDevice: ?*anyopaque,
output: ?*anyopaque,
input: ?*const anyopaque,
frameCount: u32,
) callconv(.c) void {
_ = input;
_ = maDevice;
const valuesPerBuffer = 2*frameCount; // Stereo
const buffer = @as([*]f32, @ptrCast(@alignCast(output)))[0..valuesPerBuffer];
@memset(buffer, 0);
addMusic(buffer);
}
Loading