Skip to content

Conversation

@meisei4
Copy link

@meisei4 meisei4 commented Jul 7, 2025

This is a fix to correct the the AudioStream update and Sound update to use element size rather than size in bytes. I have tested it with a new sample that is the equivalent of the raylib core examples audio_raw_stream.c:

https://github.com/raysan5/raylib/blob/master/examples/audio/audio_raw_stream.c

@AmityWilder AmityWilder self-requested a review July 7, 2025 21:09
ffi::UpdateSound(
self.0,
data.as_ptr() as *const std::os::raw::c_void,
data.len() as i32,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if these could be .try_into().unwrap() since usize as i32 can overflow without warning, but as i32 is so prevalent in the library that this isn't a deal breaker. It will probably be addressed on its own in the future.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will address with this https://github.com/raylib-rs/raylib-rs/pull/212/files#r2191223482
I have made the try_into().unwrap() update, but need to investigate the other comment further to understand.

use raylib::prelude::MouseButton::MOUSE_BUTTON_LEFT;

const MAX_SAMPLES: usize = 512;
const DATA_ELEMENTS_PER_CYCLE: usize = MAX_SAMPLES * 2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is DATA_ELEMENTS_PER_CYCLE and where is the 2 coming from? If you are calculating the size of data in bytes, it would be more clear to use size_of::<i16>() to show why 2 in particular is being used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. removed DATA_ELEMENTS_PER_CYCLE, now using only MAX_SAMPLES, with halving throughout to follow raylib c version

let raylib_audio = RaylibAudio::init_audio_device().unwrap();
raylib_audio.set_audio_stream_buffer_size_default(MAX_SAMPLES_PER_UPDATE as i32);
let mut audio_stream = raylib_audio.new_audio_stream(SAMPLE_RATE, SAMPLE_SIZE, 1);
let mut data: [i16; DATA_ELEMENTS_PER_CYCLE] = [0; DATA_ELEMENTS_PER_CYCLE];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line appears to be related to the original C version's line:

short *data = (short *)malloc(sizeof(short)*MAX_SAMPLES);

malloc does not allocate shorts. It allocates bytes, and then has the byte-array pointer cast to a short-array pointer. In other words,

(T *)malloc(sizeof(T)*N)

allocates the equivalent of

// this
[T; N]
// NOT this 
[T; size_of::<T>()*N]

DATA_ELEMENTS_PER_CYCLE is MAX_SAMPLES*2 (2 presumably being the size of a short in bytes).
I think you may have accidentally created an array with as many elements as it was intended to have bytes.

This isn't unsafe or anything, but it is allocating double the space that is intended to be used, and may confuse readers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to use MAX_SAMPLES outright (still concerned if this is a sign that my understanding of the original AudioStream::update() function is incorrect with regards to handling buffers byte size vs element size, i would like to address that in the many places that involve halving and treating the MAX_SAMPLES as a byte count vs element count, perhaps could be helpful to request clarity for this in the original raylib example even?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAX_SAMPLES is in samples. Raylib multiplies sampleCount by the size of a sample itself.

Comment on lines 32 to 34
if raylib_handle.is_mouse_button_down(MOUSE_BUTTON_LEFT) {
frequency = 40.0 + mouse_position.y;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C version of the example also calls SetAudioStreamPan after updating the frequency.

float pan = (float)(mousePosition.x) / (float)screenWidth;
SetAudioStreamPan(stream, pan);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

introduced pan, but with a mouse x position inversion to have left<->right mouse position match the pans effect. Included a comment. this might be optional, but it bugged me (can remove it to follow raylib closer if the pan behavior is identical in the rust bindings, I will check)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you would like, I'm sure you could try proposing that change to Raylib itself and find out if there's a reason for it

let mut draw_handle = raylib_handle.begin_drawing(&raylib_thread);
draw_handle.clear_background(Color::RAYWHITE);
draw_handle.draw_text(
&format!("sine frequency: {:.1} Hz", frequency),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C version floors to an integer instead of tenths. I won't be picky about this though, I just want to point out that it isn't equivalent in behavior.

Copy link
Author

@meisei4 meisei4 Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed this after investigating the difference between:

(int)float_val

and these rust fucntions i just learned float_val.floor() as i32, float_val.trunc() as i32, float_val as i32, and I also learned that float_val.floor() has behavior I didnt know about for negatives:
assert_eq!(-3.3_f32 as i32, -3); vs assert_eq!(-3.3_f32.floor() as i32, -4);
https://doc.rust-lang.org/std/primitive.f32.html#method.floor, https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast

(unrelated here because frequency is never negative) so the best version is to match the C behavior is simply:

&format!("sine frequency: {}", frequency as i32)

audio_stream.update(&write_buf);

}
let width = raylib_handle.get_screen_width();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example does not enable resizing of the window. Instead of requesting the width every frame, the width could have been stored in a constant or an immutable variable at the start of the program—like the C version does.


Also, "Width? Width of what? The wave? Its period? The text? Some rectangle I missed?"
Width is a very generic name, especially in a function as expansive as main.

You could have used the name screen_width like the original, making its intent far clearer at a glance without needing to check the definition to know what width it's referring to.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! I have no excuse for this, it was sloppy and the result of my own projects use of ambigious width things where i have to constantly switch between render_width and screen_width because of testing on apple machine and linux machine with DPI things that im still trying to grasp...

20,
Color::RED,
);
draw_handle.draw_text("click mouse button to change frequency", 10, 10, 20, Color::DARKGRAY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C version also mentions panning in this string. However, you also removed that feature from this example (I'm not sure why), so it might be intentional. Just remember to update this string if you decide to re-enable panning.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to properly add the pan feature.

this is related to the raylib-rs example copy not having the panning. I mixed up what i was implementing https://github.com/raysan5/raylib/blob/master/examples/audio/audio_raw_stream.c vs https://github.com/raylib-rs/raylib-rs/blob/unstable/showcase/original/audio/audio_raw_stream.c

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example diverges quite a bit from the C version. It looks like you did a bit more than just rewrite it in Rust...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thank you again for the review!

I am going to organize my thoughts as a reponse to this first, cause i think it will help me. I think we are discussing in discord still, but there is some very strange status of these repositories that lead to this (not making excuses for myself, just trying to explain what i think happened to me I think):

versions of this raylib sample are a bit confusing:

  1. main branch raylib core repo: https://github.com/raysan5/raylib/blob/master/examples/audio/audio_raw_stream.c
  • commented out code confusion here (I think you mentioned following up on this in the official raylib repo, so that sounds good)
  1. raylib-rs project corresponding sample: https://github.com/raylib-rs/raylib-rs/blob/unstable/showcase/original/audio/audio_raw_stream.c
  • perhaps out of date? potential sign of abandonment of the rewriting the original raylib samples? (not part of the actual samples/bin style raylib-rs "examples" at least)
  1. potentially old rewrite attempt of the original raylib example: https://github.com/raylib-rs/raylib-rs/blob/unstable/showcase/src/example/audio/audio_raw_stream.rs
  • similar sign as previous comment, something about me not understanding what the current status of this showcase directory is vs the samples/bin directory (I cant seem to get the showcase stuff to work, as i think its out of date and abandoned im unsure though)

I will continue with a new rewrite of the audio_raw_stream.c from the main repo (but with no commented out code, just to have something up and running for me to learn from this review), so we might need to discuss more about how and where this example should be placed, and honestly whether or not my "size in bytes" -> "size in elements" is even a "fix" in the first place or if it is potentially a misunderstanding of the original design

I will include references to the new code (much less verbose than this current reply) in responses to the review comments to hopefully have a fresh look at a less divergent rewrite of the original audio_raw_stream.c

// }}
/// Updates sound buffer with new data.
#[inline]
pub fn update<T: AudioSample>(&mut self, data: &[T]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to either assert that size_of::<T>() is equal to self.0.sampleSize or somehow make that a restriction on T. Otherwise a user may pass an array of u8 to an audio stream formatted for f32.

Because samples is passed in elements instead of bytes, Raylib will multiply the element count by the sample size.

Consider the case of self.0.sampleSize=32, data=&[u8; 14]:
data contains 14 bytes of data.
UpdateSound sees that 14 elements are to be read.
self.0.sampleSize tells UpdateSound to expect each element to be 4 bytes.
14 elements, 4 bytes each, means UpdateSound expects data to contain 14*4=56 bytes.
The first 3 elements will contain incorrect, though within-bounds data.
Suppose UpdateSound attempts to read the f32 (not u8) at index 4: 4*4=16 bytes. data only contains 14 bytes. This results in an out-of-bounds position being accessed, which would be Undefined Behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally:
ffi::UpdateSound performs a memcpy into (&*self.0.stream.buffer).data without performing a bounds check nor resizing. If data contains more bytes than the buffer, the buffer will be overrun resulting in another case of UB.

Because Raylib doesn't perform this bounds check, Rust needs to do so instead to prevent unsoundness.

Copy link
Member

@AmityWilder AmityWilder Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example:

assert_eq!(
    self.0.stream.sampleSize.try_into()
        .expect("sampleSize should be 8, 16, or 32"),
    std::mem::size_of::<T>()*u8::BITS as usize,
    "update data format must match sound",
);
assert!(
    data.len() <= self.0.frameCount.try_into()
        .expect("frameCount should be a valid memory allocation size"),
    "frames to update must fit in sound",
);

Alternatively:

#[derive(Error, Debug)]
pub enum UpdateSoundError {
    #[error("update data format must match sound")]
    SampleSizeMismatch,
    #[error("frames to update must fit in sound")]
    TooManyFrames,
}

pub fn update<T: AudioSample>(&mut self, data: &[T]) -> Result<(), UpdateSoundError> {
    if self.0.stream.sampleSize.try_into().expect("sampleSize should be 8, 16, or 32") != std::mem::size_of::<T>()*u8::BITS as usize {
        Err(UpdateSoundError::SampleSizeMismatch)
    } else if data.len() > self.0.frameCount.try_into().expect("frameCount should be a valid memory allocation size") {
        Err(UpdateSoundError::TooManyFrames)
    } else {
        // ...
        Ok(())
    }
}

Copy link
Author

@meisei4 meisei4 Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made my final draft i think of the full review and fixes. The code itself embeds the notes that remain to be addressed, optionally involving raylib repo side/upstream discussion. these notes are a kind of gathering of the discord discussions

/// **Notes** (iann): if raylib upstream discussion introduces any of these checks, we might simplify these to avoid any redundancy i think

/// **Notes** (iann):

I will address the AudioStreamCallback in a separate PR I think, unless you think it would be better to include in this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants