[commit] r2131 - add pulse audio driver

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[commit] r2131 - add pulse audio driver

commits-3
 
Author: piumarta
Date: 2009-09-14 14:00:51 -0700 (Mon, 14 Sep 2009)
New Revision: 2131

Added:
   trunk/platforms/unix/vm-sound-pulse/
   trunk/platforms/unix/vm-sound-pulse/config.cmake
   trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c
Modified:
   trunk/platforms/unix/ChangeLog
Log:
add pulse audio driver

Modified: trunk/platforms/unix/ChangeLog
===================================================================
--- trunk/platforms/unix/ChangeLog 2009-09-10 20:04:16 UTC (rev 2130)
+++ trunk/platforms/unix/ChangeLog 2009-09-14 21:00:51 UTC (rev 2131)
@@ -1,3 +1,8 @@
+2009-09-14    <[hidden email]>
+
+ * vm-sound-pulse: Pulse Audio driver added, thanks to Derek
+ O'Connell.
+
 2009-09-10    <[hidden email]>
 
  * plugins/SqueakFFIPrims/x86-sysv-asm.S (ffiCallAddressOf): Pop a

Added: trunk/platforms/unix/vm-sound-pulse/config.cmake
===================================================================
--- trunk/platforms/unix/vm-sound-pulse/config.cmake                        (rev 0)
+++ trunk/platforms/unix/vm-sound-pulse/config.cmake 2009-09-14 21:00:51 UTC (rev 2131)
@@ -0,0 +1 @@
+PLUGIN_REQUIRE_PACKAGE (LIBPULSE libpulse)

Added: trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c
===================================================================
--- trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c                        (rev 0)
+++ trunk/platforms/unix/vm-sound-pulse/sqUnixSoundPulseAudio.c 2009-09-14 21:00:51 UTC (rev 2131)
@@ -0,0 +1,1255 @@
+/* sqUnixSoundPulseAudio.c -- sound module for Pulse Audio
+ *
+ * Author: Derek O'Connell <[hidden email]>
+ *
+ *   Copyright (C) 2009 by Derek O'Connel
+ *   All rights reserved.
+ *  
+ *   This file is part of Unix Squeak.
+ *
+ *   Permission is hereby granted, free of charge, to any person obtaining a
+ *   copy of this software and associated documentation files (the "Software"),
+ *   to deal in the Software without restriction, including without limitation
+ *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ *   and/or sell copies of the Software, and to permit persons to whom the
+ *   Software is furnished to do so, subject to the following conditions:
+ *
+ *   The above copyright notice and this permission notice shall be included in
+ *   all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ *   DEALINGS IN THE SOFTWARE.
+ *
+ * Last edited: 2009-09-14 13:59:33 by piumarta on ubuntu.piumarta.com
+ */
+
+/* ========== */
+/* INCLUDES   */
+/* ========== */
+
+#include "sq.h"
+#include <errno.h>
+#include <signal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/errno.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+#include <glib.h>
+*/
+#include <pthread.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+/*
+#include <pulse/gccmacro.h>
+*/
+
+/* ========== */
+/* MACROS     */
+/* ========== */
+
+#define FAIL(X)   \
+{     \
+ success(false); \
+ return X;   \
+}
+
+#define snd(expr, what) \
+  if ((rc = snd_##expr) < 0) \
+    { \
+      fprintf(stderr, "%s: %s\n", what, snd_strerror(rc)); \
+      success(false); \
+      return rc; \
+    }
+
+
+/* ================================== DEBUGGING  */
+
+#define xDBG
+
+#ifdef DBG
+ #define DBG_MSG_MAX_LEN 128
+
+ char *dbg_msg[DBG_MSG_MAX_LEN];
+
+ #define DBGMSG(M) { \
+ printf("DBG: sqUnixSoundMaemo: %s (%d, %s)\n", M, errno, strerror (errno)); \
+ errno = 0; \
+ }
+
+ #define DBGERR(M, E) { \
+ sprintf(*dbg_msg, M, E); \
+ DBGMSG(dbg_msg); \
+ }
+
+#else
+ #define DBGMSG(M)
+ #define DBGERR(M, E)
+#endif
+
+
+/* ================================== TYPES */
+
+typedef struct {
+ short *buffer;
+ unsigned long samples;
+ int isFree;
+} audioBuffer_t;
+
+typedef struct {
+ pthread_mutex_t *mutex;
+ pthread_cond_t *cond;
+ unsigned int count;
+} gen_sig_t;
+
+typedef struct {
+ /* Left in for debugging >>> */
+ const char *dbgName;
+ const char *device;
+ /* <<< */
+
+ int open;
+
+ unsigned long maxSamples;
+ unsigned long maxWords;
+ unsigned long maxBytes;
+
+ audioBuffer_t *buffer;
+
+ int maxBuffers;
+ int buffersAllocated;
+ int bufferFree;
+ int bufferNext;
+ int bufferCount;
+ int bufferFull;
+
+ pthread_mutex_t *bufferMutex;
+
+ void * threadFunc;
+ pthread_t thread;
+
+ gen_sig_t sigRun;
+ gen_sig_t sigStalled;
+
+ int running;
+ int exit;
+ int stall;
+ int sqSemaphore;
+
+ int rateID;
+ int bytesPerFrame;
+
+ /* PULSE, Simple API parameters */
+ pa_stream_direction_t dir;
+ const char *stream_name;
+ pa_simple *pa_conn;
+  pa_sample_spec pa_spec;
+ } audioIO_t;
+
+
+/* ================================== FUNCTION PROTOTYPES */
+
+static int rate(int rateID);
+static int rateID(int rate);
+
+static inline unsigned short _swapw(unsigned short v); /* From io.h */
+
+static int devInputReady(int dev_fd);
+
+static void sigWait(gen_sig_t *sig);
+static void sigReset(gen_sig_t *sig);
+static void sigSignal(gen_sig_t *sig);
+
+static void ioThreadWaitToRun(audioIO_t *audioIO);
+static void ioThreadExit(audioIO_t *audioIO);
+static int  ioThreadStart(audioIO_t *audioIO);
+static int  ioThreadIsRunning(audioIO_t *audioIO);
+static void ioThreadStall(audioIO_t *audioIO);
+
+static void ioZeroBuffers(audioIO_t *audioIO);
+static void ioFreeBuffers(audioIO_t *audioIO);
+static int  ioFreeBytes(audioIO_t *audioIO);
+static int  ioIsFull(audioIO_t *audioIO);
+static int  ioAddPlayBuffer(void *buffer, int frameCount);
+static int  ioGetRecordBuffer(void *buffer, int bufferBytes);
+static int  ioAllocBuffers(audioIO_t *audioIO, int frameCount);
+static int  ioGetBufferData(audioIO_t *audioIO, void **buffer, int *frames);
+static int  ioNextBuffer(audioIO_t *audioIO);
+
+static void *writerThread(void *ptr);
+static void *readerThread(void *ptr);
+
+static int  ioInit();
+
+/* SQUEAK INTERFACE */
+
+static int trace();
+
+static sqInt sound_AvailableSpace(void);
+static sqInt sound_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, int samplesOfLeadTime);
+static sqInt sound_PlaySamplesFromAtLength(int frameCount, int arrayIndex, int startIndex);
+static sqInt sound_PlaySilence(void);
+static sqInt sound_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex);
+static sqInt sound_Stop(void);
+
+static sqInt sound_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex);
+static sqInt sound_StopRecording(void);
+static double sound_GetRecordingSampleRate(void);
+static sqInt sound_RecordSamplesIntoAtLength(int buf, int startSliceIndex, int bufferSizeInBytes);
+
+static int mixer_open(char *name);
+static void mixer_close(void);
+static inline void mixer_getVolume(char *name, int captureFlag, double *leftLevel, double *rightLevel);
+static inline void mixer_setVolume(char *name, int captureFlag, double leftLevel, double rightLevel);
+static int mixer_setSwitch(char *name, int captureFlag, int parameter);
+static int mixer_getSwitch(char *name, int captureFlag, int channel);
+static void sound_Volume(double *left, double *right);
+static void sound_SetVolume(double left, double right);
+static sqInt sound_SetRecordLevel(sqInt level);
+static sqInt sound_SetDevice(sqInt id, char *arg);
+static sqInt sound_GetSwitch(sqInt id, sqInt captureFlag, sqInt channel);
+static sqInt sound_SetSwitch(sqInt id, sqInt captureFlag, sqInt parameter);
+
+
+/* ==================== */
+/* ==================== GLOBALS */
+/* ==================== */
+
+/* Left in but not used >>> */
+#define SQ_SND_PLAY_START_THRESHOLD 7/8
+#define SQ_SND_PLAY_AVAIL_MIN 4/8
+/* <<< */
+
+/* Arbitrary (apart from minmising latency) >>> */
+#define MAX_INPUT_BUFFERS 10
+#define MAX_OUTPUT_BUFFERS 2
+/* <<< */
+
+audioBuffer_t iBuffer[MAX_INPUT_BUFFERS];
+audioBuffer_t oBuffer[MAX_OUTPUT_BUFFERS];
+
+/* STATICALLY INITIALISED SO AUTO-DESTROYED (ON CRASHING FOR INSTANCE) >>> */
+
+/* input */
+
+pthread_mutex_t audioInBufferMutex = PTHREAD_MUTEX_INITIALIZER;
+
+pthread_mutex_t audioInRunMutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t audioInRunCond = PTHREAD_COND_INITIALIZER;
+
+pthread_mutex_t audioInStalledMutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t audioInStalledCond = PTHREAD_COND_INITIALIZER;
+
+/* output */
+
+pthread_mutex_t audioOutBufferMutex = PTHREAD_MUTEX_INITIALIZER;
+
+pthread_mutex_t audioOutRunMutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t audioOutRunCond = PTHREAD_COND_INITIALIZER;
+
+pthread_mutex_t audioOutStalledMutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t audioOutStalledCond = PTHREAD_COND_INITIALIZER;
+
+/* <<< */
+
+
+audioIO_t audioIn, audioOut;
+
+int initDone = false;
+
+/* EXTRA FOR ALSA BUT UNUSED >>> */
+/*
+static int output_buffer_frames_available = 1;
+static double max_delay_frames = 0;
+*/
+/* <<< */
+
+
+/* ================================== UTILS */
+
+/* RATE CONVERSION: from dsp code but not used (yet). Maybe not needed at all with AlSA */
+/* RATE CONVERSION: fixed preset rates are used. TBD: choose nearest to requested */
+/*
+static int rate(int rateID) {
+ if (SAMPLE_RATE_8KHZ   == rateID) return  8000;
+ if (SAMPLE_RATE_16KHZ   == rateID) return 16000;
+ if (SAMPLE_RATE_11_025KHZ == rateID) return 11025;
+ if (SAMPLE_RATE_22_05KHZ  == rateID) return 22050;
+ if (SAMPLE_RATE_44_1KHZ  == rateID) return 44100;
+ return -1;
+}
+
+static int rateID(int rate) {
+ if ( 8000 == rate) return SAMPLE_RATE_8KHZ;
+ if ( 8192 == rate) return SAMPLE_RATE_8KHZ;
+ if (16000 == rate) return SAMPLE_RATE_16KHZ;
+ if (11025 == rate) return SAMPLE_RATE_11_025KHZ;
+ if (22050 == rate) return SAMPLE_RATE_22_05KHZ;
+ if (44100 == rate) return SAMPLE_RATE_44_1KHZ;
+ return -1;
+}
+*/
+
+/* From io.h because recorded data has to be Big Endian */
+static inline unsigned short _swapw(unsigned short v) {
+ return ((v << 8) | (v >> 8));
+}
+
+
+/* Not used but maybe useful */
+/*
+static int devInputReady(int dev_fd) {
+ struct pollfd pfd;
+ pfd.fd = dev_fd;
+ pfd.events = POLLIN;
+ if (poll (&pfd,1,0)>0) return true;
+ return false;
+}
+*/
+
+static void printPALatency() {
+ pa_usec_t latency;
+ int error;
+
+ if ((latency = pa_simple_get_latency(audioOut.pa_conn, &error)) == (pa_usec_t) -1)
+ fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s\n", pa_strerror(error));
+ else
+ fprintf(stderr, "%0.0f usec    \r", (float)latency);
+}
+
+/*
+static int bytesPerChannel(audioIO_t *audioIO) {
+ if ( PA_SAMPLE_S16LE == audioIO->rate) return SAMPLE_RATE_8KHZ;
+ PA_SAMPLE_S16LE
+}
+*/
+
+/* ================================== Signal Ops */
+
+static void sigWait(gen_sig_t *sig) {
+ pthread_mutex_lock(sig->mutex);
+ while( !sig->count )
+ pthread_cond_wait(sig->cond, sig->mutex);
+ sig->count -= 1;
+ pthread_mutex_unlock(sig->mutex);
+}
+
+static void sigReset(gen_sig_t *sig) {
+ pthread_mutex_lock(sig->mutex);
+ sig->count = 0;
+ pthread_mutex_unlock(sig->mutex);
+}
+
+static void sigSignal(gen_sig_t *sig) {
+ pthread_mutex_lock(sig->mutex);
+ sig->count += 1;
+ pthread_cond_signal(sig->cond);
+ pthread_mutex_unlock(sig->mutex);
+}
+
+/* Here for debugging but direct calls would be ok >>> */
+static void signalSqueak(audioIO_t *audioIO) {
+/* printf("@%d",audioIO->sqSemaphore);
+*/
+ if (0 < audioIO->sqSemaphore)
+ signalSemaphoreWithIndex(audioIO->sqSemaphore);
+}
+/* <<< */
+
+
+/* ================================== Thread Ops */
+
+static void ioThreadExit(audioIO_t *audioIO) {
+ if (!audioIO->thread) return;
+ audioIO->exit = 1;
+ sigSignal(&audioIO->sigRun);
+ pthread_join(audioIO->thread, NULL);
+ audioIO->thread = 0;
+}
+
+static int ioThreadStart(audioIO_t *audioIO) {
+ int rc;
+ if (audioIO->thread) return true;
+ rc = pthread_create(&audioIO->thread, NULL, audioIO->threadFunc, NULL);
+ if (0 != rc) DBGERR("ioThreadStart(): %d", rc);
+ return rc;
+}
+
+static int ioThreadIsRunning(audioIO_t *audioIO) {
+ return audioIO->running;
+}
+
+static void ioThreadStall(audioIO_t *audioIO) {
+ audioIO->stall = true;
+ sigSignal(&audioIO->sigRun);
+ sigWait(&audioIO->sigStalled);
+}
+
+/* Don't attempt to signal Sq here as we may not have a semaphore! */
+static void ioThreadWaitToRun(audioIO_t *audioIO) {
+ sigSignal(&audioIO->sigStalled);
+
+ pthread_mutex_lock(audioIO->sigRun.mutex);
+ audioIO->running = false;
+
+ if (audioIO->stall) audioIO->sigRun.count = 0;
+ audioIO->stall = false;
+
+ while( !audioIO->sigRun.count )
+ pthread_cond_wait(audioIO->sigRun.cond, audioIO->sigRun.mutex);
+ audioIO->sigRun.count -= 1;
+
+ audioIO->running = true;
+ pthread_mutex_unlock(audioIO->sigRun.mutex);
+
+ sigReset(&audioIO->sigStalled);
+}
+
+/* ================================== Buffer ops */
+
+static void ioZeroBuffers(audioIO_t *audioIO) {
+ int i;
+ for(i=0; i < audioIO->maxBuffers; i++) {
+ audioIO->buffer[i].samples = 0;
+ audioIO->buffer[i].isFree  = true;
+ }
+}
+
+static void ioFreeBuffers(audioIO_t *audioIO) {
+ int i;
+ for(i=0; i < audioIO->maxBuffers; i++) {
+ free(audioIO->buffer[i].buffer);
+ audioIO->buffer[i].buffer  = 0;
+ audioIO->buffer[i].samples = 0;
+ }
+ audioIO->bufferFree  = audioIO->bufferNext = 0;
+ /* audioIO->bufferCount differs for play/record */
+}
+
+/* Only used for playing, not for recording */
+static int ioFreeBytes(audioIO_t *audioIO) {
+ int freeBytes;
+ pthread_mutex_lock(audioIO->bufferMutex);
+ freeBytes = audioIO->maxBytes * audioIO->bufferCount;
+ pthread_mutex_unlock(audioIO->bufferMutex);
+ return freeBytes;
+}
+
+static int ioAllocBuffers(audioIO_t *audioIO, int frameCount) {
+ int maxBytes;
+ int i;
+
+ /* NTS: should take audioIO->bytesPerFrame into account...
+ and that depends on stereo or not. Revisit at later date.
+ maxBytes = frameCount * audioIO->bytesPerFrame;
+ */
+
+ if (audioIO->buffersAllocated)
+ if (audioOut.maxSamples == frameCount)
+ return true;
+ else
+ ioFreeBuffers(audioIO);
+
+ audioIO->maxSamples = frameCount;
+ audioIO->maxBytes   = audioIO->maxSamples * audioIO->bytesPerFrame;
+ audioIO->maxWords   = audioIO->maxBytes >> 1;
+ for(i=0; i < audioIO->maxBuffers; i++) {
+ audioIO->buffer[i].buffer = (short *)calloc(audioIO->maxBytes, 1);
+ audioIO->buffer[i].isFree = true;
+ }
+ audioIO->buffersAllocated = true;
+
+ return true;
+}
+
+static int ioIsFull(audioIO_t *audioIO) {
+ pthread_mutex_lock(audioIO->bufferMutex);
+ audioIO->bufferFull = (0 < audioIO->buffer[audioIO->bufferFree].samples);
+ pthread_mutex_unlock(audioIO->bufferMutex);
+ return audioIO->bufferFull;
+}
+
+/* Could combine some of the following but makes debugging difficult */
+
+static int ioAddPlayBuffer(void *buffer, int frameCount) {
+ long bytes;
+ if (ioIsFull(&audioOut)) return 0;
+ pthread_mutex_lock(audioOut.bufferMutex);
+ bytes = MIN(audioOut.maxBytes, frameCount * audioOut.bytesPerFrame);
+ memcpy(audioOut.buffer[audioOut.bufferFree].buffer, buffer, bytes);
+ audioOut.buffer[audioOut.bufferFree].samples = frameCount;
+ audioOut.buffer[audioOut.bufferFree].isFree  = false;
+ audioOut.bufferFree = (audioOut.bufferFree + 1) % audioOut.maxBuffers;
+ audioOut.bufferCount -= 1;
+ pthread_mutex_unlock(audioOut.bufferMutex);
+ return bytes;
+}
+
+static int ioGetRecordBuffer(void *buffer, int bufferBytes) {
+ long samples, sampleBytes;
+
+ if (bufferBytes <= 0) return 0;
+/* if (audioIn.buffer[audioIO->bufferNext].samples <=0) return 0;
+*/
+ if (audioIn.buffer[audioIn.bufferNext].isFree) return 0;
+
+ pthread_mutex_lock(audioIn.bufferMutex);
+ samples = audioIn.buffer[audioIn.bufferNext].samples;
+ sampleBytes = MIN(2 * audioIn.pa_spec.channels * samples, bufferBytes);
+ memcpy(buffer, (char *)audioIn.buffer[audioIn.bufferNext].buffer, sampleBytes);
+ /* DMOC 090909 1800: Hmmmm, what if Squeak does not read whole buffer? ATM remaining buffer data lost since */
+ /*   ioGetRecordBuffer() frees the buffer after single visit. Needs more work */
+ audioIn.buffer[audioIn.bufferNext].samples = 0;
+ audioIn.buffer[audioIn.bufferNext].isFree = true;
+ audioIn.bufferNext = (audioIn.bufferNext + 1) % audioIn.maxBuffers;
+ audioIn.bufferCount -= 1;
+ pthread_mutex_unlock(audioIn.bufferMutex);
+ return sampleBytes;
+}
+
+static int ioGetBufferData(audioIO_t *audioIO, void **buffer, int *frames) {
+ if (audioIO->buffer[audioIO->bufferNext].isFree) return false;
+ pthread_mutex_lock(audioIO->bufferMutex);
+ *buffer = (void *)(audioIO->buffer[audioIO->bufferNext].buffer);
+ *frames = audioIO->buffer[audioIO->bufferNext].samples;
+ pthread_mutex_unlock(audioIO->bufferMutex);
+ return true;
+}
+
+static int ioNextPlayBuffer() {
+ pthread_mutex_lock(audioOut.bufferMutex);
+ audioOut.buffer[audioOut.bufferNext].samples = 0;
+ audioOut.buffer[audioOut.bufferNext].isFree  = true;
+ audioOut.bufferNext = (audioOut.bufferNext + 1) % audioOut.maxBuffers;
+ audioOut.stall = (audioOut.bufferNext == audioOut.bufferFree);
+ audioOut.bufferCount += 1;
+ pthread_mutex_unlock(audioOut.bufferMutex);
+}
+
+static int ioNextRecordBuffer() {
+ pthread_mutex_lock(audioIn.bufferMutex);
+ audioIn.buffer[audioIn.bufferNext].isFree  = false;
+ audioIn.bufferFree = (audioIn.bufferNext + 1) % audioIn.maxBuffers;
+ audioIn.stall = (audioIn.bufferNext == audioIn.bufferFree);
+ audioIn.bufferCount += 1;
+ pthread_mutex_unlock(audioIn.bufferMutex);
+}
+
+/* ================================== IO THREADS */
+
+static void *writerThread(void *ptr) {
+ struct timespec tm = {0, 1000 * 1000};
+ int rc;
+ int nextBuffer, frames;
+ void *buffer;
+
+ DBGMSG("[writerThread: started]");
+
+ audioOut.exit = 0;
+
+ for (;;) {
+ DBGMSG("[writerThread: waiting]");
+
+ /* No point signalling squeak *before* running as there may not be a semaphore */
+ ioThreadWaitToRun(&audioOut);
+
+ if (audioOut.exit) break;
+ if (!audioOut.open || audioOut.stall) continue;
+
+ DBGMSG("[writerThread: running]");
+
+ for (;;) {
+ if (!audioOut.open || audioOut.stall || audioOut.exit) break;
+
+ if (!ioGetBufferData(&audioOut, &buffer, &frames)) {
+ signalSqueak(&audioOut);
+ break;
+ }
+
+/*printf("writerThread: buffer: %d, frames %d\n", audioOut.bufferNext, frames);
+*/
+
+ while (frames > 0) {
+ if (!audioOut.open || audioOut.stall || audioOut.exit) break;
+/* if ((rc = snd_pcm_writei(audioOut.alsaHandle, buffer, frames)) < frames) {
+*/
+        
+        /* Experiment to see if draining removes delay, result: undecided, seems slightly better */
+       pa_simple_drain(audioOut.pa_conn, &rc);
+
+        
+        /* PA: Have to assume for now that all frames were written */
+        if (pa_simple_write(audioOut.pa_conn, buffer, (size_t) (frames * audioOut.bytesPerFrame), &rc) < 0) {
+          fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(rc));
+/* printf("writerThread: sent %d, actual %d\n", frames, rc);
+*/
+ break;
+ }
+
+        /* PA: Have to assume for now that all frames were written */
+/* buffer = (short *)((char *)buffer + rc * audioOut.bytesPerFrame);
+ frames -= rc;
+*/
+ /* *** SO FOLLOWING CODE *AND* THE ENCLOSING WHILE-LOOP REDUNDANT!!! (so just break out of loop) *** */
+/* buffer = (short *)((char *)buffer + frames * audioOut.bytesPerFrame);
+ frames -= frames;
+*/
+ break;
+ } /* while */
+
+ if (!audioOut.open || audioOut.stall || audioOut.exit) break;
+ ioNextPlayBuffer();
+ if (!audioOut.open || audioOut.stall || audioOut.exit) break;
+
+ signalSqueak(&audioOut);
+ }
+
+ if (audioOut.exit) break;
+ }
+
+ DBGMSG("[writerThread: stopped]");
+
+}
+
+
+static void *readerThread(void *ptr) {
+ int rc;
+ int wc;
+ unsigned short *p;
+
+ DBGMSG("[readerThread: started]");
+
+ audioIn.exit = 0;
+
+ for (;;) {
+ DBGMSG("[readerThread: waiting]");
+
+ ioThreadWaitToRun(&audioIn);
+
+ if (audioIn.exit) break;
+ if (!audioIn.open || audioIn.stall) continue;
+
+ DBGMSG("[readerThread: running]");
+
+ for (;;) {
+ if (!audioIn.open || audioIn.stall || audioIn.exit) break;
+
+ /* NB: PA Simple API does not return number of bytes/samples recorded */
+ /*   (so have to assume full buffer everytime (poss padded if less than requested) */
+
+/* rc = snd_pcm_readi(audioIn.alsaHandle, audioIn.alsaBuffer, audioIn.alsaFrames);
+*/
+ if (pa_simple_read(audioIn.pa_conn, (char *)(audioIn.buffer[audioIn.bufferFree].buffer), audioIn.maxBytes, &rc) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(rc));
+ continue;
+ }
+
+ if (!audioIn.open || audioIn.stall || audioIn.exit) break;
+
+ /* PA: Assume max buffer frames returned... */
+ rc = audioIn.maxSamples;
+
+ /* EPIPE means overrun */
+/*
+ if (rc == -EPIPE) {
+ printf("readerThread: overrun occurred\n");
+ snd_pcm_prepare(audioIn.alsaHandle);
+ continue;
+ }
+
+ if (rc < 0) {
+ printf("readerThread: error from read: %s\n", snd_strerror(rc));
+ continue;
+ }
+
+ if (rc != (int)audioIn.alsaFrames) {
+ printf("readerThread: short read, read %d frames\n", rc);
+ continue;
+ }
+*/
+ if (!audioIn.buffer[audioIn.bufferFree].isFree) {
+ printf("readerThread: No free buffers!\n");
+ continue;
+ }
+
+ if (!audioIn.open || audioIn.stall || audioIn.exit) break;
+
+ /* PA: Complete buffer should be returned so skipping check and moving guts to end of loop */
+/*
+ if ((audioIn.buffer[audioIn.bufferFree].samples + rc) > audioIn.maxSamples) {
+ ioNextRecordBuffer();
+ if (!audioIn.open || audioIn.stall || audioIn.exit) break;
+ signalSqueak(&audioIn);
+ }
+*/
+
+/* Next won't get called anyway because of "stall" in ioNextRecordBuffer() */
+/* Left here in case needed for debugging */
+/*
+ if (!audioIn.buffer[audioIn.bufferFree].isFree) {
+ printf("readerThread: No free buffers!\n");
+ continue;
+ }
+*/
+
+ /* PA: Endian swap is N810 left-over... */
+
+ /* Endian Swap (rc = frames = Word Count in this case) */
+/*
+ p = (unsigned short *)(audioIn.buffer[audioIn.bufferFree].buffer);
+ wc = rc;
+ while (wc--)
+ *p = _swapw(*p);
+*/
+
+ /* PA: No copy required since record buffer used directly... */
+/*
+ memcpy((char *)(audioIn.buffer[audioIn.bufferFree].buffer) + 2 * audioIn.buffer[audioIn.bufferFree].samples, audioIn.alsaBuffer, rc*2);
+ audioIn.buffer[audioIn.bufferFree].samples += rc;
+*/
+
+ /* PA: No indication of actual bytes/samples read so assuming full buffer read (or padded)... */
+ audioIn.buffer[audioIn.bufferFree].samples = audioIn.maxSamples;
+
+ if (!audioIn.open || audioIn.stall || audioIn.exit) break;
+
+ /* PA: These three lines moved from above (since assuming full buffers are read every time) */
+ ioNextRecordBuffer();
+ if (!audioIn.open || audioIn.stall || audioIn.exit) break;
+ signalSqueak(&audioIn);
+
+ }
+ if (audioIn.exit) break;
+ }
+DBGMSG("[readerThread: stopped]");
+}
+
+
+/* ================================== OPEN/CLOSE PA */
+
+static int closePulseAudio(audioIO_t *audioIO) {
+ int rc;
+
+  if (!audioIO->pa_conn)
+   return true;
+
+  if (PA_STREAM_PLAYBACK == audioIO->dir)
+ if (pa_simple_drain(audioIO->pa_conn, &rc) < 0)
+ fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(rc));
+
+  pa_simple_free(audioIO->pa_conn);
+  audioIO->pa_conn = NULL;
+  
+ printf("closePulseAudio((): %s\n", audioIO->dbgName);
+  
+ return true;
+}
+
+static int openPulseAudio(audioIO_t *audioIO, int samplesPerSec, int stereo) {
+ int rc;
+ int channels;
+
+/*
+DBGMSG(">pa_Open()");
+#ifdef DBG
+printf("\tframeCount: %d, samplesPerSec: %d, stereo: %d\n", frameCount, samplesPerSec, stereo, semaIndex);
+#endif
+*/
+
+ /* DMOC 090912:
+ Connection for playback stream should already have been opened when module loaded so
+ now only open if that failed or parameters have changed. Could also avoid buffer
+ creation if default frameCount known/agreed.
+ */
+
+ channels = stereo ? 2 : 1;
+
+ /* If already connected then check if same spec */
+ if (audioIO->pa_conn)
+ if ((audioIO->pa_spec.rate == samplesPerSec) && (audioIO->pa_spec.channels == channels))
+ return true;
+
+ if (audioIO->pa_conn)
+ closePulseAudio(audioIO);
+
+  audioIO->pa_spec.rate = samplesPerSec;
+  audioIO->pa_spec.channels = channels;
+  
+ if (!(audioIO->pa_conn = pa_simple_new(NULL, "Scratch", audioIO->dir, NULL, audioIO->stream_name, &audioIO->pa_spec, NULL, NULL, &rc))) {
+ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(rc));
+ return false;
+ }
+
+/*
+  if (PA_STREAM_PLAYBACK == audioIO->dir)
+  pa_simple_drain(audioIO->pa_conn, &rc);
+*/
+ printf("openPulseAudio() %s, rate: %i, chans: %i\n", audioIO->dbgName, samplesPerSec, channels);
+  
+ return true;
+}
+
+/* ================================== IO INIT */
+
+/* ioInit() called when module loaded...
+ N810: Threads pre-started and held on semaphore
+  PA: Connection opened for audio-out with default settings
+*/
+
+static int ioInit() {
+ int rc;
+
+ if (initDone) return true;
+ initDone = true;
+
+ /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+ /* >>>>>>>>>>>>> AUDIO OUT >>>>>>>>>>>> */
+ /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+
+/* NOT USED >>> */
+ audioOut.dbgName = "play";
+ audioOut.device = "pa-simple";
+/* <<< */
+
+ audioOut.open = false; /* Squeak state */
+
+ audioOut.maxSamples = 0;
+ audioOut.maxWords = 0;
+ audioOut.maxBytes = 0;
+
+ audioOut.maxBuffers = MAX_OUTPUT_BUFFERS;
+ audioOut.buffer = oBuffer;
+ audioOut.bufferFree = 0;
+ audioOut.bufferNext = 0;
+ audioOut.bufferCount = audioOut.maxBuffers;
+ audioOut.bufferFull = 0;
+ audioOut.bufferMutex = &audioOutBufferMutex;
+ audioOut.buffersAllocated = false;
+
+ audioOut.threadFunc = writerThread;
+ audioOut.thread = 0;
+
+ audioOut.sigRun.mutex = &audioOutRunMutex;
+ audioOut.sigRun.cond = &audioOutRunCond;
+ sigReset(&audioOut.sigRun);
+
+ audioOut.sigStalled.mutex = &audioOutStalledMutex;
+ audioOut.sigStalled.cond  = &audioOutStalledCond;
+ sigReset(&audioOut.sigStalled);
+
+ audioOut.running = 0;
+ audioOut.exit = 0;
+ audioOut.stall = 0;
+
+ audioOut.sqSemaphore = 0;
+
+ audioOut.rateID = 0;
+ audioOut.bytesPerFrame = 4; /* Stereo S16LE */
+
+ /* PA Specific (defaults for Scratch/Squeak) */
+ audioOut.dir = PA_STREAM_PLAYBACK;
+  audioOut.stream_name = "playback";
+  audioOut.pa_spec.format = PA_SAMPLE_S16LE; /* Squeak default */
+  audioOut.pa_spec.rate = 0;
+  audioOut.pa_spec.channels = 0;
+ audioOut.pa_conn = NULL;
+
+ /* Open PA connection here to avoid delays later on */
+ openPulseAudio(&audioOut, 22050, true);
+
+ /* Allocate buffers here to avoid delays later on */
+ /* Hmmm, tricky because varies... so not doing it atm */
+/* ioAllocBuffers(&audioOut, frameCount);
+*/
+
+ ioThreadStart(&audioOut);
+
+
+ /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+ /* >>>>>>>>>>>>> AUDIO IN >>>>>>>>>>>>> */
+ /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+
+/* NOT USED >>> */
+ audioIn.dbgName = "rec";
+ audioIn.device = "pa-simple";
+/* <<< */
+
+ audioIn.open = false; /* Squeak state */
+
+ audioIn.maxSamples = 0;
+ audioIn.maxWords = 0;
+ audioIn.maxBytes = 0;
+
+ audioIn.maxBuffers = MAX_INPUT_BUFFERS;
+ audioIn.buffer = iBuffer;
+ audioIn.bufferFree = 0;
+ audioIn.bufferNext = 0;
+ audioIn.bufferCount = 0; /* No buffers yet. Was audioIn.maxBuffers; */
+ audioIn.bufferFull = 0;
+ audioIn.bufferMutex = &audioInBufferMutex;
+ audioIn.buffersAllocated = false;
+
+ audioIn.threadFunc= readerThread;
+ audioIn.thread = 0;
+
+ audioIn.sigRun.mutex = &audioInRunMutex;
+ audioIn.sigRun.cond = &audioInRunCond;
+ sigReset(&audioIn.sigRun);
+
+ audioIn.sigStalled.mutex = &audioInStalledMutex;
+ audioIn.sigStalled.cond = &audioInStalledCond;
+ sigReset(&audioIn.sigStalled);
+
+ audioIn.running = 0;
+ audioIn.exit = 0;
+ audioIn.stall = 0;
+
+ audioIn.sqSemaphore = 0;
+
+ audioIn.rateID = 0;
+ audioIn.bytesPerFrame = 2; /* Mono S16LE */
+
+ /* PA Specific */
+ audioIn.dir = PA_STREAM_RECORD;
+  audioIn.stream_name = "record";
+  audioIn.pa_spec.format = PA_SAMPLE_S16LE; /* Squeak default */
+  audioIn.pa_spec.rate = 0;
+  audioIn.pa_spec.channels = 0;
+ audioIn.pa_conn = NULL;
+
+ /* DMOC 090912: Not attempting to open default PA connection for recording */
+
+ ioThreadStart(&audioIn);
+}
+
+/* ============================================ */
+/* ================================== VM PLUGIN */
+/* ============================================ */
+
+static int trace() {
+}
+
+/* ================================== AUDIO OUT */
+
+static sqInt sound_AvailableSpace(void) {
+ return ioFreeBytes(&audioOut);
+}
+
+static sqInt sound_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, int samplesOfLeadTime) {
+DBGMSG(">sound_InsertSamplesFromLeadTime()");
+ return 0; /* or maxBytes? */
+}
+
+static sqInt sound_PlaySamplesFromAtLength(int frameCount, int arrayIndex, int startIndex) {
+ unsigned int bufferNext, samples, sampleBytes;
+
+ if (0 >= frameCount) return 0;
+
+ samples = MIN(audioOut.maxSamples, frameCount);
+
+ if (0 == (sampleBytes = ioAddPlayBuffer((void *)(arrayIndex + startIndex * 2 * audioOut.pa_spec.channels), samples)))
+ DBGMSG("sound_PlaySamplesFromAtLength(): No free buffers!");
+
+ sigSignal(&audioOut.sigRun);
+
+ return samples;
+}
+
+static sqInt sound_PlaySilence(void) {
+DBGMSG(">sound_PlaySilence()");
+ ioThreadStall(&audioOut);
+ return 0; /* or maxBytes? */
+}
+
+static sqInt sound_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex) {
+ int rc;
+ int channels;
+ int reopen;
+
+DBGMSG(">sound_Start()");
+
+#ifdef DBG
+printf("\tframeCount: %d, samplesPerSec: %d, stereo: %d, semaIndex: %d\n", frameCount, samplesPerSec, stereo, semaIndex);
+#endif
+
+ if (audioOut.open) return true;
+
+ if (!openPulseAudio(&audioOut, samplesPerSec, stereo)) {
+ success(false);
+ return false;
+ }
+
+ printf("sound_Start() frameCount >> 1: %i\n", frameCount >> 1);
+  
+ ioAllocBuffers(&audioOut, frameCount >> 1);
+
+ /* EVERY TIME: Initialise buffer count, ie, Squeak-ready buffers (max for audio-out) */
+ audioOut.bufferCount = audioOut.maxBuffers;
+
+ audioOut.sqSemaphore = semaIndex;
+ audioOut.open = true;
+
+ sigSignal(&audioOut.sigRun);
+
+DBGMSG("<sound_Start()");
+ return true;
+}
+
+
+static sqInt sound_Stop(void) {
+ int rc;
+
+DBGMSG(">sound_Stop()");
+
+ if (!audioOut.open) return true;
+ audioOut.open = false;
+
+ if (NULL == audioOut.pa_conn) return true;
+
+ ioThreadStall(&audioOut);
+
+ closePulseAudio(&audioOut);
+
+ ioFreeBuffers(&audioOut);
+
+ audioOut.sqSemaphore = 0;
+
+DBGMSG("<sound_Stop()");
+ return true;
+}
+
+
+
+/* ================================== AUDIO IN */
+
+static sqInt sound_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex) {
+ int rc;
+
+DBGMSG(">sound_StartRecording()");
+
+ if (audioIn.open) return true;
+
+  /* Only rate supported on the N810 (atm) is 8000 */
+/*  
+  desiredSamplesPerSec = 8000;
+*/
+  
+ if (!openPulseAudio(&audioIn, desiredSamplesPerSec, stereo)) {
+ success(false);
+ return false;
+ }
+
+ /* Buffers will be filled before signalling Squeak. So rate & buffer size determined signalling freq... */
+  /* 20Hz update freq for Squeak sounds reasonable, so... */
+  audioIn.maxSamples = audioIn.pa_spec.rate / 20;
+
+ ioAllocBuffers(&audioIn, audioIn.maxSamples);
+
+ /* EVERY TIME: Initialise buffer count, ie, Squeak-ready buffers (ZERO for audio-in) */
+ audioIn.bufferCount = 0;
+
+ audioIn.sqSemaphore = semaIndex;
+
+ audioIn.open = true;
+
+ sigSignal(&audioIn.sigRun);
+
+DBGMSG("<sound_StartRecording()");
+ return true;
+}
+
+static sqInt sound_StopRecording(void) {
+DBGMSG(">sound_StopRecording()");
+
+ if (!audioIn.open) return;
+ audioIn.open = false;
+
+ if (NULL == audioIn.pa_conn) return;
+
+ ioThreadStall(&audioIn);
+
+ closePulseAudio(&audioIn);  
+  
+ ioFreeBuffers(&audioIn);
+
+ audioIn.pa_conn = NULL;
+
+ audioIn.sqSemaphore = 0;
+
+DBGMSG("<sound_StopRecording()");
+ return true;
+}
+
+static double sound_GetRecordingSampleRate(void) {
+ return (double)audioIn.pa_spec.rate;
+}
+
+static sqInt sound_RecordSamplesIntoAtLength(int buf, int startSliceIndex, int bufferSizeInBytes) {
+ unsigned int bufferNext, bufferBytes, sampleBytes;
+
+ bufferBytes = MAX(0, bufferSizeInBytes - (startSliceIndex * 2));
+ if (0 == bufferBytes) {
+ printf("***(%d) sound_RecordSamplesIntoAtLength(): No space in Squeak buffer!\n", startSliceIndex);
+ return 0;
+ }
+
+ /* DMOC 090909 1800: Hmmmm, what if Squeak does not read whole buffer? ATM remaining buffer data lost since */
+ /*   ioGetRecordBuffer() frees the buffer after single visit. Needs more work */
+
+ bufferNext = audioIn.bufferNext; /* preserved for debug output */
+ sampleBytes = ioGetRecordBuffer((void *)(buf + (startSliceIndex * 2)), bufferBytes);
+/*
+ if (0 < sampleBytes)
+ printf("   sound_RecordSamplesIntoAtLength(%d, %d, %d) %d, %d\n", buf, startSliceIndex, bufferSizeInBytes, bufferNext, sampleBytes);
+ else
+ printf("***sound_RecordSamplesIntoAtLength(%d, %d, %d) %d, %d\n", buf, startSliceIndex, bufferSizeInBytes, bufferNext, sampleBytes);
+*/
+ return MAX(0, sampleBytes)/(2 * audioIn.pa_spec.channels);
+}
+
+
+/* ================================== sound mixer */
+
+/*
+static int     sound_nomixer  = 0;
+static snd_mixer_t  *mixer_handle = 0;
+static snd_mixer_elem_t *mixer_element  = 0;
+*/
+
+static int mixer_open(char *name) {
+  trace();
+  return -EACCES;
+}
+
+static void mixer_close(void) {
+  trace();
+}
+
+static inline void mixer_getVolume(char *name, int captureFlag, double *leftLevel, double *rightLevel) {
+  trace();
+}
+
+static inline void mixer_setVolume(char *name, int captureFlag, double leftLevel, double rightLevel) {
+  trace();
+}
+
+static int mixer_setSwitch(char *name, int captureFlag, int parameter) {
+  trace();
+  return 0;
+}
+
+static int mixer_getSwitch(char *name, int captureFlag, int channel) {
+  trace();
+  return -1;
+}
+
+static void sound_Volume(double *left, double *right) {
+  trace();
+  *left= 1.0;
+  *right= 1.0;
+}
+
+static void sound_SetVolume(double left, double right) {
+  trace();
+}
+
+static sqInt sound_SetRecordLevel(sqInt level) {
+  trace();
+  return 1;
+  return level;
+}
+
+static sqInt sound_SetDevice(sqInt id, char *arg) {
+  trace();
+  return -1;
+}
+
+static sqInt sound_GetSwitch(sqInt id, sqInt captureFlag, sqInt channel) {
+  trace();
+  return -1;
+}
+
+static sqInt sound_SetSwitch(sqInt id, sqInt captureFlag, sqInt parameter) {
+  trace();
+  return -1;
+}
+
+
+/* module */
+
+#include "SqSound.h"
+
+SqSoundDefine(PA);
+
+#include "SqModule.h"
+
+static void sound_parseEnvironment(void) {
+/*
+  char *ev= 0;
+
+  sound_SetDevice(0, NULL);
+  sound_SetDevice(1, NULL);
+  sound_SetDevice(2, NULL);
+
+  if (     getenv("SQUEAK_NOMIXER"   )) sound_nomixer= 1;
+  if ((ev= getenv("SQUEAK_SOUNDCARD"))) sound_SetDevice(0, ev);
+  if ((ev= getenv("SQUEAK_PLAYBACK" ))) sound_SetDevice(1, ev);
+  if ((ev= getenv("SQUEAK_CAPTURE"  ))) sound_SetDevice(2, ev);
+*/
+}
+
+static int sound_parseArgument(int argc, char **argv) {
+/*
+  if     (!strcmp(argv[0], "-nomixer"  )) { sound_nomixer= 1;   return 1; }
+  else if (argv[1])
+    {
+      if (!strcmp(argv[0], "-soundcard")) { sound_SetDevice(0, argv[1]);  return 2; }
+      if (!strcmp(argv[0], "-playback" )) { sound_SetDevice(1, argv[1]);  return 2; }
+      if (!strcmp(argv[0], "-capture"  )) { sound_SetDevice(2, argv[1]);  return 2; }
+    }
+*/
+  return 0;
+}
+
+static void  sound_printUsage(void) {
+  printf("\nPulseAudio <option>s: <none>\n");
+/*
+  printf("  -nomixer              disable mixer (volume) adjustment\n");
+  printf("  -soundcard <name>     open the named sound card (default: %s)\n", sound_device);
+  printf("  -playback <name>      play to the named sound device (default: %s)\n", sound_playback);
+  printf("  -capture <name>       record from the named sound device (default: %s)\n", sound_capture);
+*/
+}
+
+static void  sound_printUsageNotes(void) {
+}
+
+static void *sound_makeInterface(void) {
+//#ifdef NEWSIG
+//  sigalrm_save(); // DMOC: Being here assumes old handler same for run duration! Same for sigio handler.
+//#else
+// DMOC: Rethink: Signal captured once, preserved and restored when/where necessary?
+//  sigio_save();
+//#endif
+
+#ifdef USE_RESOURCE_MANAGER
+printf("USE_RESOURCE_MANAGER\n");
+#endif
+
+ ioInit();
+
+  return &sound_PA_itf;
+}
+
+SqModuleDefine(sound, PA);
+