diff --git a/c_src/rayex/rayex.c b/c_src/rayex/rayex.c index 4d243c5..17a64a9 100644 --- a/c_src/rayex/rayex.c +++ b/c_src/rayex/rayex.c @@ -1,6 +1,28 @@ #include "rayex.h" #include +/* + * Payloads let us ignore some structs when binding data from Elixir <-> C + * But will make the data unavailable on the elixir side! + */ +#define CREATE_UNIFEX_PAYLOAD_FOR(T, name) \ + UnifexPayload create_##name##_unifex_payload(UnifexEnv *env, T value) { \ + UnifexPayload payload; \ + unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, sizeof(value), &payload); \ + memcpy(payload.data, &value, payload.size); \ + return payload; \ + } \ + \ + T get_##name##_unifex_payload(UnifexEnv *env, UnifexPayload *in_payload) { \ + UnifexPayload out_payload; \ + unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, in_payload->size, \ + &out_payload); \ + memcpy(out_payload.data, in_payload->data, out_payload.size); \ + T result; \ + memcpy(&result, out_payload.data, out_payload.size); \ + return result; \ + } + // NOTE: "E_" when converting back to NIF struct #define VECTOR2(v) ((Vector2){.x = v.x, .y = v.y}) @@ -581,12 +603,66 @@ UNIFEX_TERM draw_grid(UnifexEnv *env, int slices, double spacing) { // Audio device management functions +UNIFEX_TERM init_audio_device(UnifexEnv *env) { + InitAudioDevice(); + return init_audio_device_result_ok(env); +} + +UNIFEX_TERM close_audio_device(UnifexEnv *env) { + CloseAudioDevice(); + return close_audio_device_result_ok(env); +} + +UNIFEX_TERM is_audio_device_ready(UnifexEnv *env) { + bool res = IsAudioDeviceReady(); + return is_audio_device_ready_result(env, res); +} + +UNIFEX_TERM set_master_volume(UnifexEnv *env, double volume) { + SetMasterVolume((float)volume); + return set_master_volume_result_ok(env); +} + +UNIFEX_TERM get_master_volume(UnifexEnv *env) { + float res = GetMasterVolume(); + return get_master_volume_result(env, res); +} + // Wave/Sound loading/unloading functions +CREATE_UNIFEX_PAYLOAD_FOR(Sound, sound) + +UNIFEX_TERM load_sound(UnifexEnv *env, char *fileName) { + Sound sound = LoadSound(fileName); + UnifexPayload payload = create_sound_unifex_payload(env, sound); + UNIFEX_TERM result = load_sound_result(env, &payload); + unifex_payload_release(&payload); + + return result; +} + // Wave/Sound management functions // Music management functions +UNIFEX_TERM play_sound(UnifexEnv *env, UnifexPayload *payload) { + Sound sound = get_sound_unifex_payload(env, payload); + PlaySound(sound); + return play_sound_result_ok(env); +} + +UNIFEX_TERM stop_sound(UnifexEnv *env, UnifexPayload *payload) { + Sound sound = get_sound_unifex_payload(env, payload); + StopSound(sound); + return stop_sound_result_ok(env); +} + +UNIFEX_TERM is_sound_ready(UnifexEnv *env, UnifexPayload *payload) { + Sound sound = get_sound_unifex_payload(env, payload); + bool res = IsSoundReady(sound); + return is_sound_ready_result(env, res); +} + // AudioStream management functions /*********** diff --git a/c_src/rayex/rayex.spec.exs b/c_src/rayex/rayex.spec.exs index 46275b7..d7b17d7 100644 --- a/c_src/rayex/rayex.spec.exs +++ b/c_src/rayex/rayex.spec.exs @@ -227,11 +227,24 @@ spec draw_grid(slices :: int, spacing :: float) :: :ok :: label ######### # Audio device management functions +spec init_audio_device() :: :ok :: label +spec close_audio_device() :: :ok :: label +spec is_audio_device_ready() :: result :: bool +spec set_master_volume(volume :: float) :: :ok :: label +spec get_master_volume() :: volume :: float # Wave/Sound loading/unloading functions +spec load_sound(file_name :: string) :: sound :: payload +dirty(:io, load_sound: 1) + +spec is_sound_ready(sound :: payload) :: result :: bool + # Wave/Sound management functions +spec play_sound(sound :: payload) :: :ok :: label +spec stop_sound(sound :: payload) :: :ok :: label + # Music management functions # AudioStream management functions @@ -490,23 +503,21 @@ type wave :: %Rayex.Structs.Wave{ data: payload } -# FIXME: ? https://github.com/raysan5/raylib/blob/master/src/raylib.h#L428 -type r_audio_buffer :: %Rayex.Structs.RAudioBuffer{} - -type audio_stream :: %Rayex.Structs.AudioStream{ - buffer: [r_audio_buffer], - sample_rate: unsigned, - sample_size: unsigned, - channels: unsigned - } - -type sound :: %Rayex.Structs.Sound{ - stream: audio_stream, - frame_count: unsigned - } +# type audio_stream :: %Rayex.Structs.AudioStream{ +# buffer: [r_audio_buffer], +# processor: [r_audio_processor], +# sample_rate: unsigned, +# sample_size: unsigned, +# channels: unsigned +# } +# +# type sound :: %Rayex.Structs.Sound{ +# stream: audio_stream, +# frame_count: unsigned +# } type music :: %Rayex.Structs.Music{ - stream: audio_stream, + stream: payload, frame_count: unsigned, looping: bool, ctx_type: int, diff --git a/examples/audio.exs b/examples/audio.exs new file mode 100755 index 0000000..d2f11a0 --- /dev/null +++ b/examples/audio.exs @@ -0,0 +1,77 @@ +#!elixir + +Mix.install([ + {:rayex, path: ".."} +]) + +defmodule Test do + @moduledoc false + + alias Rayex.Structs, as: S + + use Rayex + use Rayex.Keys + + @camera_first_person 4 + @color_white %S.Color{r: 245, g: 245, b: 245, a: 255} + @color_darkgray %S.Color{r: 80, g: 80, b: 80, a: 255} + + def run do + init_window(800, 450, "raylib [core] example - 3d picking") + set_target_fps(60) + + camera = %S.Camera3D{ + position: %S.Vector3{x: 6.0, y: 6.0, z: 6.0}, + target: %S.Vector3{x: 0.0, y: 2.0, z: 0.0}, + up: %S.Vector3{x: 0.0, y: 1.0, z: 0.0}, + fovy: 45.0, + # perspective projection + projection: 0 + } + + camera = update_camera(camera, @camera_first_person) + init_audio_device() + sound = load_sound("resources/audio/country.mp3") + play_sound(sound) + + game_loop(%{camera: camera, sound: sound, pause: false}) + end + + defp game_loop(%{camera: camera, sound: sound, pause: pause} = state) do + camera = update_camera(camera, @camera_first_person) + + pause = + if is_key_pressed?(key_space()) do + (!pause && stop_sound(sound)) || play_sound(sound) + !pause + else + pause + end + + state = draw(%{state | camera: camera, pause: pause}) + + (window_should_close() && close_window()) || game_loop(state) + end + + defp draw(%{camera: camera} = state) do + begin_drawing() + clear_background(@color_white) + + # --- 3D --- + begin_mode_3d(camera) + + draw_grid(10, 1.0) + + end_mode_3d() + # --- 3D --- + + draw_text("PRESS SPACE TO PAUSE/RESUME MUSIC", 200, 10, 20, @color_darkgray) + draw_fps(10, 10) + + end_drawing() + + state + end +end + +Test.run() diff --git a/examples/resources/audio/LICENSE.md b/examples/resources/audio/LICENSE.md new file mode 100644 index 0000000..a9fcc6e --- /dev/null +++ b/examples/resources/audio/LICENSE.md @@ -0,0 +1,3 @@ +| resource | author | licence | notes | +| :------------------- | :---------: | :------ | :---- | +| country.mp3 | [@emegeme](https://github.com/emegeme) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | Originally created for "DART that TARGET" game | diff --git a/examples/resources/audio/country.mp3 b/examples/resources/audio/country.mp3 new file mode 100644 index 0000000..91066cc Binary files /dev/null and b/examples/resources/audio/country.mp3 differ diff --git a/flake.nix b/flake.nix index d95da5b..4b770df 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,6 @@ ]; buildInputs = with pkgs; [ raylib - glibc ]; shellHook = '' export CPATH="$(erl -eval 'io:format("~s", [lists:concat([code:root_dir(), "/erts-", erlang:system_info(version), "/include"])])' -s init stop -noshell):`pwd`/deps/unifex/c_src/unifex/nif" diff --git a/lib/rayex/audio.ex b/lib/rayex/audio.ex index a288187..0c64aa6 100644 --- a/lib/rayex/audio.ex +++ b/lib/rayex/audio.ex @@ -3,12 +3,43 @@ defmodule Rayex.Audio do Audio related functions """ + alias Rayex.Structs, as: S + alias Rayex.Unifex.Raylib + # Audio device management functions + @doc "Initialize audio device and context" + @spec init_audio_device() :: :ok + defdelegate init_audio_device(), to: Raylib + + @doc "Check if audio device has been initialized successfully" + @spec is_audio_device_ready?() :: boolean() + defdelegate is_audio_device_ready?(), to: Raylib, as: :is_audio_device_ready + + @doc "Set master volume (listener)" + @spec set_master_volume(float()) :: :ok + defdelegate set_master_volume(volume), to: Raylib + # Wave/Sound loading/unloading functions + @doc "Load sound from file" + @spec load_sound(String.t()) :: S.Sound.t() + defdelegate load_sound(fileName), to: Raylib + + @doc "Checks if a sound is ready" + @spec is_sound_ready?(S.Sound.t()) :: boolean() + defdelegate is_sound_ready?(sound), to: Raylib, as: :is_sound_ready + # Wave/Sound management functions + @doc "Play a sound" + @spec play_sound(S.Sound.t()) :: :ok + defdelegate play_sound(sound), to: Raylib + + @doc "Stop a sound" + @spec stop_sound(S.Sound.t()) :: :ok + defdelegate stop_sound(sound), to: Raylib + # Music management functions # AudioStream management functions diff --git a/lib/rayex/structs.ex b/lib/rayex/structs.ex index 08657fb..9813968 100644 --- a/lib/rayex/structs.ex +++ b/lib/rayex/structs.ex @@ -315,14 +315,14 @@ end defmodule Rayex.Structs.Model do @moduledoc "Model" - @enforce_keys ~w[transform mesh_count material_count mashes materials mesh_material bone_count bones bind_pose]a - defstruct ~w[transform mesh_count material_count mashes materials mesh_material bone_count bones bind_pose]a + @enforce_keys ~w[transform mesh_count material_count meshes materials mesh_material bone_count bones bind_pose]a + defstruct ~w[transform mesh_count material_count meshes materials mesh_material bone_count bones bind_pose]a @type t :: %__MODULE__{ transform: Rayex.Structs.Matrix.t(), mesh_count: integer(), material_count: integer(), - mashes: [Rayex.Structs.Mesh.t()], + meshes: [Rayex.Structs.Mesh.t()], materials: [Rayex.Structs.Material.t()], mesh_material: [integer()], bone_count: integer(), @@ -394,37 +394,35 @@ defmodule Rayex.Structs.Wave do } end -# XXX: ? https://github.com/raysan5/raylib/blob/master/src/raylib.h#L428 -defmodule Rayex.Structs.RAudioBuffer do - @moduledoc "RAudioBuffer" - @enforce_keys ~w[]a - defstruct ~w[]a - - @type t :: %__MODULE__{} -end - -defmodule Rayex.Structs.AudioStream do - @moduledoc "AudioStream" - @enforce_keys ~w[buffer sample_rate sample_size channels]a - defstruct ~w[buffer sample_rate sample_size channels]a - - @type t :: %__MODULE__{ - buffer: [Rayex.Structs.RAudioBuffer.t()], - sample_rate: non_neg_integer(), - sample_size: non_neg_integer(), - channels: non_neg_integer() - } -end +# defmodule Rayex.Structs.AudioStream do +# @moduledoc "AudioStream" +# @enforce_keys ~w[buffer processor sample_rate sample_size channels]a +# defstruct ~w[buffer processor sample_rate sample_size channels]a +# +# @type t :: %__MODULE__{ +# buffer: unifex_payload :: binary(), +# processor: unifex_payload :: binary(), +# sample_rate: non_neg_integer(), +# sample_size: non_neg_integer(), +# channels: non_neg_integer() +# } +# end +# +# defmodule Rayex.Structs.Sound do +# @moduledoc "Sound" +# @enforce_keys ~w[stream frame_count]a +# defstruct ~w[stream frame_count]a +# +# @type t :: %__MODULE__{ +# stream: Rayex.Structs.AudioStream.t(), +# frame_count: non_neg_integer() +# } +# end defmodule Rayex.Structs.Sound do @moduledoc "Sound" - @enforce_keys ~w[stream frame_count]a - defstruct ~w[stream frame_count]a - @type t :: %__MODULE__{ - stream: Rayex.Structs.AudioStream.t(), - frame_count: non_neg_integer() - } + @type t :: binary() end defmodule Rayex.Structs.Music do @@ -433,7 +431,7 @@ defmodule Rayex.Structs.Music do defstruct ~w[stream frame_count looping ctx_type ctx_data]a @type t :: %__MODULE__{ - stream: Rayex.Structs.AudioStream.t(), + stream: binary(), frame_count: non_neg_integer(), looping: boolean(), ctx_type: integer(),