aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakamichi Horikawa <takamichiho@gmail.com>2017-07-12 00:34:35 +0900
committerTakamichi Horikawa <takamichiho@gmail.com>2017-07-12 00:34:35 +0900
commit44c20e41ddd0c394daf5aa45d88baa95334f04fc (patch)
tree9d503654c49585ee580c4033e086e1a2275b933e
parentf0c957012f65775976be05d2497a6a30c222b7e6 (diff)
test soundout
-rw-r--r--gtk/Makefile.am14
-rw-r--r--gtk/configure.ac4
-rw-r--r--gtk/main.c67
-rw-r--r--gtk/soundout/alsaout.c152
-rw-r--r--gtk/soundout/alsaout.h9
-rw-r--r--gtk/soundout/jackout.c127
-rw-r--r--gtk/soundout/jackout.h9
-rw-r--r--gtk/soundout/ossout.c8
-rw-r--r--gtk/soundout/pulseout.c133
-rw-r--r--gtk/soundout/pulseout.h9
-rw-r--r--gtk/soundout/soundout.c14
-rw-r--r--gtk/soundout/soundout.h16
12 files changed, 511 insertions, 51 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 8777418..43a767d 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -19,12 +19,17 @@ FMDSP_SRC=../fmdsp/fmdsp.c \
../fmdsp/font_fmdsp_small.c \
../fmdsp/fmdsp_platform_unix.c
+SOUNDOUT_SRC=soundout/soundout.c \
+ soundout/pulseout.c \
+ soundout/jackout.c \
+ soundout/alsaout.c
+
#fmplayer_CFLAGS=$(CFLAGS)
#CFLAGS=
fmplayer_CPPFLAGS=-Wall -Wextra -pedantic \
- -I.. \
- $(GTK3_CFLAGS) $(PORTAUDIO_CFLAGS) $(SNDFILE_CFLAGS)
-fmplayer_LDADD=$(GTK3_LIBS) $(PORTAUDIO_LIBS) $(SNDFILE_LIBS)
+ -I.. -Isoundout \
+ $(GTK3_CFLAGS) $(JACK_CFLAGS) $(PULSE_CFLAGS) $(ALSA_CFLAGS) $(SNDFILE_CFLAGS)
+fmplayer_LDADD=$(GTK3_LIBS) $(JACK_LIBS) $(PULSE_LIBS) $(ALSA_LIBS) $(SNDFILE_LIBS) -lm -lpthread
if ENABLE_NEON
LIBOPNA_SRC+=../libopna/opnassg-sinc-neon.s
@@ -52,7 +57,8 @@ fmplayer_SOURCES=main.c \
../fft/fft.c \
$(LIBOPNA_SRC) \
$(FMDRIVER_SRC) \
- $(FMDSP_SRC)
+ $(FMDSP_SRC) \
+ $(SOUNDOUT_SRC)
if ENABLE_OPENGL
fmplayer_SOURCES+=oscilloview-gl.c
diff --git a/gtk/configure.ac b/gtk/configure.ac
index cab8553..8a61ac1 100644
--- a/gtk/configure.ac
+++ b/gtk/configure.ac
@@ -6,7 +6,9 @@ AC_PROG_RANLIB
AM_PROG_AR
AM_PROG_AS
-PKG_CHECK_MODULES([PORTAUDIO], [portaudio-2.0])
+PKG_CHECK_MODULES([JACK], [jack soxr])
+PKG_CHECK_MODULES([PULSE], [libpulse])
+PKG_CHECK_MODULES([ALSA], [alsa])
PKG_CHECK_MODULES([GTK3], [gtk+-3.0 cairo])
PKG_CHECK_MODULES([SNDFILE], [sndfile])
diff --git a/gtk/main.c b/gtk/main.c
index ba84df8..948adf1 100644
--- a/gtk/main.c
+++ b/gtk/main.c
@@ -1,5 +1,4 @@
#include <gtk/gtk.h>
-#include <portaudio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
@@ -21,6 +20,8 @@
#include "common/fmplayer_common.h"
#include "fft/fft.h"
+#include "soundout.h"
+
#include "fmplayer.xpm"
#include "fmplayer32.xpm"
@@ -29,6 +30,7 @@
enum {
SRATE = 55467,
+// SRATE = 55555,
PPZ8MIX = 0xa000,
AUDIOBUFLEN = 0,
};
@@ -40,9 +42,8 @@ static struct {
GtkWidget *box_widget;
GtkWidget *fmdsp_widget;
GtkWidget *filechooser_widget;
- bool pa_initialized;
- bool pa_paused;
- PaStream *pastream;
+ bool sound_paused;
+ struct sound_state *ss;
struct opna opna;
struct opna_timer opna_timer;
struct ppz8 ppz8;
@@ -68,10 +69,9 @@ static struct {
};
static void quit(void) {
- if (g.pastream) {
- Pa_CloseStream(g.pastream);
+ if (g.ss) {
+ g.ss->free(g.ss);
}
- if (g.pa_initialized) Pa_Terminate();
fmplayer_file_free(g.fmfile);
gtk_main_quit();
}
@@ -118,17 +118,9 @@ static void msgbox_err(const char *msg) {
gtk_widget_destroy(d);
}
-
-static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,
- const PaStreamCallbackTimeInfo *timeinfo,
- PaStreamCallbackFlags statusFlags,
- void *userdata) {
- (void)inptr;
- (void)timeinfo;
- (void)statusFlags;
- struct opna_timer *timer = (struct opna_timer *)userdata;
- int16_t *buf = (int16_t *)outptr;
- memset(outptr, 0, sizeof(int16_t)*frames*2);
+static void soundout_cb(void *userptr, int16_t *buf, unsigned frames) {
+ struct opna_timer *timer = (struct opna_timer *)userptr;
+ memset(buf, 0, sizeof(int16_t)*frames*2);
opna_timer_mix_oscillo(timer, buf, frames,
g.oscillo_should_update ?
g.oscillodata_audiothread : 0);
@@ -150,7 +142,6 @@ static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,
fft_write(&g.at_fftdata, buf, frames);
atomic_flag_clear_explicit(&g.at_fftdata_flag, memory_order_release);
}
- return paContinue;
}
static void load_fontrom(void) {
@@ -188,10 +179,6 @@ err:
static bool openfile(const char *uri) {
struct fmplayer_file *fmfile = 0;
- if (!g.pa_initialized) {
- msgbox_err("Could not initialize Portaudio");
- goto err;
- }
enum fmplayer_file_error error;
fmfile = fmplayer_file_alloc(uri, &error);
if (!fmfile) {
@@ -206,19 +193,14 @@ static bool openfile(const char *uri) {
free(errbuf);
goto err;
}
- if (!g.pastream) {
- PaError pe = Pa_OpenDefaultStream(&g.pastream, 0, 2, paInt16, SRATE, AUDIOBUFLEN,
- pastream_cb, &g.opna_timer);
- if (pe != paNoError) {
- msgbox_err("cannot open portaudio stream");
- goto err;
- }
- } else if (!g.pa_paused) {
- PaError pe = Pa_StopStream(g.pastream);
- if (pe != paNoError) {
- msgbox_err("Portaudio Error");
+ if (!g.ss) {
+ g.ss = sound_init("FMPlayer", SRATE, soundout_cb, &g.opna_timer);
+ if (!g.ss) {
+ msgbox_err("cannot open audio stream");
goto err;
}
+ } else if (!g.sound_paused) {
+ g.ss->pause(g.ss, 1, 0);
}
fmplayer_file_free(g.fmfile);
g.fmfile = fmfile;
@@ -236,8 +218,8 @@ static bool openfile(const char *uri) {
}
fmplayer_file_load(&g.work, g.fmfile, 1);
fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
- Pa_StartStream(g.pastream);
- g.pa_paused = false;
+ g.ss->pause(g.ss, 0, 1);
+ g.sound_paused = false;
g.work.paused = false;
{
const char *turi = strdup(uri);
@@ -404,15 +386,9 @@ static gboolean key_press_cb(GtkWidget *w,
}
break;
case GDK_KEY_F7:
- if (g.pa_paused) {
- Pa_StartStream(g.pastream);
- g.pa_paused = false;
- g.work.paused = false;
- } else {
- Pa_StopStream(g.pastream);
- g.pa_paused = true;
- g.work.paused = true;
- }
+ g.sound_paused ^= 1;
+ g.work.paused = g.sound_paused;
+ g.ss->pause(g.ss, g.sound_paused, 0);
break;
case GDK_KEY_F11:
fmdsp_dispstyle_set(&g.fmdsp, (g.fmdsp.style+1) % FMDSP_DISPSTYLE_CNT);
@@ -528,7 +504,6 @@ int main(int argc, char **argv) {
create_box();
- g.pa_initialized = (Pa_Initialize() == paNoError);
fmdsp_init(&g.fmdsp, &g.font98);
fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
g.vram32_stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, PC98_W);
diff --git a/gtk/soundout/alsaout.c b/gtk/soundout/alsaout.c
new file mode 100644
index 0000000..5c5e667
--- /dev/null
+++ b/gtk/soundout/alsaout.c
@@ -0,0 +1,152 @@
+#include "alsaout.h"
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <sys/eventfd.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <poll.h>
+#include <asoundlib.h>
+
+enum {
+ BUF_FRAMES = 1024,
+};
+
+struct alsaout_state {
+ struct sound_state ss;
+ sound_callback cbfunc;
+ void *userptr;
+ snd_pcm_t *apcm;
+ int fd_event;
+ int fds_space;
+ struct pollfd *fds;
+ bool paused;
+ bool terminate;
+ atomic_flag cb_flag;
+ bool thread_valid;
+ pthread_t alsa_thread;
+ int16_t buf[BUF_FRAMES*2];
+};
+
+static void *alsaout_thread(void *ptr) {
+ struct alsaout_state *as = ptr;
+ for (;;) {
+ as->fds[0] = (struct pollfd) {
+ .fd = as->fd_event,
+ .events = POLLIN,
+ };
+ int fd_cnt = 1;
+ fd_cnt += snd_pcm_poll_descriptors(
+ as->apcm, as->fds + 1, as->fds_space - 1
+ );
+ int err = poll(as->fds, fd_cnt, -1);
+ if (err <= 0) {
+ continue;
+ }
+ if (as->terminate) return 0;
+ if (as->fds[0].revents) {
+ uint64_t eventdata;
+ read(as->fd_event, &eventdata, sizeof(eventdata));
+ }
+ unsigned short event;
+ if (snd_pcm_poll_descriptors_revents(
+ as->apcm, as->fds + 1, fd_cnt - 1, &event) < 0) continue;
+ if (!event) continue;
+ snd_pcm_sframes_t frames = snd_pcm_avail_update(as->apcm);
+ if (frames <= 0) continue;
+ while (frames) {
+ snd_pcm_sframes_t genframes = frames;
+ if (genframes > BUF_FRAMES) genframes = BUF_FRAMES;
+ while (atomic_flag_test_and_set_explicit(
+ &as->cb_flag, memory_order_acquire));
+ if (as->paused) {
+ atomic_flag_clear_explicit(&as->cb_flag, memory_order_release);
+ break;
+ }
+ as->cbfunc(as->userptr, as->buf, genframes);
+ atomic_flag_clear_explicit(&as->cb_flag, memory_order_release);
+ frames -= genframes;
+ if (snd_pcm_state(as->apcm) == SND_PCM_STATE_XRUN) {
+ err = snd_pcm_prepare(as->apcm);
+ }
+ snd_pcm_sframes_t written = snd_pcm_writei(as->apcm, as->buf, genframes);
+ if (written < 0) {
+ snd_pcm_prepare(as->apcm);
+ }
+ }
+ }
+}
+
+static void alsaout_pause(struct sound_state *ss, int pause, int flush) {
+ struct alsaout_state *as = (struct alsaout_state *)ss;
+
+ while (atomic_flag_test_and_set_explicit(
+ &as->cb_flag, memory_order_acquire));
+ as->paused = pause;
+ snd_pcm_pause(as->apcm, pause);
+ atomic_flag_clear_explicit(&as->cb_flag, memory_order_release);
+ uint64_t event = 1;
+ write(as->fd_event, &event, sizeof(event));
+}
+
+static void alsaout_free(struct sound_state *ss) {
+ if (!ss) return;
+ struct alsaout_state *as = (struct alsaout_state *)ss;
+ if (as->thread_valid) {
+ as->terminate = true;
+ uint64_t event = 1;
+ write(as->fd_event, &event, sizeof(event));
+ pthread_join(as->alsa_thread, 0);
+ }
+ if (as->fd_event != -1) {
+ close(as->fd_event);
+ }
+ if (as->fds) free(as->fds);
+ if (as->apcm) {
+ snd_pcm_close(as->apcm);
+ }
+ free(as);
+}
+
+struct sound_state *alsaout_init(
+ const char *clientname, unsigned srate,
+ sound_callback cbfunc, void *userptr) {
+ (void)clientname;
+ struct alsaout_state *as = malloc(sizeof(*as));
+ if (!as) goto err;
+ *as = (struct alsaout_state) {
+ .ss = {
+ .pause = alsaout_pause,
+ .free = alsaout_free,
+ .apiname = "ALSA",
+ },
+ .cbfunc = cbfunc,
+ .userptr = userptr,
+ .fd_event = -1,
+ .cb_flag = ATOMIC_FLAG_INIT,
+ .paused = true,
+ };
+ if (snd_pcm_open(&as->apcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) {
+ goto err;
+ }
+ as->fd_event = eventfd(0, EFD_CLOEXEC);
+ if (as->fd_event < 0) goto err;
+ as->fds_space = snd_pcm_poll_descriptors_count(as->apcm) + 1;
+ as->fds = malloc(sizeof(*as->fds) * as->fds_space);
+ if (!as->fds) goto err;
+ if (snd_pcm_set_params(
+ as->apcm,
+ SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 2, srate, 1,
+ 1000*1000/60)) {
+ goto err;
+ }
+ if (pthread_create(&as->alsa_thread, 0, alsaout_thread, as)) {
+ goto err;
+ }
+ as->thread_valid = true;
+ return as;
+
+err:
+ alsaout_free(&as->ss);
+ return 0;
+}
diff --git a/gtk/soundout/alsaout.h b/gtk/soundout/alsaout.h
new file mode 100644
index 0000000..cd79c27
--- /dev/null
+++ b/gtk/soundout/alsaout.h
@@ -0,0 +1,9 @@
+#ifndef MYON_ALSAOUT_H_INCLUDED
+#define MYON_ALSAOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct sound_state *alsaout_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_ALSAOUT_H_INCLUDED
+
diff --git a/gtk/soundout/jackout.c b/gtk/soundout/jackout.c
new file mode 100644
index 0000000..54e7f16
--- /dev/null
+++ b/gtk/soundout/jackout.c
@@ -0,0 +1,127 @@
+#include "jackout.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+#include <soxr.h>
+
+typedef jack_default_audio_sample_t sample_t;
+
+enum {
+ RFRAMES = 1024,
+ JFRAMES = 2048,
+};
+
+struct jackout_state {
+ struct sound_state ss;
+ sound_callback cbfunc;
+ void *userptr;
+ jack_client_t *jc;
+ jack_port_t *jp[2];
+ soxr_t soxr;
+ bool paused;
+ int16_t rbuf[RFRAMES*2];
+ float jbuf[JFRAMES*2];
+};
+
+static size_t soxr_cb(void *ptr, soxr_in_t *data, size_t nframes) {
+ struct jackout_state *js = ptr;
+ if (nframes > RFRAMES) nframes = RFRAMES;
+ for (size_t i = 0; i < RFRAMES*2; i++) {
+ js->rbuf[i] = 0;
+ }
+ js->cbfunc(js->userptr, js->rbuf, nframes);
+ *data = js->rbuf;
+ return nframes;
+}
+
+static int jack_cb(jack_nframes_t nframes, void *arg) {
+ struct jackout_state *js = arg;
+ sample_t *out[2];
+ for (int i = 0; i < 2; i++) out[i] = jack_port_get_buffer(js->jp[i], nframes);
+ jack_nframes_t outi = 0;
+ while (nframes) {
+ jack_nframes_t pframes = nframes < JFRAMES ? nframes : JFRAMES;
+ pframes = soxr_output(js->soxr, js->jbuf, pframes);
+ for (jack_nframes_t j = 0; j < pframes; j++) {
+ out[0][outi] = js->jbuf[j*2+0];
+ out[1][outi] = js->jbuf[j*2+1];
+ outi++;
+ }
+ nframes -= pframes;
+ }
+ return 0;
+}
+
+static void jackout_pause(struct sound_state *ss, int pause, int flush) {
+ struct jackout_state *js = (struct jackout_state *)ss;
+ // TODO
+ if (js->paused && !pause) {
+ if (flush) {
+ soxr_clear(js->soxr);
+ soxr_set_input_fn(js->soxr, soxr_cb, js, RFRAMES);
+ }
+ jack_activate(js->jc);
+ jack_connect(js->jc, jack_port_name(js->jp[0]), "system:playback_1");
+ jack_connect(js->jc, jack_port_name(js->jp[1]), "system:playback_2");
+ } else if (!js->paused && pause) {
+ jack_deactivate(js->jc);
+ }
+ js->paused = pause;
+}
+
+static void jackout_free(struct sound_state *ss) {
+ struct jackout_state *js = (struct jackout_state *)ss;
+ if (js) {
+ if (js->jc) {
+ for (int i = 0; i < 2; i++) {
+ if (js->jp[i]) jack_port_unregister(js->jc, js->jp[i]);
+ }
+ jack_client_close(js->jc);
+ }
+ if (js->soxr) soxr_delete(js->soxr);
+ free(js);
+ }
+}
+
+struct sound_state *jackout_init(
+ const char *clientname, unsigned srate,
+ sound_callback cbfunc, void *userptr) {
+ struct jackout_state *js = malloc(sizeof(*js));
+ if (!js) goto err;
+ *js = (struct jackout_state){
+ .ss = {
+ .pause = jackout_pause,
+ .free = jackout_free,
+ .apiname = "JACK Audio",
+ },
+ .cbfunc = cbfunc,
+ .userptr = userptr,
+ .paused = true,
+ };
+ js->jc = jack_client_open(clientname, 0, 0);
+ if (!js->jc) goto err;
+ for (int i = 0; i < 2; i++) {
+ js->jp[i] = jack_port_register(
+ js->jc, i ? "right" : "left", JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput | JackPortIsTerminal, 0);
+ if (!js->jp[i]) goto err;
+ }
+ soxr_io_spec_t iospec = {
+ .itype = SOXR_INT16_I,
+ .otype = SOXR_FLOAT32_I,
+ .scale = 1.0,
+ };
+ js->soxr = soxr_create(
+ srate, jack_get_sample_rate(js->jc),
+ 2, 0, &iospec, 0, 0
+ );
+ if (!js->soxr) goto err;
+ soxr_set_input_fn(js->soxr, soxr_cb, js, RFRAMES);
+ if (jack_set_process_callback(js->jc, jack_cb, js)) goto err;
+
+ return &js->ss;
+err:
+ jackout_free(&js->ss);
+ return 0;
+}
diff --git a/gtk/soundout/jackout.h b/gtk/soundout/jackout.h
new file mode 100644
index 0000000..4b72f2e
--- /dev/null
+++ b/gtk/soundout/jackout.h
@@ -0,0 +1,9 @@
+#ifndef MYON_JACKOUT_H_INCLUDED
+#define MYON_JACKOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct sound_state *jackout_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_JACKOUT_H_INCLUDED
+
diff --git a/gtk/soundout/ossout.c b/gtk/soundout/ossout.c
new file mode 100644
index 0000000..1083f20
--- /dev/null
+++ b/gtk/soundout/ossout.c
@@ -0,0 +1,8 @@
+#include <sys/stat.h>
+#include <fcntl.h>
+
+struct ossout_state {
+ struct sound_state ss;
+};
+
+struct sound_state *ossout_init(
diff --git a/gtk/soundout/pulseout.c b/gtk/soundout/pulseout.c
new file mode 100644
index 0000000..9dbcd6c
--- /dev/null
+++ b/gtk/soundout/pulseout.c
@@ -0,0 +1,133 @@
+#include "pulseout.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/stream.h>
+
+struct pulseout_state {
+ struct sound_state ss;
+ bool paused;
+ bool flush;
+ unsigned srate;
+ sound_callback cbfunc;
+ void *userptr;
+ pa_threaded_mainloop *pa_tm;
+ pa_context *pa_c;
+ pa_stream *pa_s;
+ bool pa_status_changed;
+};
+
+static void pulseout_pause(struct sound_state *ss, int pause, int flush) {
+ //return;
+ struct pulseout_state *ps = (struct pulseout_state *)ss;
+ if (ps->paused != !!pause) {
+ if (pause) {
+ pa_threaded_mainloop_lock(ps->pa_tm);
+ }
+ ps->paused = pause;
+ if (!pause) {
+ if (flush) ps->flush = true;
+ pa_threaded_mainloop_unlock(ps->pa_tm);
+ }
+ }
+}
+
+static void pulseout_free(struct sound_state *ss) {
+ struct pulseout_state *ps = (struct pulseout_state *)ss;
+ if (ps) {
+ if (ps->pa_tm) {
+ if (ps->paused) pa_threaded_mainloop_unlock(ps->pa_tm);
+ pa_threaded_mainloop_stop(ps->pa_tm);
+ if (ps->pa_s) {
+ pa_stream_disconnect(ps->pa_s);
+ pa_stream_unref(ps->pa_s);
+ }
+ if (ps->pa_c) pa_context_unref(ps->pa_c);
+ pa_threaded_mainloop_free(ps->pa_tm);
+ }
+ free(ps);
+ }
+}
+
+static void pulseout_cb(pa_stream *p, size_t bytes, void *userdata) {
+ struct pulseout_state *ps = userdata;
+ int16_t *buf;
+ pa_stream_begin_write(p, (void **)&buf, &bytes);
+ size_t nframes = bytes / (2 * sizeof(int16_t));
+ if (!ps->paused) {
+ ps->cbfunc(ps->userptr, buf, nframes);
+ } else {
+ for (size_t i = 0; i < nframes; i++) {
+ buf[i*2+0] = 0;
+ buf[i*2+1] = 0;
+ }
+ }
+ pa_seek_mode_t smode =
+ ps->flush ? PA_SEEK_RELATIVE_ON_READ : PA_SEEK_RELATIVE;
+ pa_stream_write(p, buf, nframes * 2 * sizeof(int16_t), 0, 0, smode);
+ ps->flush = false;
+}
+
+static void pa_c_cb(pa_context *pa_c, void *userdata) {
+ struct pulseout_state *ps = userdata;
+ ps->pa_status_changed = true;
+ pa_threaded_mainloop_signal(ps->pa_tm, 0);
+}
+
+struct sound_state *pulseout_init(
+ const char *clientname, unsigned srate,
+ sound_callback cbfunc, void *userptr) {
+ struct pulseout_state *ps = malloc(sizeof(*ps));
+ if (!ps) goto err;
+ *ps = (struct pulseout_state){
+ .ss = {
+ .pause = pulseout_pause,
+ .free = pulseout_free,
+ .apiname = "PulseAudio",
+ },
+ .cbfunc = cbfunc,
+ .userptr = userptr,
+ .paused = false,
+ .srate = srate,
+ };
+ ps->pa_tm = pa_threaded_mainloop_new();
+ if (!ps->pa_tm) goto err;
+ ps->pa_c = pa_context_new(
+ pa_threaded_mainloop_get_api(ps->pa_tm), clientname
+ );
+ if (!ps->pa_c) goto err;
+ if (pa_context_connect(ps->pa_c, 0, 0, 0) < 0) goto err;
+ pa_context_set_state_callback(ps->pa_c, pa_c_cb, ps);
+ if (pa_threaded_mainloop_start(ps->pa_tm) < 0) goto err;
+ pa_threaded_mainloop_lock(ps->pa_tm);
+ ps->paused = true;
+ for (;;) {
+ while (!ps->pa_status_changed) {
+ pa_threaded_mainloop_wait(ps->pa_tm);
+ }
+ ps->pa_status_changed = false;
+ pa_context_state_t state = pa_context_get_state(ps->pa_c);
+ if (state == PA_CONTEXT_CONNECTING ||
+ state == PA_CONTEXT_AUTHORIZING ||
+ state == PA_CONTEXT_SETTING_NAME) continue;
+ else if (state == PA_CONTEXT_READY) break;
+ else goto err;
+ }
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16NE,
+ .rate = ps->srate,
+ .channels = 2,
+ };
+ ps->pa_s = pa_stream_new(
+ ps->pa_c,
+ "stereoout", &ss, 0
+ );
+ if (!ps->pa_s) goto err;
+ pa_stream_set_write_callback(ps->pa_s, pulseout_cb, ps);
+ if (pa_stream_connect_playback(ps->pa_s, 0, 0, 0, 0, 0) < 0) goto err;
+ return &ps->ss;
+err:
+ pulseout_free(&ps->ss);
+ return 0;
+}
diff --git a/gtk/soundout/pulseout.h b/gtk/soundout/pulseout.h
new file mode 100644
index 0000000..d9fbb20
--- /dev/null
+++ b/gtk/soundout/pulseout.h
@@ -0,0 +1,9 @@
+#ifndef MYON_PULSEOUT_H_INCLUDED
+#define MYON_PULSEOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct sound_state *pulseout_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_PULSEOUT_H_INCLUDED
+
diff --git a/gtk/soundout/soundout.c b/gtk/soundout/soundout.c
new file mode 100644
index 0000000..56134be
--- /dev/null
+++ b/gtk/soundout/soundout.c
@@ -0,0 +1,14 @@
+#include "soundout.h"
+#include "jackout.h"
+#include "pulseout.h"
+#include "alsaout.h"
+
+struct sound_state *sound_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr) {
+ struct sound_state *ss = 0;
+ //ss = jackout_init(clientname, srate, cbfunc, userptr);
+ if (ss) return ss;
+ //ss = pulseout_init(clientname, srate, cbfunc, userptr);
+ if (ss) return ss;
+ ss = alsaout_init(clientname, srate, cbfunc, userptr);
+ return ss;
+}
diff --git a/gtk/soundout/soundout.h b/gtk/soundout/soundout.h
new file mode 100644
index 0000000..aea926f
--- /dev/null
+++ b/gtk/soundout/soundout.h
@@ -0,0 +1,16 @@
+#ifndef MYON_SOUNDOUT_H_INCLUDED
+#define MYON_SOUNDOUT_H_INCLUDED
+
+#include <stdint.h>
+
+typedef void (*sound_callback)(void *userptr, int16_t *buf, unsigned frames);
+
+struct sound_state {
+ void (*pause)(struct sound_state *state, int pause, int flush);
+ void (*free)(struct sound_state *state);
+ const char *apiname;
+};
+
+struct sound_state *sound_init(const char *clientname, unsigned srate, sound_callback cbfunc, void *userptr);
+
+#endif // MYON_SOUNDOUT_H_INCLUDED