njin có hai loại âm thanh, cho hai nhu cầu khác nhau:
| Sound | Music |
| Dùng cho | Hiệu ứng ngắn: tiếng bắn, nhấp, va chạm | Nhạc nền, âm thanh môi trường dài |
| Lưu ở đâu | Nằm hết trong bộ nhớ | Stream từ đĩa, giải mã từng đoạn |
| Phát chồng nhiều bản | Có (tối đa 8) | Không, một bài một luồng |
| Lặp | Có, nhưng chỗ nối có quãng lặng cỡ một frame | Có, không có quãng lặng |
| Hàm | sound_* | music_* |
Cả hai dùng handle giống texture và shader: xem Vẽ ảnh và shader.
Ví dụ
#include <njin.h>
namespace {
bool paused = false;
void load(njin::context &ctx) {
}
void update(njin::context &ctx) {
}
paused = !paused;
if (paused)
else
}
}
void setup(njin::context &ctx) {
}
}
return {.name = "audio_demo", .setup = setup};
}
void ecs_register(context &ctx, sys_phase phase, sys_fnc fnc, const char *name=nullptr)
Thêm một system vào lịch chạy của một phase.
@ phase_update
Mỗi frame, logic chính của game.
Definition _mod.h:35
@ phase_startup
Một lần, trước frame đầu tiên.
Definition _mod.h:32
void music_play(context &ctx, music_handle handle)
Phát từ đầu, kể cả khi đang tạm dừng hoặc đang phát.
void music_resume(context &ctx, music_handle handle)
Phát tiếp từ chỗ music_pause() đã dừng.
music_handle music_load(context &ctx, const char *path)
Nạp một bản nhạc để stream từ đĩa.
void music_set_volume(context &ctx, music_handle handle, f32 volume)
Đặt âm lượng của music, từ 0 trở lên (1 là âm lượng gốc).
void music_pause(context &ctx, music_handle handle)
Tạm dừng nhưng giữ nguyên vị trí.
void sound_play_once_at(context &ctx, sound_handle handle, f32 pitch, f32 gain)
Giống sound_play_once() nhưng chỉnh cao độ và độ lớn cho riêng bản này.
sound_handle sound_load(context &ctx, const char *path)
Nạp một âm thanh ngắn vào bộ nhớ.
void sound_play_restart(context &ctx, sound_handle handle)
Cắt mọi bản đang phát rồi phát lại từ đầu, nên lúc nào cũng chỉ nghe một bản.
void sound_set_volume(context &ctx, sound_handle handle, f32 volume)
Đặt âm lượng của sound, từ 0 trở lên (1 là âm lượng gốc).
void sound_play_loop(context &ctx, sound_handle handle)
Đánh dấu sound là lặp lại và phát nếu nó chưa phát.
float f32
Số thực 32 bit.
Definition _types.h:28
Định danh của một action nhập liệu, tạo bởi action_register().
Definition _types.h:85
Mô tả một module: một nhóm system có tên.
Definition _mod.h:67
Định danh của một bản nhạc stream (music), tạo bởi music_load().
Definition _types.h:123
Định danh của một âm thanh ngắn (sound), tạo bởi sound_load().
Definition _types.h:116
Nạp và thất bại
njin::sound_load() và njin::music_load() trả về handle có id == 0 khi:
- Máy không có thiết bị âm thanh (không loa, lỗi driver). Game vẫn chạy bình thường, chỉ là không có tiếng.
- File không tồn tại.
- File không có đuôi (như Doxyfile). raylib chọn bộ giải mã theo đuôi file nên không đoán được định dạng.
- File không giải mã được.
Mọi trường hợp đều ghi log lý do kèm đường dẫn. Mọi hàm nhận handle không hợp lệ hoặc đã unload thì không làm gì, nên bạn không cần kiểm tra ở mỗi chỗ gọi.
Định dạng hỗ trợ là những gì raylib giải mã được, gồm wav, ogg, mp3, flac. Đường dẫn tính từ thư mục làm việc khi chạy game (xem Bắt đầu).
Sound
Phát
Bốn cách phát, cho bốn nhu cầu:
Với njin::sound_play_once_at(), pitch 1 là như bản ghi và 2 là cao gấp đôi, gain nhân vào âm lượng của sound. Cả hai chỉ áp dụng cho bản đó: lần phát thường sau đó trở về 1 và 1.
Âm thanh theo vị trí
njin::sound_play_at() phát như njin::sound_play_once() kèm một vị trí trong thế giới:
- Âm lượng giảm dần theo khoảng cách tới điểm camera đang nhìn: đủ to trong khoảng gần, nhỏ dần đều, im hẳn ở khoảng xa. Chỉnh bằng njin::audio_set_range() (mặc định 200 và 1200).
- Trái phải: tiếng lệch sang loa trái hoặc phải theo vị trí trên màn hình.
Âm lượng và tắt tiếng
Âm thanh tự sinh
Không phải âm thanh nào cũng đọc từ file. njin::sound_load_samples() tạo sound từ các mẫu trong bộ nhớ:
#include <cmath>
#include <njin.h>
#include <vector>
namespace {
void make_beep(njin::context &ctx) {
std::vector<njin::f32> samples((std::size_t)count);
samples[(std::size_t)i] = 0.4f * fade * std::sin(2.0f * 3.14159265f * 880.0f * t);
}
}
void setup(njin::context &ctx) {
}
}
return {.name = "procedural_audio", .setup = setup};
}
sound_handle sound_load_samples(context &ctx, const f32 *samples, i32 count, i32 sample_rate)
Tạo một âm thanh từ các mẫu đã có trong bộ nhớ.
std::int32_t i32
Số nguyên có dấu 32 bit.
Definition _types.h:14
Music
njin::music_load() không nạp cả file mà mở một stream. Vòng lặp và bài hát nhạc nền thường dùng cách này.
Âm thanh 3D
Trong game 3D, tiếng phát ra từ một chỗ trong thế giới và được nghe từ một tai nghe (njin::audio_listener3d). Engine tính lại cho từng tiếng, mỗi frame:
- Âm lượng theo khoảng cách từ tai tới nguồn, theo hướng loa (nếu có nón) và theo vật che giữa hai bên.
- Trái phải theo hướng của nguồn so với hướng tai đang nhìn.
- Cao độ theo hiệu ứng Doppler: nguồn và tai đi lại gần nhau thì tiếng cao lên, đi xa nhau thì trầm xuống (tiếng xe lao qua).
#include <njin.h>
namespace {
using namespace njin;
void load(context &ctx) {
body3d_create(ctx, {.position = {0.0f, -0.5f, 0.0f}, .size = {200.0f, 1.0f, 200.0f}});
body3d_create(ctx, {.position = {0.0f, 1.5f, -6.0f}, .size = {6.0f, 3.0f, 0.3f}});
{.max_distance = 25.0f, .occlusion = true, .occlusion_volume = 0.3f});
{.volume = 0.6f, .max_distance = 60.0f, .occlusion = true, .occlusion_margin = 2.5f});
}
void update(context &ctx) {
{.min_distance = 3.0f, .max_distance = 120.0f});
}
void render(context &ctx) {
begin_3d(ctx, {.position = {0.0f, 6.0f, 14.0f}, .target = {0.0f, 0.0f, 0.0f}});
}
void setup(context &ctx) {
}
}
njin::mod_desc audio3d_demo_module() {
return {.name =
"audio3d_demo", .setup = setup}; }
void begin_3d(context &ctx, const camera3d &camera)
Bắt đầu vẽ 3D qua camera.
void draw_plane3d(const context &ctx, vec3 center, vec2 size, rgba color)
Vẽ mặt phẳng nằm ngang (song song mặt xz), mặt trên hướng +y.
void end_3d(context &ctx)
Kết thúc vẽ 3D và quay về vẽ 2D trong không gian thế giới.
@ phase_render
Mỗi frame, vẽ trong không gian thế giới.
Definition _mod.h:38
f32 vehicle3d_rpm(const context &ctx, vehicle3d_handle handle)
Vòng tua động cơ lúc này, cho tiếng máy và đồng hồ.
vec3 body3d_velocity(const context &ctx, body3d_handle handle)
Vận tốc thẳng của body, đơn vị mỗi giây.
body3d_handle vehicle3d_body(const context &ctx, vehicle3d_handle handle)
Thân xe, để đọc vị trí (body3d_transform()), vận tốc, hay đẩy nó.
body3d_handle body3d_create(context &ctx, const body3d_desc &desc)
Tạo một body vật lý 3D.
transform3d body3d_transform(const context &ctx, body3d_handle handle)
Vị trí và góc xoay hiện tại của body, để vẽ nó (scale luôn là 1).
vehicle3d_handle vehicle3d_create(context &ctx, const vehicle3d_desc &desc)
Tạo một xe có bánh: thân xe là body động, mỗi bánh là một giảm xóc dò mặt đất (không phải body),...
void voice3d_set_desc(context &ctx, voice3d_handle voice, const sound3d_desc &desc)
Đổi cách nghe của tiếng đang phát: âm lượng theo ga của xe, cao độ theo vòng tua máy (vehicle3d_rpm()...
void voice3d_set_position(context &ctx, voice3d_handle voice, vec3 position)
Dời tiếng tới position, và thôi đi theo entity nếu đang đi theo.
void voice3d_set_velocity(context &ctx, voice3d_handle voice, vec3 velocity)
Đặt vận tốc của nguồn cho Doppler.
sound3d_desc voice3d_desc(const context &ctx, voice3d_handle voice)
Cách nghe đang dùng của tiếng.
voice3d_handle sound_loop3d(context &ctx, sound_handle handle, vec3 position, const sound3d_desc &desc={})
Phát lặp handle tại position cho đến khi voice3d_stop() (hoặc sound_stop() cho sound đó): tiếng máy n...
voice3d_handle sound_play3d(context &ctx, sound_handle handle, vec3 position, const sound3d_desc &desc={})
Phát một lần handle tại position, nghe từ tai nghe 3D theo desc.
constexpr rgba gray
Xám.
Definition _math.h:240
Định danh của một body vật lý 3D, tạo bởi body3d_create().
Definition _types.h:160
Cách nghe một tiếng 3D.
Definition njin_audio.h:307
f32 pitch
Cao độ trước Doppler. 1 là như bản ghi.
Definition njin_audio.h:309
Vector 3 chiều.
Definition _types.h:47
Định danh của một xe có bánh, tạo bởi vehicle3d_create().
Definition _types.h:222
Định danh của một tiếng đang phát trong không gian 3D, tạo bởi sound_play3d() hoặc sound_loop3d().
Definition _types.h:270
Tai nghe
Mặc định tai đi theo camera của lần njin::begin_3d() vẽ ra màn hình gần nhất: cùng vị trí, cùng hướng nhìn, và vận tốc engine đo từ quãng camera đi mỗi frame. Game 3D thường không phải gọi gì.
Khi tai không ở camera (góc nhìn thứ ba mà muốn nghe từ nhân vật), đặt tay bằng njin::audio_set_listener3d(); từ lúc đó tai thôi theo camera, cho đến khi gọi njin::audio_listener3d_follow_camera(). Lần begin_3d() vẽ vào render texture (model xoay trong menu) không đổi tai.
Phát
Cả hai trả về một njin::voice3d_handle cho riêng tiếng đó. Dùng nó để dời tiếng (njin::voice3d_set_position(), njin::voice3d_attach()), đặt vận tốc cho Doppler (njin::voice3d_set_velocity()), đổi cách nghe lúc đang phát (njin::voice3d_set_desc(): cao độ theo vòng tua máy, âm lượng theo ga) và dừng nó. Tiếng phát xong thì handle tự hết hiệu lực; ô của nó được dùng lại nhưng handle cũ không bao giờ trỏ nhầm sang tiếng mới.
Tiếng đi theo entity: entity bị hủy thì tiếng một lần phát nốt ở chỗ cuối cùng, còn tiếng lặp dừng luôn. njin::sound_stop() cho một sound cũng dừng mọi tiếng 3D của sound đó.
Tối đa 64 tiếng 3D cùng lúc. Quá số đó thì tiếng một lần đã chạy lâu nhất bị cắt; tiếng lặp không bao giờ bị cắt.
Khoảng cách
njin::sound3d_desc cho biết tiếng nghe được tới đâu:
| Trường | Ý nghĩa |
| min_distance | Gần hơn thì nghe đủ to. Mặc định 1 |
| max_distance | Xa hơn thì im. Mặc định 40 |
| rolloff | Cách giảm ở giữa (bảng dưới) |
| rolloff_factor | Độ dốc: 2 là tắt nhanh hơn, 0.5 là vang xa hơn |
| rolloff | Âm lượng ở khoảng cách d | Hợp với |
| njin::rolloff_inverse | min / (min + factor * (d - min)): như ngoài đời | Mặc định, hầu hết mọi tiếng |
| njin::rolloff_linear | Giảm đều từ 1 đến 0 giữa min_distance và max_distance | Khi cần biết chắc tiếng nghe được tới đâu |
| njin::rolloff_exponential | (d / min)^-factor: tắt nhanh hơn | Tiếng nhỏ, chỉ nghe khi đứng sát |
Inverse và exponential tự chúng không bao giờ về 0, nên ở 10% cuối trước max_distance engine cho chúng nhỏ dần về 0: tiếng đi ra khỏi tầm không bị tắt đột ngột.
Trái phải, Doppler, nón
- spread (0..1) là độ lệch trái phải: 1 là tiếng bên phải nghe hẳn ở loa phải, 0 là luôn ở giữa. Gần tai hơn min_distance thì tiếng tự về giữa dần, để tiếng ngay trên đầu không nhảy từ loa này sang loa kia.
- doppler (0 là tắt, 1 là như ngoài đời). Vận tốc của tiếng mặc định do engine đo từ quãng nó đi mỗi frame, làm mượt trong khoảng 0,1 giây; có vận tốc thật (njin::body3d_velocity()) thì đưa vào bằng njin::voice3d_set_velocity(). Tốc độ âm thanh mặc định 343 đơn vị mỗi giây (một đơn vị là một mét), đổi bằng njin::audio_set_speed_of_sound().
- Nón: loa có hướng (còi xe, loa phóng thanh) đặt cone_direction. Trong góc cone_inner quanh hướng đó thì đủ to, ngoài cone_outer thì còn cone_outer_volume, ở giữa thì chuyển dần. cone_direction bằng 0 (mặc định) là phát đều mọi hướng.
Vật che
Bật occlusion thì mỗi frame engine bắn một tia vật lý (njin::physics3d_raycast()) từ tai tới nguồn. Tia chạm một body ở trước nguồn thì tiếng nhỏ lại còn occlusion_volume, chuyển mượt trong khoảng 0,15 giây. Chỗ chạm cách nguồn trong occlusion_margin thì không tính: thân xe không che tiếng máy của chính nó.
- Ghi chú
- Vật che chỉ làm tiếng nhỏ đi, không làm tiếng đục đi (không lọc âm cao). raylib trộn từng tiếng bằng âm lượng, trái phải và cao độ, không có bộ lọc riêng cho mỗi tiếng.
Âm lượng cuối cùng
Âm lượng của một tiếng 3D là tích của: âm lượng riêng của sound (njin::sound_set_volume()), kênh của nó và bus_master (njin::audio_set_bus_volume(), xem Cài đặt của người chơi), volume trong njin::sound3d_desc, khoảng cách, nón và vật che. Đổi âm lượng kênh trong menu cài đặt thì tiếng 3D đang phát nhận ngay ở frame sau.
njin::voice3d_state() trả về những gì engine vừa tính cho một tiếng (âm lượng, trái phải, cao độ, khoảng cách, có bị che không), để debug hoặc vẽ chỉ báo tiếng động trên màn hình.
Module âm thanh của engine
Có một module lõi tên njin.audio luôn được đăng ký sẵn. Mỗi frame, ở phase_post_update, nó làm ba việc:
- Cấp dữ liệu cho các stream nhạc. Nhạc giải mã từng đoạn nhỏ và sẽ im lặng ngay sau đoạn đầu nếu không có việc này.
- Phát lại các sound lặp đã kết thúc. Đó là lý do njin::sound_play_loop() chỉ cần gọi một lần.
- Cập nhật các tiếng 3D: dời tai theo camera, dời tiếng theo entity, tính lại âm lượng, trái phải và cao độ, phát lại tiếng 3D lặp và giải phóng tiếng một lần đã phát xong.
Bạn không cần gọi gì để bật nó. Xem Kiến trúc bên trong để biết module lõi hoạt động thế nào.
- Ghi chú
- Vì sound lặp được phát lại sau khi nó kết thúc, chỗ nối có một quãng lặng cỡ một frame. Với tiếng nền ngắn thì không đáng kể. Với nhạc nền dài, dùng njin::music_load().
Giải phóng
Tài nguyên được giải phóng tự động khi gọi njin::destroy(). Chỉ gọi njin::sound_unload() hoặc njin::music_unload() khi muốn giải phóng sớm. Thiết bị âm thanh được mở cùng cửa sổ và đóng sau khi mọi tài nguyên đã được giải phóng.