Setting large amounts of SoundData at a time

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
DTmg
Prole
Posts: 16
Joined: Tue Apr 18, 2023 12:47 pm

Setting large amounts of SoundData at a time

Post by DTmg »

Hello :)
I'm attempting to parse a file that contains raw sound data as part of it. I've managed to get individual samples out of it by creating a DataView from the ByteData object that hold all the sample data, and wanted to test to see if I was pulling the right section out by playing back one of the samples, however I can't figure out how to do that without manually setting each individual sample through a loop. Is there a way, either through some function I don't know about or ffi trickery with the data pointers to copy large chunks of data directly into a SoundData?
DTmg
Prole
Posts: 16
Joined: Tue Apr 18, 2023 12:47 pm

Re: Setting large amounts of SoundData at a time

Post by DTmg »

I've figured out a roundabout solution, which is to take the chunk of SoundData and put a .wav file header in front of it, then put that in a FileData and put *that* into a SoundData, however for some reason the sample is not looping when I play it back with looping enabled. It plays back the audio just fine though, and I'm able to use it mostly like a normal audio source, but isPlaying() doesn't ever return false unless I stop it manually. This makes me worried that the header hasn't been set up correctly (I've been following this: http://soundfile.sapp.org/doc/WaveFormat/ to set it up), so ideally it would be great to just copy the sample data directly into a SoundData if that is possible. I don't have time right now, but later after I'm done moving I'll compare the generated wav "file" with an actual .wav file to see if maybe I'm missing some piece of data to make it loop properly.
RNavega
Party member
Posts: 355
Joined: Sun Aug 16, 2020 1:28 pm

Re: Setting large amounts of SoundData at a time

Post by RNavega »

DTmg wrote: Sun Aug 18, 2024 2:13 am Is there a way, either through some function I don't know about or ffi trickery with the data pointers to copy large chunks of data directly into a SoundData?
Hi, I think it's something like this (untested):

Code: Select all

local ffi = require('ffi')

local all_samples = love.filesystem.read(path)
local all_samples_buf = love.data.newByteData(all_samples)

-- Create a blank SoundData based on settings you already know.
local my_sounddata = love.sound.newSoundData(TOTAL_SAMPLES, SAMPLE_RATE, 
                                             BIT_DEPTH, CHANNELS)

-- Zero-based index into the byte to start copying from.
local byte_offset = 3316

-- How many bytes per sample (assuming the platform byte is 8 bits long).
local bytes_per_sample = BIT_DEPTH / 8

local destination_ptr = my_sounddata:getFFIPointer()
local source_ptr = all_samples_buf:getFFIPointer()
ffi.copy(destination_ptr,
         source_ptr + byte_offset,
         TOTAL_SAMPLES * bytes_per_sample)
The API function is this:
https://luajit.org/ext_ffi_api.html#ffi_copy

Also note there's an extension included in LuaJIT, string.buffer, which is helpful when parsing files or buffer-like things. The functions buffer:set() and buffer:ref() are the most useful IMO.
DTmg
Prole
Posts: 16
Joined: Tue Apr 18, 2023 12:47 pm

Re: Setting large amounts of SoundData at a time

Post by DTmg »

Attempting to add directly to a pointer like that gives me

Code: Select all

attempt to perform arithmetic on 'void *' and 'number'
but casting to a uint64_t and back to a void * worked, thanks!
RNavega
Party member
Posts: 355
Joined: Sun Aug 16, 2020 1:28 pm

Re: Setting large amounts of SoundData at a time

Post by RNavega »

Ah of course, thanks for the follow-up.

LuaJIT does support pointer arithmetic, so I think if you cast (or rather, construct) the void* into a byte pointer (uint8_t*) then it should work, without having to convert it back.

That is:

Code: Select all

local TYPEOF_UINT8_T = ffi.typeof("uint8_t *")

(...)

local source_ptr = all_samples_buf:getFFIPointer()
source_ptr = TYPEOF_UINT8_T(source_ptr)
...with the rest as in the original, including that add with "byte_offset".
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Setting large amounts of SoundData at a time

Post by zorg »

DTmg wrote: Sun Aug 18, 2024 2:13 am I've managed to get individual samples out of it by creating a DataView from the ByteData object that hold all the sample data, and wanted to test to see if I was pulling the right section out by playing back one of the samples, however I can't figure out how to do that without manually setting each individual sample through a loop.
I might be wrong about this, but can't you just create a SoundData object directly from the DataView? (FileData should work, not sure about ByteData nor DataView objects)
Of course, you'd still need to pass in some extra info (sampling rate, bit depth, channel count), but that's literally just a constructor in a one-liner.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
DTmg
Prole
Posts: 16
Joined: Tue Apr 18, 2023 12:47 pm

Re: Setting large amounts of SoundData at a time

Post by DTmg »

I tried from just a DataView/ByteData and it wasn't working right, it seemed to only want FileData. In any case I did get it working!
User avatar
SiENcE
Party member
Posts: 805
Joined: Thu Jul 24, 2008 2:25 pm
Location: Berlin/Germany
Contact:

Re: Setting large amounts of SoundData at a time

Post by SiENcE »

I am currently writing an MOD and HSC player and therefore had to deal with a similar problem.

You can use setSample to insert generated raw data into SoundData.

Here is the code for continuous buffered playback. But it only has 1 channel.

Code: Select all

-- Create a QueueableSource with the desired sample rate, bit depth, and channel count
local queueSource = love.audio.newQueueableSource(44100, 16, 1)

-- TEST SOUND
function createSound(samplerate, frequency)
    local samples = {}
    local amplitude = 0.5
    for i = 1, samplerate do
        samples[i] = amplitude * math.sin((i / 44100) * frequency * 2 * math.pi)
    end
	return samples
end

function generateRandomSound()
    -- Generate random samples
	local samples = createSound(44100, 385)
    local audioData = love.sound.newSoundData(#samples, 44100, 16, 1)

    for i, v in ipairs(samples) do
        audioData:setSample(i - 1, v) -- i - 1 because SoundData is 0-indexed
    end

    -- Queue the generated audio data
    queueSource:queue(audioData)
end

function love.update(dt)
    -- Continuously generate and queue sound while maintaining playback
    if queueSource:getFreeBufferCount() > 0 then
        generateRandomSound()
    end

    -- Start playing if not already
    if not queueSource:isPlaying() then
        queueSource:play()
    end
end

function love.draw()
    love.graphics.print("Playing continuous random sound ...", 10, 10)
end
Now when you want to mix channels it works like this. I setup 9 channels.

Code: Select all

-- Create 9 QueueableSources with the desired sample rate, bit depth, and channel count
local channels = {}
local numChannels = 9
local sampleRate = 44100
local bitDepth = 16

for i = 1, numChannels do
    channels[i] = love.audio.newQueueableSource(sampleRate, bitDepth, 1)
end

-- Function to create a sine wave sound for a specific channel
function createSound(samplerate, frequency)
    local samples = {}
    local amplitude = 0.5
    for i = 1, samplerate do
        samples[i] = amplitude * math.sin((i / 44100) * frequency * 2 * math.pi)
    end
    return samples
end

-- Function to generate sounds for each channel and mix them together
function generateMixedSound()
    local mixedSamples = {}
    for i = 1, sampleRate do
        mixedSamples[i] = 0 -- Initialize the mixed sample to 0
    end

    -- Generate and mix sounds from each channel
    for ch = 1, numChannels do
        local frequency = love.math.random(1,200) + ch * 50 -- Vary the frequency for each channel
        local samples = createSound(sampleRate, frequency)

        for i = 1, #samples do
            mixedSamples[i] = mixedSamples[i] + (samples[i] / numChannels) -- Mix the sound, normalize by number of channels
        end
    end

    -- Create SoundData for the mixed samples
    local audioData = love.sound.newSoundData(#mixedSamples, sampleRate, bitDepth, 1)
    for i, v in ipairs(mixedSamples) do
        audioData:setSample(i - 1, v) -- SoundData is 0-indexed
    end

    -- Queue the mixed audio data in each channel
    for ch = 1, numChannels do
        if channels[ch]:getFreeBufferCount() > 0 then
            channels[ch]:queue(audioData)
        end
    end
end

function love.update(dt)
    -- Continuously generate and queue mixed sound while maintaining playback
    generateMixedSound()

    -- Start playing each channel if not already playing
    for ch = 1, numChannels do
        if not channels[ch]:isPlaying() then
            channels[ch]:play()
        end
    end
end

function love.draw()
    love.graphics.print("Playing mixed sound from 9 channels...", 10, 10)
end
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Setting large amounts of SoundData at a time

Post by zorg »

That sine wave implementation will pop at the ends unless you taper it to zero, or keep a continuous phase value that doesn't reset at the end of each sounddata.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
SiENcE
Party member
Posts: 805
Joined: Thu Jul 24, 2008 2:25 pm
Location: Berlin/Germany
Contact:

Re: Setting large amounts of SoundData at a time

Post by SiENcE »

Ah thanks zorg! It was just a sample to show how continous sampling works.
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 5 guests