afritz1 / OpenTESArena

Open-source re-implementation of The Elder Scrolls: Arena.
MIT License
915 stars 68 forks source link

Add city entrance jingle #180

Closed afritz1 closed 4 years ago

afritz1 commented 4 years ago

Hey @kcat, I'm working on adding the city entrance jingle which requires supporting non-looping music in the OpenALStream. How should I listen for the end-of-song event? It seems like a hard problem because OpenAL Soft is fed the buffer in a fairly opaque manner and I don't see a way to get accurate timing. Maybe it involves counting AL_BUFFERS_PROCESSED with some multiple of the sample rate?

For very small music files, OpenALStream::fillBuffer() seems to be able to fully loop through the song and continue filling before the buffer is full. I thought of maybe filling the end of the buffer with zeroes if it tries asking for more samples at the end, but that would probably just result in extra silence.

I'm implementing the next music with mNextSong and AudioManager::setNextMusic(). The change from old music to new music occurs in AudioManagerImpl::update().

(~as a side note, I think it would be better to define mNextSong as a std::function so that it is up-to-date in the audio manager in case the jingle happens right at a day/night music transition~ nevermind, the day/night event would call playMusic() anyway, so nothing would be out-of-date)

Thanks :)

kcat commented 4 years ago

Hey @kcat, I'm working on adding the city entrance jingle which requires supporting non-looping music in the OpenALStream. How should I listen for the end-of-song event?

If the source stops playing (its state becomes AL_STOPPED) and there's no more data to buffer, the stream is finished.

For very small music files, OpenALStream::fillBuffer() seems to be able to fully loop through the song and continue filling before the buffer is full. I thought of maybe filling the end of the buffer with zeroes if it tries asking for more samples at the end, but that would probably just result in extra silence.

For looping music, that definitely wouldn't be a good idea to have extra 0s before rewinding. You'll need some way to check if mSong should loop or not, maybe a parameter to OpenALStream::play that's stored in the class and checked in OpenALStream::fillBuffer. Or maybe it can be passed to mSong, and have its read method do the loop instead of OpenALStream::fillBuffer rewinding the stream at the end. Either method has their advantages and disadvantages (having MidiSong handle the loop means it can loop more seamlessly, since it can rewind on the midi event queue without interrupting the generated samples, but any additional song types made in the future would have to each handle looping itself too, instead of it being handled in a single place for all song types).

With some way to prevent looping in place, it looks like you can then add a function to OpenALStream that checks the thread status to test if it's stopped:

class OpenALStream {
    ...
    bool isPlaying() const
    {
        return mThread.get_id() != std::thread::id() && !mQuit.load();
    }
    ...

Then you can call mSongStream->isPlaying() to check if the stream is still playing. If it returns false, the jingle is over.

afritz1 commented 4 years ago

Alright, thanks. So I can check if the source is AL_STOPPED. Okay. I was a little confused because it looked like you designed it so the source never stopped, so it would just be an ever-continuing stream.

I did experiment a bit with an mLoop in fillBuffer() yesterday but it became complicated when it was like I couldn't break out of that got < toget condition or else I would lose the last received samples (which could be the entire buffer for a short jingle).

I'll try adding an OpenALStream::isPlaying().

(thanks for responding despite my random ping!)

afritz1 commented 4 years ago

Looks like it's working now; will probably push soon. It does seem to cut off the end of a couple jingles like Arabic city, but that is a WildMIDI problem I think?

kcat commented 4 years ago

It does seem to cut off the end of a couple jingles like Arabic city, but that is a WildMIDI problem I think?

Probably an issue with software midi synths in general. Since midi files only contain the commands to play music, the file ends after the last command regardless of any notes that may need to fade out. Most midi renders will either stop there (cutting off any such note), or let you keep generating samples indefinitely (it'll fade out the final note(s), but then continuously supply silence).