Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
6d7e9e3
Merge branch 'master' into esefeks
RanPix Mar 22, 2026
f6596ce
play sounds and load them from assets
RanPix Mar 22, 2026
4023a02
convert all bad ogg files to good ogg files and fix memory leak
RanPix Mar 23, 2026
ba74fc8
rename sound setting to audio, add sound volume slider, play multiple…
RanPix Mar 23, 2026
cae0ac4
fix crash while playing multiple sounds
RanPix Mar 23, 2026
986af32
add active sound count to debug menu
RanPix Mar 23, 2026
d364d90
play sound only on actual jump
RanPix Mar 23, 2026
00fa770
fix crash when playing alot of sounds
RanPix Mar 23, 2026
369f237
fixed sound clicking and add volume control for sound type
RanPix Mar 23, 2026
b082e90
oopsie hardcoded path
RanPix Mar 23, 2026
66ea594
change sounds and tweak volume
RanPix Mar 23, 2026
59a71a6
add mono channel support
RanPix Mar 23, 2026
a819070
everything works
RanPix Mar 23, 2026
090cdde
Merge remote-tracking branch 'upstream/master' into esefeks
RanPix Apr 4, 2026
40e76aa
Merge remote-tracking branch 'upstream/master' into esefeks
RanPix Apr 5, 2026
a469cf3
Merge remote-tracking branch 'upstream/master' into esefeks
RanPix Apr 5, 2026
d4ad470
Merge branch 'master' into esefeks
RanPix Jun 21, 2026
4449ef9
back to master
RanPix Jun 21, 2026
7f154fc
cleanup and reorder
RanPix Jun 21, 2026
d7e745b
rename
RanPix Jun 21, 2026
7d8ed9b
add spatial sound
RanPix Jun 21, 2026
731974b
deinit sound map properly
RanPix Jun 24, 2026
70b9356
Merge remote-tracking branch 'origin/master' into esefeks
RanPix Jun 24, 2026
eb74150
improve error message
RanPix Jun 24, 2026
be2f60c
fmt
RanPix Jun 24, 2026
1d8cf49
Merge branch 'master' into esefeks
RanPix Jul 4, 2026
d2da43a
dont process sounds that are too far
RanPix Jul 14, 2026
92fcc3f
Merge branch 'master' into esefeks
RanPix Jul 14, 2026
f0781a4
allow loading audio files explicitly in sound asset files
RanPix Jul 15, 2026
943129d
tiny cleanup
RanPix Jul 15, 2026
22329c6
fmt
RanPix Jul 15, 2026
5a12f1b
add to reset
RanPix Jul 15, 2026
5f209d7
remove fixme
RanPix Jul 15, 2026
0de8631
Merge remote-tracking branch 'origin/master' into esefeks
RanPix Jul 15, 2026
8de387a
rename sounds in settings to audio
RanPix Jul 15, 2026
99ab682
refactor
RanPix Jul 15, 2026
0f6fc28
fmt
RanPix Jul 15, 2026
5c64c2d
remove cast
RanPix Jul 15, 2026
368835a
Merge branch 'smallAudioZigRefactor' into esefeks
RanPix Jul 15, 2026
adf1f8b
oops
RanPix Jul 15, 2026
e108ca9
Merge branch 'smallAudioZigRefactor' into esefeks
RanPix Jul 15, 2026
c902189
Merge remote-tracking branch 'origin/master' into smallAudioZigRefactor
RanPix Jul 19, 2026
79dee6c
review changes
RanPix Jul 19, 2026
e97295b
rename enum
RanPix Jul 19, 2026
ee0e6db
Merge remote-tracking branch 'origin/master' into esefeks
RanPix Jul 19, 2026
20d8d60
Merge branch 'smallAudioZigRefactor' into esefeks
RanPix Jul 19, 2026
241bf5b
add enum value
RanPix Jul 19, 2026
99b9fa0
Merge commit 'b9d3733366d6878b734b4732bcb0c58a70b86b98' into smallAud…
RanPix Jul 19, 2026
72087ec
Merge branch 'smallAudioZigRefactor' into esefeks
RanPix Jul 19, 2026
d5714c0
test and move to enum
RanPix Jul 19, 2026
d6d4fbd
error fixes or whatever
RanPix Jul 19, 2026
5c8d86f
remove no error check
RanPix Jul 19, 2026
092ceb7
now its good
RanPix Jul 19, 2026
7247c70
fixessss
RanPix Jul 19, 2026
09083b0
Merge branch 'smallAudioZigRefactor' into esefeks
RanPix Jul 19, 2026
f6dfb8e
fix sounds
RanPix Jul 19, 2026
35a4ed4
Merge remote-tracking branch 'origin/master' into esefeks
RanPix Jul 19, 2026
7516242
Merge remote-tracking branch 'origin/master' into esefeks
RanPix Jul 25, 2026
f5e5c18
lock the thread on adding
RanPix Jul 25, 2026
e4aaf90
swaped over to swapremove
Crepestrom Sep 20, 2026
804d80a
compressed inputs into struct
Crepestrom Sep 20, 2026
79dcfea
oops update that back
Crepestrom Sep 20, 2026
0ef870d
Merge branch 'master' into esefeks-sfx-crepestrom-additions
Crepestrom Sep 20, 2026
52e3f1c
oops was loading wrong version
Crepestrom Sep 20, 2026
56c0cca
Sounds are now in a cache rather than a different structure
Crepestrom Sep 20, 2026
d4bca68
removed unused code
Crepestrom Sep 20, 2026
4854565
formatting
Crepestrom Sep 20, 2026
cd55784
formatting
Crepestrom Sep 20, 2026
4097f5d
formting
Crepestrom Sep 20, 2026
28af153
tab
Crepestrom Sep 20, 2026
852ec87
now is always converted into mono
Crepestrom Sep 20, 2026
c774d0b
Update src/gui/windows/audio.zig
Crepestrom Oct 1, 2026
b1c0186
Merge branch 'master' into esefeks-sfx-crepestrom-additions
Crepestrom Oct 1, 2026
7a20a55
removed readAllAudio
Crepestrom Oct 1, 2026
9ac76c1
wonder why this was even added
Crepestrom Oct 1, 2026
a6b1eec
Merge branch 'master' into esefeks-sfx-crepestrom-additions
Crepestrom Oct 4, 2026
3a22cea
no conversion to mono but throws a error
Crepestrom Oct 4, 2026
a116a20
formatting
Crepestrom Oct 4, 2026
18eec2a
formatting
Crepestrom Oct 4, 2026
0a722b2
sound effects are downloaded from assets
Crepestrom Oct 7, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ test.png
!/assets/cubyz
!/assets/backgrounds/default_background.png
/assets/cubyz/music
/assets/cubyz/sounds/audio
/assets/cubyz/fonts
Binary file added assets/cubyz/sounds/audio/block_break.ogg
Binary file not shown.
Binary file added assets/cubyz/sounds/audio/block_hit.ogg
Binary file not shown.
Binary file added assets/cubyz/sounds/audio/jump.ogg
Binary file not shown.
4 changes: 4 additions & 0 deletions assets/cubyz/sounds/block_break.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.{
.audio = "cubyz:block_break",
.volume = 1.25,
}
4 changes: 4 additions & 0 deletions assets/cubyz/sounds/block_hit.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.{
.audio = "cubyz:block_hit",
.volume = 0.25,
}
4 changes: 4 additions & 0 deletions assets/cubyz/sounds/jump.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.{
.audio = "cubyz:jump",
.volume = 0.35,
}
5 changes: 5 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ pub fn build(b: *std.Build) !void {
.install_subdir = "assets/cubyz/music/",
.install_dir = .{.custom = ".."},
});
b.installDirectory(.{
.source_dir = largeAssets.path("sounds"),
.install_subdir = "assets/cubyz/sounds/audio/",
.install_dir = .{.custom = ".."},
});
b.installDirectory(.{
.source_dir = largeAssets.path("fonts"),
.install_subdir = "assets/cubyz/fonts/",
Expand Down
16 changes: 14 additions & 2 deletions src/assets.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const migrations = @import("migrations.zig");
const blueprint = @import("blueprint.zig");
const Blueprint = blueprint.Blueprint;
const particles = @import("particles.zig");
const audio = @import("audio.zig");
const ZonElement = main.ZonElement;
const biomes = main.server.terrain.biomes;
const sbb = main.server.terrain.sbb;
Expand Down Expand Up @@ -42,6 +43,7 @@ pub const Assets = struct { // MARK: Assets
worldPresets: ZonHashMap,
entityModelDescriptions: ZonHashMap,
entityModelMigrations: ZonHashMap,
sounds: ZonHashMap,

fn init() Assets {
return .{
Expand All @@ -65,6 +67,7 @@ pub const Assets = struct { // MARK: Assets
.worldPresets = .{},
.entityModelDescriptions = .{},
.entityModelMigrations = .{},
.sounds = .{},
};
}
fn deinit(self: *Assets, allocator: NeverFailingAllocator) void {
Expand All @@ -88,6 +91,7 @@ pub const Assets = struct { // MARK: Assets
self.worldPresets.deinit(allocator.allocator);
self.entityModelDescriptions.deinit(allocator.allocator);
self.entityModelMigrations.deinit(allocator.allocator);
self.sounds.deinit(allocator.allocator);
}
fn clone(self: Assets, allocator: NeverFailingAllocator) Assets {
return .{
Expand All @@ -111,6 +115,7 @@ pub const Assets = struct { // MARK: Assets
.worldPresets = .{}, // Not accessible inside the world
.entityModelDescriptions = self.entityModelDescriptions.clone(allocator.allocator) catch unreachable,
.entityModelMigrations = self.entityModelMigrations.clone(allocator.allocator) catch unreachable,
.sounds = self.sounds.clone(allocator.allocator) catch unreachable,
};
}
fn read(self: *Assets, allocator: NeverFailingAllocator, assetDir: main.files.Dir, assetPath: []const u8) void {
Expand All @@ -133,12 +138,13 @@ pub const Assets = struct { // MARK: Assets
addon.readAllZon(allocator, "particles", true, &self.particles, null);
addon.readAllZon(allocator, "world_presets", true, &self.worldPresets, null);
addon.readAllZon(allocator, "entity_models", true, &self.entityModelDescriptions, &self.entityModelMigrations);
addon.readAllZon(allocator, "sounds", true, &self.sounds, null);
}
}
fn log(self: *Assets, typ: enum { common, world }) void {
std.log.info(
"Finished {s} assets reading with {} blocks, {} items, {} procedural items, {} biomes, {} cave layers, {} structure tables, {} recipes, {} structure building blocks, {} blueprints, {} particles, {} world presets, block models {}, and {} block model ZONs",
.{@tagName(typ), self.blocks.count(), self.items.count(), self.proceduralItems.count(), self.biomes.count(), self.caveLayers.count(), self.structureTables.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count(), self.worldPresets.count(), self.blockModels.count(), self.blockModelsZon.count()},
"Finished {s} assets reading with {} blocks, {} items, {} procedural items, {} biomes, {} cave layers, {} structure tables, {} recipes, {} structure building blocks, {} blueprints, {} particles, {} world presets, block models {}, {} block model ZONs, and {} sounds",
.{@tagName(typ), self.blocks.count(), self.items.count(), self.proceduralItems.count(), self.biomes.count(), self.caveLayers.count(), self.structureTables.count(), self.recipes.count(), self.structureBuildingBlocks.count(), self.blueprints.count(), self.particles.count(), self.worldPresets.count(), self.blockModels.count(), self.blockModelsZon.count(), self.sounds.count()},
);
}

Expand Down Expand Up @@ -724,6 +730,11 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, itemPale
particles.ParticleManager.register(assetFolder, entry.key_ptr.*, entry.value_ptr.*);
}

iterator = worldAssets.sounds.iterator();
while (iterator.next()) |entry| {
audio.registerSound(assetFolder, entry.key_ptr.*, entry.value_ptr.*);
}

// Biomes:
var nextBiomeNumericId: u32 = 0;
for (biomePalette.palette.items) |id| {
Expand Down Expand Up @@ -806,6 +817,7 @@ pub fn unloadAssets() void { // MARK: unloadAssets()
main.server.terrain.structures.reset();
main.models.reset();
main.particles.ParticleManager.reset();
main.audio.reset();
main.rotation.reset();
main.Tag.resetTags();
main.entityModel.reset();
Expand Down
171 changes: 171 additions & 0 deletions src/audio.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const std = @import("std");

const main = @import("main");
const vec = main.vec;
const Vec3f = vec.Vec3f;
const utils = main.utils;
const ZonElement = @import("zon.zig").ZonElement;

const c = @import("c");

Expand Down Expand Up @@ -138,6 +141,7 @@ var activeTasks: main.List([]const u8) = .empty; // MARK: Music
var taskMutex: main.utils.Mutex = .{};

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

fn findMusic(musicId: []const u8) ?[]f32 {
{
Expand All @@ -156,6 +160,28 @@ fn findMusic(musicId: []const u8) ?[]f32 {
return null;
}

fn findSound(musicId: []const u8) ?*AudioData {
{
taskMutex.lock();
defer taskMutex.unlock();
if (audioCache.find(AudioData{.audioId = musicId}, null)) |musicData| {
return musicData;
}
const data = AudioData.init(musicId, "sounds/audio");
// we convert to mono for easier audio processing
if (data.channelType == .stereo) {
std.log.err("Sound effects must be mono. Error on {s}", .{musicId});
return null;
}
const hasOld = audioCache.addToCache(data, data.hashCode());
if (hasOld) |old| {
old.deinit();
}
return data;
}
return null;
}

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

Expand Down Expand Up @@ -236,11 +262,22 @@ pub fn deinit() void {
defer mutex.unlock();
main.threadPool.closeAllTasksOfType(&MusicLoadTask.vtable);
musicCache.clear();
audioCache.clear();
activeTasks.deinit(main.globalAllocator);
main.globalAllocator.free(preferredMusic);
preferredMusic.len = 0;
main.globalAllocator.free(activeMusicId);
activeMusicId.len = 0;

soundDataIdMap.deinit(main.globalAllocator.allocator);
soundDatas.deinit(main.globalAllocator);
activeSounds.deinit(main.globalAllocator);
}

pub fn reset() void {
activeSounds.clearRetainingCapacity();
soundDataIdMap.clearRetainingCapacity();
soundDatas.clearRetainingCapacity();
}

const currentMusic = struct {
Expand Down Expand Up @@ -286,6 +323,77 @@ pub fn setMusic(music: []const u8) void {
preferredMusic = main.globalAllocator.dupe(u8, music);
}

const SoundData = struct { // MARK: Sounds
audioName: []const u8,
volume: f32 = 1,
};

const PlayingSound = struct {
pos: Vec3f = @splat(0),
audioName: []const u8,
bufPos: u32 = 0,

volume: f32 = 1,
maxDistance: f32 = 10,
isSpatial: bool = false,
};

var soundDataIdMap: std.StringHashMapUnmanaged(u32) = .{};
var soundDatas: main.List(SoundData) = .empty;
var activeSounds: main.List(PlayingSound) = .empty;

pub fn getActiveSoundCount() u32 {
return @intCast(activeSounds.items.len);
}

pub fn registerSound(assetsFolder: []const u8, id: []const u8, zon: ZonElement) void {
const audioId = zon.get([]const u8, "audio") orelse {
std.log.err("Error: Sound Data audio was not specified: {s} ({s})", .{id, assetsFolder});
return;
};
if (findSound(audioId) == null) {
std.log.err("Error: Sound Data audio was not found: {s} ({s})", .{id, assetsFolder});
return;
}

soundDataIdMap.put(main.globalAllocator.allocator, id, @intCast(soundDatas.items.len)) catch unreachable;
soundDatas.append(main.globalAllocator, SoundData{
.audioName = audioId,
.volume = zon.get(f32, "volume") orelse 1.0,
});

std.log.debug("Registered sound data: {s}", .{id});
}

const AddSoundParameters = struct { // MARK: Sounds
isSpatial: bool,
maxDistance: f32,
pos: Vec3f,
};

pub fn addSound(id: []const u8, soundParameters: AddSoundParameters) void {
mutex.lock();
defer mutex.unlock();

const idx = soundDataIdMap.get(id) orelse return;
const soundData = soundDatas.items[idx];
activeSounds.append(main.globalAllocator, PlayingSound{
.audioName = soundData.audioName,
.volume = soundData.volume,
.pos = soundParameters.pos,
.isSpatial = soundParameters.isSpatial,
.maxDistance = soundParameters.maxDistance,
});
}

pub fn playSound(id: []const u8) void {
addSound(id, .{.pos = @splat(0), .maxDistance = 0, .isSpatial = false});
}

pub fn playSpatialSound(id: []const u8, pos: Vec3f, maxDistance: f32) void {
addSound(id, .{.pos = pos, .maxDistance = maxDistance, .isSpatial = true});
}

fn mixMusic(buffer: []f32) void {
mutex.lock();
defer mutex.unlock();
Expand Down Expand Up @@ -333,6 +441,68 @@ fn mixMusic(buffer: []f32) void {
}
}

fn mixSound(buffer: []f32) void {
mutex.lock();
defer mutex.unlock();

if (activeSounds.items.len == 0) return;

const playerPos: Vec3f = @floatCast(main.game.Player.getPosBlocking());
const playerForward = main.game.camera.direction;
const playerRight = vec.normalize(vec.cross(playerForward, Vec3f{0, 0, 1}));

var i: u32 = 0;
main: while (i < activeSounds.items.len) {
var sound = activeSounds.items[i];
const audioData = findSound(sound.audioName) orelse continue :main;
const soundBuffer = audioData.data;

var leftVol: f32 = 1;
var rightVol: f32 = 1;

if (sound.isSpatial) {
const toSound = sound.pos - playerPos;
const distance: f32 = vec.length(toSound);

if (distance > sound.maxDistance) {
sound.bufPos += @intCast(if (audioData.channelType == .mono) @divFloor(buffer.len, 2) else buffer.len);
if (sound.bufPos >= soundBuffer.len) {
_ = activeSounds.swapRemove(i);
continue :main;
}
}

const pan: f32 = vec.dot(toSound/@as(Vec3f, @splat(distance)), playerRight);

const angle = (pan + 1)*0.25*std.math.pi;
leftVol = @cos(angle);
rightVol = @sin(angle);

var volume: f32 = 1 - distance/sound.maxDistance;

volume = if (volume < 0) 0 else volume;
leftVol *= volume;
rightVol *= volume;
}

var j: usize = 0;
while (j < buffer.len) : (j += 2) {
const amplitude: f32 = main.settings.soundVolume*sound.volume;

buffer[j] += soundBuffer[sound.bufPos]*amplitude*leftVol;
buffer[j + 1] += soundBuffer[sound.bufPos]*amplitude*rightVol;
sound.bufPos += 1;

if (sound.bufPos >= soundBuffer.len) {
_ = activeSounds.swapRemove(i);
continue :main;
}
}
activeSounds.items[i] = sound;
i += 1;
}
}

fn miniaudioCallback(
maDevice: ?*anyopaque,
output: ?*anyopaque,
Expand All @@ -345,4 +515,5 @@ fn miniaudioCallback(
const buffer = @as([*]f32, @ptrCast(@alignCast(output)))[0..valuesPerBuffer];
@memset(buffer, 0);
mixMusic(buffer);
mixSound(buffer);
}
9 changes: 9 additions & 0 deletions src/game.zig
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ pub fn getBlockWithSide(comptime side: main.sync.Side, x: i32, y: i32, z: i32) ?
}
}

var timeing: f64 = 0;

pub fn update(deltaTime: f64) void { // MARK: update()
if (world.?.shouldRestart.load(.acquire)) {
restart();
Expand Down Expand Up @@ -650,6 +652,12 @@ pub fn update(deltaTime: f64) void { // MARK: update()
const right = Vec3d{-horizontalForward[1], horizontalForward[0], 0};
var movementDir: Vec3d = .{0, 0, 0};

timeing += deltaTime;
if (timeing > 0.5) {
main.audio.playSpatialSound("cubyz:block_break", Vec3f{0, 0, 0}, 20);
timeing = 0;
}

if (main.Window.grabbed) {
const walkingSpeed: f64 = if (Player.crouching) 2.5 else 4.5;
var movementSpeed: f64 = walkingSpeed*@min(1, vec.length(Vec2f{
Expand Down Expand Up @@ -707,6 +715,7 @@ pub fn update(deltaTime: f64) void { // MARK: update()
movementDir[2] += 5.5;
}
} else if ((Player.onGround or Player.jumpCoyote > 0.0) and Player.jumpCooldown <= 0) {
main.audio.playSound("cubyz:jump");
jumping = true;
Player.jumpCooldown = Player.jumpCooldownConstant;
if (!Player.onGround) {
Expand Down
12 changes: 12 additions & 0 deletions src/gui/windows/audio.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ fn musicCallback(newValue: f32) void {
settings.save();
}

fn soundCallback(newValue: f32) void {
settings.soundVolume = deziBelToLinear(newValue);
settings.save();
}

fn deziBelToLinear(x: f32) f32 {
if (x < -59.95) return 0;
return std.math.pow(f32, 10, x/20);
Expand All @@ -39,11 +44,18 @@ fn musicFormatter(allocator: NeverFailingAllocator, value: f32) []const u8 {
return allocator.print("Music volume:", .{});
}

fn soundFormatter(allocator: NeverFailingAllocator, value: f32) []const u8 {
const percentage = 100*deziBelToLinear(value);
if (percentage == 0) return allocator.dupe(u8, "Sound volume: Off");
return allocator.dupe(u8, "Sound volume:");
}

const padding: f32 = 8;

pub fn onOpen() void {
const list = VerticalList.init(.{padding, 16 + padding}, 300, 16);
list.add(ContinuousSlider.init(.{0, 0}, 128, -60, 0, linearToDezibel(settings.musicVolume), &musicCallback, &musicFormatter));
list.add(ContinuousSlider.init(.{0, 0}, 128, -60, 0, linearToDezibel(settings.soundVolume), &soundCallback, &soundFormatter));
list.finish(.center);
window.rootComponent = list.toComponent();
window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding));
Expand Down
Loading
Loading