Skip to content

Commit 70dc362

Browse files
feat: add audio support with Sound and Music classes in vectorjs
1 parent 518a98b commit 70dc362

10 files changed

Lines changed: 257 additions & 4 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ add_executable(vectorjs)
5454
target_sources(vectorjs PRIVATE
5555
"src/main.cpp"
5656
"src/core.cpp"
57+
"src/api/vectorjs/vjs_audio.cpp"
5758
"src/api/vectorjs/vjs_enums.cpp"
5859
"src/api/vectorjs/vjs_color.cpp"
5960
"src/api/vectorjs/vjs_palette.cpp"

README.MD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ VectorJS is a lightweight engine designed to execute JavaScript-based 2D/3D grap
99
| Feature | Status |
1010
|-----------|----------|
1111
| Shapes 2D | ✔ |
12-
| Audio | |
12+
| Audio | ✔ |
1313
| Text | ✔ |
1414
| Fonts | ✔ |
1515

examples/assets/coin.wav

17.7 KB
Binary file not shown.

examples/assets/country.mp3

1.43 MB
Binary file not shown.

examples/audio/music.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {Application, Vector2, Music } from "vectorjs";
2+
3+
const screenWidth = 400;
4+
const screenHeight = 400;
5+
const fpsPos = new Vector2(10, 10);
6+
let music;
7+
8+
const app = new Application(screenWidth, screenHeight,"Music Player");
9+
app.run({
10+
11+
onInit() {
12+
music = new Music("C:/workspace/c/vectorjs/examples/assets/country.mp3");
13+
music.play();
14+
music.setVolume(0.5)
15+
},
16+
17+
onUpdate(ctx) {
18+
if (music) {
19+
music.update();
20+
}
21+
},
22+
23+
onDraw(render) {
24+
render.clearBackground();
25+
render.withScreenSpace((ctx) => {
26+
ctx.drawFPS(fpsPos);
27+
});
28+
}
29+
});

src/api/vectorjs/js_types.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,39 @@ namespace App::Module::VectorJS {
7070
}
7171
};
7272

73+
struct JSSound {
74+
std::shared_ptr<::Sound> sound_ptr;
75+
JSSound() = default;
76+
77+
explicit JSSound(std::string_view path) {
78+
const ::Sound s = LoadSound(path.data());
79+
sound_ptr = std::shared_ptr<::Sound>(new ::Sound(s), [](::Sound* ps) {
80+
if (ps) {
81+
if (IsAudioDeviceReady()) {
82+
UnloadSound(*ps);
83+
}
84+
delete ps;
85+
}
86+
});
87+
}
88+
};
89+
90+
struct JSMusic {
91+
std::shared_ptr<::Music> music_ptr;
92+
93+
explicit JSMusic(std::string_view path) {
94+
const ::Music m = LoadMusicStream(path.data());
95+
music_ptr = std::shared_ptr<::Music>(new ::Music(m), [](::Music* pm) {
96+
if (pm) {
97+
if (IsAudioDeviceReady()) {
98+
UnloadMusicStream(*pm);
99+
}
100+
delete pm;
101+
}
102+
});
103+
}
104+
};
105+
73106
struct JSCamera2D {
74107
Vector2 offset = { .x = 0.0f, .y = 0.0f };
75108
Vector2 target = { .x = 0.0f, .y = 0.0f };

src/api/vectorjs/vectorjs.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace App::Module::VectorJS {
1212
void register_font_class(qjspp::Engine& engine, qjspp::ModuleBuilder& builder);
1313
void register_vector2(qjspp::Engine& engine, qjspp::ModuleBuilder& builder);
1414
void register_rectangle(qjspp::Engine& engine, qjspp::ModuleBuilder& builder);
15+
void register_audio_classes(qjspp::Engine& engine, qjspp::ModuleBuilder& builder);
1516

1617
inline void register_module(qjspp::Engine& engine) {
1718
auto m = engine.new_module("vectorjs");
@@ -23,6 +24,7 @@ namespace App::Module::VectorJS {
2324
register_font_class(engine, m);
2425
register_vector2(engine, m);
2526
register_rectangle(engine, m);
27+
register_audio_classes(engine, m);
2628
m.finalize();
2729
}
2830

src/api/vectorjs/vjs_application.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ namespace App::Module::VectorJS {
330330

331331
JSApplication::JSApplication(qjspp::Engine& engine, const int w, const int h, const std::string_view title): engine(engine) {
332332
InitWindow(w, h, title.data());
333+
InitAudioDevice();
333334
SetTargetFPS(60);
334335
}
335336

@@ -349,18 +350,18 @@ namespace App::Module::VectorJS {
349350

350351
try {
351352
if (!on_init_func.is_undefined()) {
352-
(void)on_init_func.call_method(user_app, {});
353+
std::ignore = on_init_func.call_method(user_app, {});
353354
}
354355

355356
while (!WindowShouldClose()) {
356357
BeginDrawing();
357358

358359
if (!on_update_func.is_undefined()) {
359-
(void)on_update_func.call_method(user_app, {update_obj.clone()});
360+
std::ignore = on_update_func.call_method(user_app, {update_obj.clone()});
360361
}
361362

362363
if (!on_draw_func.is_undefined()) {
363-
(void)on_draw_func.call_method(user_app, {render_obj.clone()});
364+
std::ignore = on_draw_func.call_method(user_app, {render_obj.clone()});
364365
}
365366

366367
EndDrawing();
@@ -373,6 +374,7 @@ namespace App::Module::VectorJS {
373374
}
374375

375376
if (IsWindowReady()) {
377+
CloseAudioDevice();
376378
CloseWindow();
377379
}
378380

src/api/vectorjs/vjs_audio.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#include <memory>
2+
#include <string>
3+
#include <qjspp.hpp>
4+
#include <raylib.h>
5+
#include "js_types.hpp"
6+
7+
namespace App::Module::VectorJS {
8+
9+
static void register_sound_class(qjspp::Engine& engine, qjspp::ModuleBuilder& builder) {
10+
auto sound = engine.make_class<JSSound>("Sound");
11+
12+
// Constructor: const mySound = new vectorjs.Sound("assets/sfx.wav");
13+
sound.constructor([](const qjspp::ArgList& args) -> std::unique_ptr<JSSound> {
14+
if (args.empty()) return nullptr;
15+
return std::make_unique<JSSound>(args[0].to_string());
16+
});
17+
18+
// Instance Method: mySound.play()
19+
sound.instance_method("play", [](JSSound* self, const qjspp::ArgList& args) -> qjspp::Value {
20+
if (self && self->sound_ptr) {
21+
PlaySound(*(self->sound_ptr));
22+
}
23+
return {};
24+
});
25+
26+
// Instance Method: mySound.pause()
27+
sound.instance_method("pause", [](JSSound* self, const qjspp::ArgList& args) -> qjspp::Value {
28+
if (self && self->sound_ptr) {
29+
PauseSound(*(self->sound_ptr));
30+
}
31+
return {};
32+
});
33+
34+
// Instance Method: mySound.resume()
35+
sound.instance_method("resume", [](JSSound* self, const qjspp::ArgList& args) -> qjspp::Value {
36+
if (self && self->sound_ptr) {
37+
ResumeSound(*(self->sound_ptr));
38+
}
39+
return {};
40+
});
41+
42+
// Instance Method: mySound.stop()
43+
sound.instance_method("stop", [](JSSound* self, const qjspp::ArgList& args) -> qjspp::Value {
44+
if (self && self->sound_ptr) {
45+
StopSound(*(self->sound_ptr));
46+
}
47+
return {};
48+
});
49+
50+
// Instance Method: mySound.setVolume(0.5)
51+
sound.instance_method("setVolume", [](JSSound* self, const qjspp::ArgList& args) -> qjspp::Value {
52+
if (self && self->sound_ptr && !args.empty()) {
53+
SetSoundVolume(*(self->sound_ptr), static_cast<float>(args[0].to_double()));
54+
}
55+
return {};
56+
});
57+
58+
builder.export_class("Sound", sound.build());
59+
}
60+
61+
static void register_music_class(qjspp::Engine& engine, qjspp::ModuleBuilder& builder) {
62+
auto music = engine.make_class<JSMusic>("Sound");
63+
64+
// Constructor: const mySound = new vectorjs.Sound("assets/sfx.wav");
65+
music.constructor([](const qjspp::ArgList& args) -> std::unique_ptr<JSMusic> {
66+
if (args.empty()) return nullptr;
67+
return std::make_unique<JSMusic>(args[0].to_string());
68+
});
69+
70+
// Instance Method: mySound.play()
71+
music.instance_method("play", [](JSMusic* self, const qjspp::ArgList& args) -> qjspp::Value {
72+
if (self && self->music_ptr) {
73+
PlayMusicStream(*(self->music_ptr));
74+
}
75+
return {};
76+
});
77+
78+
// Instance Method: mySound.pause()
79+
music.instance_method("pause", [](JSMusic* self, const qjspp::ArgList& args) -> qjspp::Value {
80+
if (self && self->music_ptr) {
81+
PauseMusicStream(*(self->music_ptr));
82+
}
83+
return {};
84+
});
85+
86+
// Instance Method: mySound.resume()
87+
music.instance_method("resume", [](JSMusic* self, const qjspp::ArgList& args) -> qjspp::Value {
88+
if (self && self->music_ptr) {
89+
ResumeMusicStream(*(self->music_ptr));
90+
}
91+
return {};
92+
});
93+
94+
// Instance Method: mySound.stop()
95+
music.instance_method("stop", [](JSMusic* self, const qjspp::ArgList& args) -> qjspp::Value {
96+
if (self && self->music_ptr) {
97+
StopMusicStream(*(self->music_ptr));
98+
}
99+
return {};
100+
});
101+
102+
// Instance Method: myMusic.update()
103+
music.instance_method("update", [](JSMusic* self, const qjspp::ArgList& args) -> qjspp::Value {
104+
if (self && self->music_ptr) {
105+
UpdateMusicStream(*(self->music_ptr));
106+
}
107+
return {};
108+
});
109+
110+
// Instance Method: mySound.setVolume(0.5)
111+
music.instance_method("setVolume", [](JSMusic* self, const qjspp::ArgList& args) -> qjspp::Value {
112+
if (self && self->music_ptr && !args.empty()) {
113+
SetMusicVolume(*(self->music_ptr), static_cast<float>(args[0].to_double()));
114+
}
115+
return {};
116+
});
117+
118+
builder.export_class("Music", music.build());
119+
}
120+
121+
void register_audio_classes(qjspp::Engine& engine, qjspp::ModuleBuilder& builder) {
122+
register_sound_class(engine, builder);
123+
register_music_class(engine, builder);
124+
}
125+
126+
} // namespace App::Module::VectorJS

ts/vectorjs.d.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,64 @@ declare module "vectorjs" {
425425
readonly COFFEE: Color;
426426
readonly CHOCOLATE: Color;
427427
};
428+
429+
/**
430+
* Represents a short audio clip loaded entirely into memory.
431+
* Ideal for sound effects and quick, repeatable noises.
432+
*/
433+
export class Sound {
434+
/**
435+
* Loads a sound from the specified file path.
436+
* @param path The file path to the audio file (e.g., .wav, .ogg, .mp3)
437+
*/
438+
constructor(path: string);
439+
440+
/** Plays the sound. */
441+
play(): void;
442+
443+
/** Stops the sound if it is currently playing. */
444+
stop(): void;
445+
446+
/**
447+
* Sets the playback volume of the sound.
448+
* @param volume Volume level, where 1.0 is max and 0.0 is silent.
449+
*/
450+
setVolume(volume: number): void;
451+
}
452+
453+
/**
454+
* Represents a streaming audio track.
455+
* Ideal for background music (BGM) or long ambient tracks to save RAM.
456+
*/
457+
export class Music {
458+
/**
459+
* Opens an audio stream from the specified file path.
460+
* @param path The file path to the audio file (e.g., .wav, .ogg, .mp3)
461+
*/
462+
constructor(path: string);
463+
464+
/** Starts playing the music stream. */
465+
play(): void;
466+
467+
/** Stops the music stream and resets it to the beginning. */
468+
stop(): void;
469+
470+
/** Pauses the music stream at its current position. */
471+
pause(): void;
472+
473+
/** Resumes a paused music stream. */
474+
resume(): void;
475+
476+
/**
477+
* Sets the playback volume of the music.
478+
* @param volume Volume level, where 1.0 is max and 0.0 is silent.
479+
*/
480+
setVolume(volume: number): void;
481+
482+
/**
483+
* Refills the audio buffer.
484+
* **Note:** This MUST be called every frame in your update loop for the music to keep playing!
485+
*/
486+
update(): void;
487+
}
428488
}

0 commit comments

Comments
 (0)