Skip to content

Commit

Permalink
Consume audio buffer and send to JS
Browse files Browse the repository at this point in the history
Turns out to be on the critical path to getting boot. If we don't
pull data from the audio DMA channel it appears to fill up and the
boot process hangs very early.

We would have just had a no-op read, but might as well actually
send the data to the JS side. We use a 1khz polling loop, which is
perhaps too frequent, but it works for now.

The 6100 guitar chord startup chime now plays, but there is some
glitching after, and then a continous hum.

Polling for input also turns out to be on the critical path,
since we need to consume the input buffer to get the "audio context
running" flag sent from the browser process to the worker. We don't
actually do anything with the input data yet.

Updates mihaip/infinite-mac#219
  • Loading branch information
mihaip committed Sep 9, 2023
1 parent d7469d4 commit f476d1f
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 3 deletions.
8 changes: 7 additions & 1 deletion core/hostevents_js.cpp
Expand Up @@ -21,12 +21,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

#include <core/hostevents.h>
#include <loguru.hpp>
#include <emscripten.h>

EventManager* EventManager::event_manager;

void EventManager::poll_events()
{
// LOG_F(INFO, "EventManager::poll_events()");
int lock = EM_ASM_INT_V({ return workerApi.acquireInputLock(); });
if (!lock) {
return;
}
EM_ASM({ workerApi.releaseInputLock(); });

// perform post-processing
this->_post_signal.emit();
}
85 changes: 83 additions & 2 deletions devices/sound/soundserver_js.cpp
Expand Up @@ -19,11 +19,27 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include <devices/common/dmacore.h>
#include <core/timermanager.h>
#include <devices/sound/soundserver.h>
#include <loguru.hpp>
#include <functional>
#include <endianswap.h>
#include <emscripten.h>

typedef enum {
SND_SERVER_DOWN = 0,
SND_SERVER_STARTED,
SND_SERVER_STREAM_OPENED,
SND_SERVER_STREAM_STARTED,
} Status;

class SoundServer::Impl {
public:
Status status = Status::SND_SERVER_DOWN;
uint32_t poll_timer = 0;
std::function<void()> poll_cb;
std::unique_ptr<uint8_t[]> sound_buffer;
};


Expand All @@ -39,27 +55,92 @@ SoundServer::~SoundServer()
int SoundServer::start()
{
LOG_F(INFO, "SoundServer::start()");
impl->status = SND_SERVER_STARTED;
return 0;
}

void SoundServer::shutdown()
{
LOG_F(INFO, "SoundServer::shutdown()");
switch (impl->status) {
case SND_SERVER_STREAM_STARTED:
case SND_SERVER_STREAM_OPENED:
close_out_stream();
break;
case SND_SERVER_STARTED:
case SND_SERVER_DOWN:
// nothing to do
break;
}

impl->status = SND_SERVER_DOWN;

LOG_F(INFO, "Sound Server shut down.");
}

static void poll_sound(uint8_t *sound_buffer, int sound_buffer_size, DmaOutChannel *dma_ch) {
if (!dma_ch->is_active()) {
return;
}

// Let the JS side get ahead a bit, in case we can't feed it fast enough.
int js_audio_buffer_size = EM_ASM_INT_V({ return workerApi.audioBufferSize(); });
if (js_audio_buffer_size >= sound_buffer_size * 4) {
return;
}

int req_size = sound_buffer_size;
int out_size = 0;

while (req_size > 0) {
uint8_t *chunk;
uint32_t chunk_size;
if (!dma_ch->pull_data(req_size, &chunk_size, &chunk)) {
std::copy(chunk, chunk + chunk_size, sound_buffer + out_size);
req_size -= chunk_size;
out_size += chunk_size;
} else {
break;
}
}

EM_ASM_({ workerApi.enqueueAudio($0, $1); }, sound_buffer, out_size);
}

int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data)
{
LOG_F(INFO, "SoundServer::open_out_stream()");
uint32_t sample_size = 16;
uint32_t channels = 2;

// The audio worklet API processes things in 128 frame chunks. Have some
// buffer to make sure we don't starve it, but don't buffer too much either,
// to avoid latency.
int audio_frames_per_block = 384;
int sound_buffer_size = (sample_size >> 3) * channels * audio_frames_per_block;
impl->sound_buffer = std::make_unique<uint8_t[]>(sound_buffer_size);

impl->poll_cb = std::bind(poll_sound, impl->sound_buffer.get(), sound_buffer_size, static_cast<DmaOutChannel*>(user_data));
impl->status = SND_SERVER_STREAM_OPENED;

EM_ASM_({ workerApi.didOpenAudio($0, $1, $2, $3); }, sample_rate, sample_size, channels);

return 0;
}

int SoundServer::start_out_stream()
{
LOG_F(INFO, "SoundServer::start_out_stream()");
impl->poll_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(1), impl->poll_cb);
impl->status = SND_SERVER_STREAM_STARTED;

return 0;
}

void SoundServer::close_out_stream()
{
LOG_F(INFO, "SoundServer::close_out_stream()");
if (impl->status == SND_SERVER_STREAM_STARTED) {
TimerManager::get_instance()->cancel_timer(impl->poll_timer);
}
impl->status = SND_SERVER_STARTED;

}

0 comments on commit f476d1f

Please sign in to comment.