Streaming Audio

About Monkey 2 Forums Monkey 2 Development Streaming Audio

This topic contains 15 replies, has 4 voices, and was last updated by  Diffrenzy 2 years, 7 months ago.

Viewing 15 posts - 1 through 15 (of 16 total)
  • Author
    Posts
  • #1232

    Simon Armstrong
    Participant

    As the SDL2 audio mixer is a little limited I thought I would have a go streaming audio in monkey2.

    My first issue was that SDL_OpenAudio returning an already in use error. Calling Mix_CloseAudio fixes that for us but means we are on our own and can’t share device with mojo2 mixer audio.

     

    Second issue was a small bug in sdl2.monkey2[1025] which should read

    Alias SDL_AudioCallback:Void(Void Ptr,UByte Ptr,Int)

     

    And with that solved we have a functioning callback and a question.

    Can I cast a monkey2 class to Void Ptr?

     

    #1234

    Mark Sibly
    Keymaster

    Nice idea, but I think you’ll run into problems with the complete lack of thread sync in mx2, as the audio callback is called on a different thread (I think).

    An alternative would be to have the SDL callback trigger a ‘main thread’ callback. There’s actually a kludgy behind-the-scenes way to do this in mojo.app and mojo.process that I eventually want to wrap in a nicer way. This would only work as long as the main thread was running fast enough of course.

    Alias SDL_AudioCallback:Void(Void Ptr,UByte Ptr,Int)

    Thanks, fixed.

    Can I cast a monkey2 class to Void Ptr?

    Nope, but you can wrap the object in a struct and pass a pointer to that. I use this in the pixmap loader to pass the mx2 stream as ‘context’ void ptr, something like:

    Casting object to void ptr may happen eventually, but it’s potentially dangerous for a number of reasons. I like having the struct wrapper as it ensures the object is wrapped in a variable and will be kept ‘alive’ while the callback is happening and not inadvertantly collected, similar to sticking something in a global to keep it alive.

    #1236

    Simon Armstrong
    Participant

    Cool, am slowly getting the hang of structs.

    Yup, got threading and debugger acting in most arbitrary manner. To be honest, it was nice to be back in unsafe environment:)

    The actual call back needs to do a mem copy and increment a pointer so I will move that to a cpp file.

    Or yes, it could block and wait for a monkey2 signal to say the copy has been performed on main thread…

    Also, without a NoDebug flag like BlitzMax I think rule seems to be all monkey2 code should be app thread only no matter what.

    I aim to implement a freeaudio style mixer in monkey2 code using float samples and just poll it per update to keep write pointer ahead of read pointer on some shared memory.

    #1237

    Mark Sibly
    Keymaster

    > Or yes, it could block and wait for a monkey2 signal to say the copy has been performed on main thread…

    Not quite sure what you mean here, but the native callback should never block. It should just copy in audio data, signal the gui/mx2 thread that it needs more data, and return ASAP.

    In a polled scenario, the mx2 polling code could just check a flag, but sync signaling is nicer – see native process.cpp, esp:

    int callback=g_mojo_app_AppInstance_AddAsyncCallback( finished );

    …and…

    postEvent( callback|INVOKE|REMOVE );

    AddAsyncCallback is called on the main thread at setup time, while postEvent is called by native code running in it’s own thread.

    Here, ‘finished’ is an mx2 function that gets called when the process is finished.

    For an audio mixer, ‘finished’ would be something like ‘fillBuffer’ and postEvent would be called once native code has copied out the last buffer. You could in fact double buffer here (on the native thread) and call postEvent after flipping buffers but before copying audio data. This would help allow native/mx2 threads to run concurrently.

    #1256

    Simon Armstrong
    Participant

    I am running with fragment size of 32 samples on Mac in release mode and in audio heaven.

    A vsynth banana featuring 5 types of oscillator, an ADSR envelope and arpeggiator from unknown universe looks to be this weeks project.

    #1257

    Simon Armstrong
    Participant

    I have written following C++ class so vsynth.monkey2 can hopefully be thread safe.

    It seems to be working and hooking up C++ to monkey2 felt pretty darn cool.

     

     

    As a possible optimisation I would like to change the deque to be fragments (const sized array of doubles) but not sure if that makes sense.

    #1259

    Mark Sibly
    Keymaster

    That’s the easy part – the syncing is where it gets tricky!

    App code can’t just WriteSamples at will, it will need to halt to allow ReadSamples to catch up or the buffer will get ahead. On the other hand, if app code can’t WriteSamples fast enough, well yer basically hosed…

    IMO, the best thing to do is start with getting the Callback moved to the monkey side, via either polling or signalling, so the audio device pulls data from SDL code running on it’s own audio thread, which pulls data from mx2 code running on the GUI thread.

    Then you could perhaps look at using a fiber to provide synchronous/blocking writes to audio. This way, the fiber can block, not the callback.

    And it’s probably time to add Deque I guess…

    Just some thoughts!

    #1260

    Simon Armstrong
    Participant

    I use a mutex to stay thread safe and it is up to monkey2 app to keep the buffer fill.

    This is my current polling technique which I call every render:

     

    #1261

    Mark Sibly
    Keymaster

    Ok, that looks like it’s basically just moving fillBuffer from SDL->mx2 which should work.

    It seems a little long-winded to me though – how about something like:

    I guess it depends on whether it needs to be able to handle variable sized chunks etc – my code assumes a fixed size but I still feel like yours does more copying than it needs to.

    #1262

    Simon Armstrong
    Participant

    Samples per frame is 44100/60 = 735 but typical audio buffers need to be power of two so I am thinking for simple ping pong buffers fragment size of 2048 should be safe bet for uninterrupted polling per OnRender.

    How do Monkey2 timers work? Do they interrupt code or fire on app update?

    #1263

    Mark Sibly
    Keymaster

    > How do Monkey2 timers work? Do they interrupt code or fire on app update?

    Timers use the same mechanism as process.finished, process.stdoutready etc mentioned above.

    When the timer fires, it’s handled in timer thread which basically just sticks a custom event in the SDL event queue (which can be done safely from another thread), which will cause the callback to be invoked the next time events are updated.

    The nice thing is that posting the event will also wake up a WaitMsg blocked app so you don’t have to poll.

    #1275

    Simon Armstrong
    Participant

    The vsynth feature set is growing including noise wave and pitch bend.

    Attachments:
    #1449

    DruggedBunny
    Participant

    vsynth is vcool!

    #3759

    Diffrenzy
    Keymaster

    This is so cool.

    Will it work on Android and iOS?

    #3797

    Mark Sibly
    Keymaster

    Will it work on Android and iOS?

    I’m not sure about iOS but, somewhat to my surprise, it does work on Android! Both the sdl-mixer and openal drivers too (had to increase the ‘polling’ rate of the openal one though).

    I haven’t got bluetooth keyboard working on iOS yet so I haven’t really tested it there.

Viewing 15 posts - 1 through 15 (of 16 total)

You must be logged in to reply to this topic.