Skip to content

Commit f476d1f

Browse files
committed
Consume audio buffer and send to JS
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
1 parent d7469d4 commit f476d1f

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

core/hostevents_js.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
2121

2222
#include <core/hostevents.h>
2323
#include <loguru.hpp>
24+
#include <emscripten.h>
2425

2526
EventManager* EventManager::event_manager;
2627

2728
void EventManager::poll_events()
2829
{
29-
// LOG_F(INFO, "EventManager::poll_events()");
30+
int lock = EM_ASM_INT_V({ return workerApi.acquireInputLock(); });
31+
if (!lock) {
32+
return;
33+
}
34+
EM_ASM({ workerApi.releaseInputLock(); });
35+
3036
// perform post-processing
3137
this->_post_signal.emit();
3238
}

devices/sound/soundserver_js.cpp

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,27 @@ You should have received a copy of the GNU General Public License
1919
along with this program. If not, see <https://www.gnu.org/licenses/>.
2020
*/
2121

22+
#include <devices/common/dmacore.h>
23+
#include <core/timermanager.h>
2224
#include <devices/sound/soundserver.h>
2325
#include <loguru.hpp>
26+
#include <functional>
27+
#include <endianswap.h>
28+
#include <emscripten.h>
29+
30+
typedef enum {
31+
SND_SERVER_DOWN = 0,
32+
SND_SERVER_STARTED,
33+
SND_SERVER_STREAM_OPENED,
34+
SND_SERVER_STREAM_STARTED,
35+
} Status;
2436

2537
class SoundServer::Impl {
2638
public:
39+
Status status = Status::SND_SERVER_DOWN;
40+
uint32_t poll_timer = 0;
41+
std::function<void()> poll_cb;
42+
std::unique_ptr<uint8_t[]> sound_buffer;
2743
};
2844

2945

@@ -39,27 +55,92 @@ SoundServer::~SoundServer()
3955
int SoundServer::start()
4056
{
4157
LOG_F(INFO, "SoundServer::start()");
58+
impl->status = SND_SERVER_STARTED;
4259
return 0;
4360
}
4461

4562
void SoundServer::shutdown()
4663
{
47-
LOG_F(INFO, "SoundServer::shutdown()");
64+
switch (impl->status) {
65+
case SND_SERVER_STREAM_STARTED:
66+
case SND_SERVER_STREAM_OPENED:
67+
close_out_stream();
68+
break;
69+
case SND_SERVER_STARTED:
70+
case SND_SERVER_DOWN:
71+
// nothing to do
72+
break;
73+
}
74+
75+
impl->status = SND_SERVER_DOWN;
76+
77+
LOG_F(INFO, "Sound Server shut down.");
78+
}
79+
80+
static void poll_sound(uint8_t *sound_buffer, int sound_buffer_size, DmaOutChannel *dma_ch) {
81+
if (!dma_ch->is_active()) {
82+
return;
83+
}
84+
85+
// Let the JS side get ahead a bit, in case we can't feed it fast enough.
86+
int js_audio_buffer_size = EM_ASM_INT_V({ return workerApi.audioBufferSize(); });
87+
if (js_audio_buffer_size >= sound_buffer_size * 4) {
88+
return;
89+
}
90+
91+
int req_size = sound_buffer_size;
92+
int out_size = 0;
93+
94+
while (req_size > 0) {
95+
uint8_t *chunk;
96+
uint32_t chunk_size;
97+
if (!dma_ch->pull_data(req_size, &chunk_size, &chunk)) {
98+
std::copy(chunk, chunk + chunk_size, sound_buffer + out_size);
99+
req_size -= chunk_size;
100+
out_size += chunk_size;
101+
} else {
102+
break;
103+
}
104+
}
105+
106+
EM_ASM_({ workerApi.enqueueAudio($0, $1); }, sound_buffer, out_size);
48107
}
49108

50109
int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data)
51110
{
52-
LOG_F(INFO, "SoundServer::open_out_stream()");
111+
uint32_t sample_size = 16;
112+
uint32_t channels = 2;
113+
114+
// The audio worklet API processes things in 128 frame chunks. Have some
115+
// buffer to make sure we don't starve it, but don't buffer too much either,
116+
// to avoid latency.
117+
int audio_frames_per_block = 384;
118+
int sound_buffer_size = (sample_size >> 3) * channels * audio_frames_per_block;
119+
impl->sound_buffer = std::make_unique<uint8_t[]>(sound_buffer_size);
120+
121+
impl->poll_cb = std::bind(poll_sound, impl->sound_buffer.get(), sound_buffer_size, static_cast<DmaOutChannel*>(user_data));
122+
impl->status = SND_SERVER_STREAM_OPENED;
123+
124+
EM_ASM_({ workerApi.didOpenAudio($0, $1, $2, $3); }, sample_rate, sample_size, channels);
125+
53126
return 0;
54127
}
55128

56129
int SoundServer::start_out_stream()
57130
{
58131
LOG_F(INFO, "SoundServer::start_out_stream()");
132+
impl->poll_timer = TimerManager::get_instance()->add_cyclic_timer(MSECS_TO_NSECS(1), impl->poll_cb);
133+
impl->status = SND_SERVER_STREAM_STARTED;
134+
59135
return 0;
60136
}
61137

62138
void SoundServer::close_out_stream()
63139
{
64140
LOG_F(INFO, "SoundServer::close_out_stream()");
141+
if (impl->status == SND_SERVER_STREAM_STARTED) {
142+
TimerManager::get_instance()->cancel_timer(impl->poll_timer);
143+
}
144+
impl->status = SND_SERVER_STARTED;
145+
65146
}

0 commit comments

Comments
 (0)