]> git.sesse.net Git - pistorm/commitdiff
Adapt piaudio to work on PiStorm + A314 emulation
authorbeeanyew <beeanyew@gmail.com>
Tue, 18 May 2021 11:53:01 +0000 (13:53 +0200)
committerbeeanyew <beeanyew@gmail.com>
Tue, 18 May 2021 11:53:01 +0000 (13:53 +0200)
Maybe, maybe not, I have no idea. Untested, and will remain so, by me.

a314/files_pi/.asoundrc [new file with mode: 0644]
a314/files_pi/piaudio.py [new file with mode: 0644]
a314/software-amiga/piaudio [new file with mode: 0644]
a314/software-amiga/piaudio_pistorm/README.md [new file with mode: 0644]
a314/software-amiga/piaudio_pistorm/build.bat [new file with mode: 0644]
a314/software-amiga/piaudio_pistorm/piaudio.c [new file with mode: 0644]

diff --git a/a314/files_pi/.asoundrc b/a314/files_pi/.asoundrc
new file mode 100644 (file)
index 0000000..283f999
--- /dev/null
@@ -0,0 +1,17 @@
+pcm.amiga {
+    type plug
+    slave {
+        pcm {
+            type file
+            format raw
+            file "/tmp/piaudio_pipe"
+            slave.pcm null
+        }
+        format S8
+        rate 18000
+        channels 2
+    }
+    hint {
+        description "Play audio to Amiga using A314"
+    }
+}
diff --git a/a314/files_pi/piaudio.py b/a314/files_pi/piaudio.py
new file mode 100644 (file)
index 0000000..113a4ce
--- /dev/null
@@ -0,0 +1,252 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Niklas Ekström
+
+import fcntl
+import logging
+import os
+import select
+import socket
+import struct
+import sys
+
+fcntl.F_SETPIPE_SZ = 1031
+
+logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s')
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+
+MSG_REGISTER_REQ        = 1
+MSG_REGISTER_RES        = 2
+MSG_DEREGISTER_REQ      = 3
+MSG_DEREGISTER_RES      = 4
+MSG_READ_MEM_REQ        = 5
+MSG_READ_MEM_RES        = 6
+MSG_WRITE_MEM_REQ       = 7
+MSG_WRITE_MEM_RES       = 8
+MSG_CONNECT             = 9
+MSG_CONNECT_RESPONSE    = 10
+MSG_DATA                = 11
+MSG_EOS                 = 12
+MSG_RESET               = 13
+
+def wait_for_msg():
+    header = b''
+    while len(header) < 9:
+        data = drv.recv(9 - len(header))
+        if not data:
+            logger.error('Connection to a314d was closed, terminating.')
+            exit(-1)
+        header += data
+    (plen, stream_id, ptype) = struct.unpack('=IIB', header)
+    payload = b''
+    while len(payload) < plen:
+        data = drv.recv(plen - len(payload))
+        if not data:
+            logger.error('Connection to a314d was closed, terminating.')
+            exit(-1)
+        payload += data
+    return (stream_id, ptype, payload)
+
+def send_register_req(name):
+    m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name
+    drv.sendall(m)
+
+def send_read_mem_req(address, length):
+    m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length)
+    drv.sendall(m)
+
+def read_mem(address, length):
+    send_read_mem_req(address, length)
+    stream_id, ptype, payload = wait_for_msg()
+    if ptype != MSG_READ_MEM_RES:
+        logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype)
+        exit(-1)
+    return payload
+
+def send_write_mem_req(address, data):
+    m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data
+    drv.sendall(m)
+
+def write_mem(address, data):
+    send_write_mem_req(address, data)
+    stream_id, ptype, payload = wait_for_msg()
+    if ptype != MSG_WRITE_MEM_RES:
+        logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype)
+        exit(-1)
+
+def send_connect_response(stream_id, result):
+    m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result)
+    drv.sendall(m)
+
+def send_data(stream_id, data):
+    m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data
+    drv.sendall(m)
+
+def send_eos(stream_id):
+    m = struct.pack('=IIB', 0, stream_id, MSG_EOS)
+    drv.sendall(m)
+
+def send_reset(stream_id):
+    m = struct.pack('=IIB', 0, stream_id, MSG_RESET)
+    drv.sendall(m)
+
+current_stream_id = None
+first_msg = True
+raw_received = b''
+is_empty = [True, True]
+
+def process_msg_data(payload):
+    global ptrs, first_msg, raw_received
+
+    if first_msg:
+        ptrs = struct.unpack('>II', payload)
+        logger.debug('Received pointers %s', ptrs)
+        first_msg = False
+        return
+
+    buf_index = payload[0]
+
+    if len(raw_received) < 900*2:
+        if not is_empty[buf_index]:
+            data = b'\x00' * (900*2)
+            send_write_mem_req(ptrs[buf_index], data)
+            is_empty[buf_index] = True
+    else:
+        ldata = raw_received[0:900*2:2]
+        rdata = raw_received[1:900*2:2]
+        data = ldata + rdata
+        raw_received = raw_received[900*2:]
+        send_write_mem_req(ptrs[buf_index], data)
+        is_empty[buf_index] = False
+
+def process_drv_msg(stream_id, ptype, payload):
+    global current_stream_id, first_msg
+
+    if ptype == MSG_CONNECT:
+        if payload == b'piaudio' and current_stream_id is None:
+            logger.info('Amiga connected')
+            current_stream_id = stream_id
+            first_msg = True
+            send_connect_response(stream_id, 0)
+        else:
+            send_connect_response(stream_id, 3)
+    elif current_stream_id == stream_id:
+        if ptype == MSG_DATA:
+            process_msg_data(payload)
+        elif ptype == MSG_EOS:
+            pass
+        elif ptype == MSG_RESET:
+            current_stream_id = None
+            logger.info('Amiga disconnected')
+
+done = False
+
+try:
+    idx = sys.argv.index('-ondemand')
+except ValueError:
+    idx = -1
+
+if idx != -1:
+    fd = int(sys.argv[idx + 1])
+    drv = socket.socket(fileno=fd)
+else:
+    drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    drv.connect(('localhost', 7110))
+    drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+    send_register_req(b'piaudio')
+    _, _, payload = wait_for_msg()
+    if payload[0] != 1:
+        logger.error('Unable to register piaudio with driver, shutting down')
+        drv.close()
+        done = True
+
+rbuf = b''
+
+PIPE_NAME = '/tmp/piaudio_pipe'
+
+if not done:
+    exists = True
+    try:
+        if (os.stat(PIPE_NAME).st_mode & 0o170000) != 0o10000:
+            logger.error('A file that is not a named pipe exists at ' + PIPE_NAME)
+            done = True
+    except:
+        exists = False
+
+if not done and not exists:
+    try:
+        os.mkfifo(PIPE_NAME)
+    except:
+        logger.error('Unable to create named pipe at ' + PIPE_NAME)
+        done = True
+
+if not done:
+    try:
+        pipe_fd = os.open(PIPE_NAME, os.O_RDONLY | os.O_NONBLOCK)
+        fcntl.fcntl(pipe_fd, fcntl.F_SETPIPE_SZ, 4096)
+    except:
+        logger.error('Unable to open named pipe at ' + PIPE_NAME)
+        done = True
+
+if not done:
+    logger.info('piaudio service is running')
+
+while not done:
+    sel_fds = [drv]
+
+    if idx == -1:
+        sel_fds.append(sys.stdin)
+
+    if len(raw_received) < 900*2:
+        sel_fds.append(pipe_fd)
+
+    rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0)
+
+    for fd in rfd:
+        if fd == sys.stdin:
+            line = sys.stdin.readline()
+            if not line or line.startswith('quit'):
+                if current_stream_id is not None:
+                    send_reset(current_stream_id)
+                drv.close()
+                done = True
+        elif fd == drv:
+            buf = drv.recv(1024)
+            if not buf:
+                if current_stream_id is not None:
+                    send_reset(current_stream_id)
+                drv.close()
+                done = True
+            else:
+                rbuf += buf
+                while True:
+                    if len(rbuf) < 9:
+                        break
+
+                    (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9])
+                    if len(rbuf) < 9 + plen:
+                        break
+
+                    rbuf = rbuf[9:]
+                    payload = rbuf[:plen]
+                    rbuf = rbuf[plen:]
+
+                    process_drv_msg(stream_id, ptype, payload)
+        elif fd == pipe_fd:
+            data = os.read(pipe_fd, 900*2)
+
+            if len(data) == 0:
+                os.close(pipe_fd)
+
+                l = len(raw_received)
+                c = l // (900*2)
+                if c * 900*2 < l:
+                    raw_received += b'\x00' * ((c + 1) * 900*2 - l)
+
+                pipe_fd = os.open(PIPE_NAME, os.O_RDONLY | os.O_NONBLOCK)
+                fcntl.fcntl(pipe_fd, fcntl.F_SETPIPE_SZ, 4096)
+            else:
+                raw_received += data
diff --git a/a314/software-amiga/piaudio b/a314/software-amiga/piaudio
new file mode 100644 (file)
index 0000000..5aec55e
Binary files /dev/null and b/a314/software-amiga/piaudio differ
diff --git a/a314/software-amiga/piaudio_pistorm/README.md b/a314/software-amiga/piaudio_pistorm/README.md
new file mode 100644 (file)
index 0000000..8e26ed0
--- /dev/null
@@ -0,0 +1,13 @@
+# PiAudio
+
+PiAudio is a service that integrates with ALSA, the sound sub-system on the Raspberry Pi, and lets sound samples be played via Paula on the Amiga.
+
+The *piaudio* program should run on the Amiga. It allocates two sounds channels, one left and one right. Using the following command the piaudio program can run in the background:
+```
+run piaudio >NIL:
+```
+
+The file *.asoundrc* should be stored in `/home/pi` on the RPi. Most programs that play audio on the RPi can then be used. One such program is mpg123, which is started as:
+```
+mpg123 -a amiga song.mp3
+```
diff --git a/a314/software-amiga/piaudio_pistorm/build.bat b/a314/software-amiga/piaudio_pistorm/build.bat
new file mode 100644 (file)
index 0000000..9cde638
--- /dev/null
@@ -0,0 +1 @@
+vc piaudio.c -lamiga -o ../piaudio
diff --git a/a314/software-amiga/piaudio_pistorm/piaudio.c b/a314/software-amiga/piaudio_pistorm/piaudio.c
new file mode 100644 (file)
index 0000000..82c23fd
--- /dev/null
@@ -0,0 +1,319 @@
+#include <exec/types.h>
+#include <exec/memory.h>
+
+#include <libraries/dos.h>
+
+#include <devices/audio.h>
+
+#include <proto/exec.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "../../a314device/a314.h"
+#include "../../a314device/proto_a314.h"
+
+#include <clib/alib_protos.h>
+
+#define SERVICE_NAME "piaudio"
+
+#define FREQ 18000
+#define BUFFER_LEN_MS 50
+#define SAMPLES (FREQ * BUFFER_LEN_MS / 1000)
+
+#define R0             1
+#define L0             2
+#define L1             4
+#define R1             8
+
+#define LEFT_CHAN_MASK (L0 | L1)
+#define RIGHT_CHAN_MASK        (R0 | R1)
+
+#define LEFT 0
+#define RIGHT 1
+
+struct MsgPort *sync_mp = NULL;
+struct MsgPort *async_mp = NULL;
+
+struct A314_IORequest *sync_a314_req = NULL;
+struct A314_IORequest *write_a314_req = NULL;
+
+struct Library *A314Base;
+
+char *audio_buffers[4] = { NULL, NULL, NULL, NULL };
+
+struct IOAudio *sync_audio_req = NULL;
+struct IOAudio *async_audio_req[4] = { NULL, NULL, NULL, NULL };
+
+ULONG allocated_channels;
+
+BOOL a314_device_open = FALSE;
+BOOL audio_device_open = FALSE;
+BOOL stream_open = FALSE;
+BOOL pending_a314_write = FALSE;
+
+ULONG socket;
+int back_index = 0;
+char awbuf[8];
+
+void start_a314_cmd(struct MsgPort *reply_port, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
+{
+       ior->a314_Request.io_Message.mn_ReplyPort = reply_port;
+       ior->a314_Request.io_Command = cmd;
+       ior->a314_Request.io_Error = 0;
+       ior->a314_Socket = socket;
+       ior->a314_Buffer = buffer;
+       ior->a314_Length = length;
+       SendIO((struct IORequest *)ior);
+}
+
+BYTE a314_connect(char *name)
+{
+       socket = time(NULL);
+       start_a314_cmd(sync_mp, sync_a314_req, A314_CONNECT, name, strlen(name));
+       Wait(1L << sync_mp->mp_SigBit);
+       GetMsg(sync_mp);
+       return sync_a314_req->a314_Request.io_Error;
+}
+
+BYTE a314_write(char *buffer, int length)
+{
+       start_a314_cmd(sync_mp, sync_a314_req, A314_WRITE, buffer, length);
+       Wait(1L << sync_mp->mp_SigBit);
+       GetMsg(sync_mp);
+       return sync_a314_req->a314_Request.io_Error;
+}
+
+BYTE a314_eos()
+{
+       start_a314_cmd(sync_mp, sync_a314_req, A314_EOS, NULL, 0);
+       Wait(1L << sync_mp->mp_SigBit);
+       GetMsg(sync_mp);
+       return sync_a314_req->a314_Request.io_Error;
+}
+
+BYTE a314_reset()
+{
+       start_a314_cmd(sync_mp, sync_a314_req, A314_RESET, NULL, 0);
+       Wait(1L << sync_mp->mp_SigBit);
+       GetMsg(sync_mp);
+       return sync_a314_req->a314_Request.io_Error;
+}
+
+void start_a314_write(char *buffer, int length)
+{
+       start_a314_cmd(async_mp, write_a314_req, A314_WRITE, buffer, length);
+       pending_a314_write = TRUE;
+}
+
+void submit_async_audio_req(int index)
+{
+       ULONG mask = ((index & 1) == LEFT) ? LEFT_CHAN_MASK : RIGHT_CHAN_MASK;
+       ULONG unit = allocated_channels & mask;
+
+       async_audio_req[index]->ioa_Request.io_Message.mn_ReplyPort = async_mp;
+       async_audio_req[index]->ioa_Request.io_Command = CMD_WRITE;
+       async_audio_req[index]->ioa_Request.io_Flags = ADIOF_PERVOL;
+       async_audio_req[index]->ioa_Request.io_Unit = (void*)unit;
+       async_audio_req[index]->ioa_Data = audio_buffers[index];
+       async_audio_req[index]->ioa_Length = SAMPLES;
+       async_audio_req[index]->ioa_Period = 197;
+       async_audio_req[index]->ioa_Volume = 64;
+       async_audio_req[index]->ioa_Cycles = 1;
+       BeginIO((struct IORequest *)async_audio_req[index]);
+}
+
+int main()
+{
+       SetTaskPri(FindTask(NULL), 50);
+
+       sync_mp = CreatePort(NULL, 0);
+       if (!sync_mp)
+       {
+               printf("Unable to create sync reply message port\n");
+               goto cleanup;
+       }
+
+       async_mp = CreatePort(NULL, 0);
+       if (!async_mp)
+       {
+               printf("Unable to create async reply message port\n");
+               goto cleanup;
+       }
+
+       sync_a314_req = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));
+       write_a314_req = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));
+       if (!sync_a314_req || !write_a314_req)
+       {
+               printf("Unable to create A314_IORequest\n");
+               goto cleanup;
+       }
+
+       if (OpenDevice(A314_NAME, 0, (struct IORequest *)sync_a314_req, 0))
+       {
+               printf("Unable to open a314.device\n");
+               goto cleanup;
+       }
+
+       a314_device_open = TRUE;
+
+       A314Base = &(sync_a314_req->a314_Request.io_Device->dd_Library);
+
+       memcpy(write_a314_req, sync_a314_req, sizeof(struct A314_IORequest));
+
+       audio_buffers[0] = AllocMem(SAMPLES * 2, MEMF_FAST | MEMF_CHIP | MEMF_CLEAR);
+       audio_buffers[2] = AllocMem(SAMPLES * 2, MEMF_FAST | MEMF_CHIP | MEMF_CLEAR);
+       if (!audio_buffers[0] || !audio_buffers[2])
+       {
+               printf("Unable to allocate audio buffers in A314 chip memory\n");
+               goto cleanup;
+       }
+
+       audio_buffers[1] = audio_buffers[0] + SAMPLES;
+       audio_buffers[3] = audio_buffers[2] + SAMPLES;
+
+       sync_audio_req = (struct IOAudio *)CreateExtIO(sync_mp, sizeof(struct IOAudio));
+       if (!sync_audio_req)
+       {
+               printf("Unable to allocate sync audio request\n");
+               goto cleanup;
+       }
+
+       int i;
+       for (i = 0; i < 4; i++)
+       {
+               async_audio_req[i] = AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC);
+               if (!async_audio_req[i])
+               {
+                       printf("Unable to allocate async audio request\n");
+                       goto cleanup;
+               }
+       }
+
+       UBYTE which_channels[] = { L0 | R0, L0 | R1, L1 | R0, L1 | R1 };
+
+       sync_audio_req->ioa_Request.io_Message.mn_ReplyPort = sync_mp;
+       sync_audio_req->ioa_Request.io_Message.mn_Node.ln_Pri = 127;
+       sync_audio_req->ioa_Request.io_Command = ADCMD_ALLOCATE;
+       sync_audio_req->ioa_Request.io_Flags = ADIOF_NOWAIT;
+       sync_audio_req->ioa_AllocKey = 0;
+       sync_audio_req->ioa_Data = which_channels;
+       sync_audio_req->ioa_Length = sizeof(which_channels);
+
+       if (OpenDevice(AUDIONAME, 0, (struct IORequest *)sync_audio_req, 0))
+       {
+               printf("Unable to open audio.device\n");
+               goto cleanup;
+       }
+
+       audio_device_open = TRUE;
+
+       allocated_channels = (ULONG)sync_audio_req->ioa_Request.io_Unit;
+
+       for (i = 0; i < 4; i++)
+               memcpy(async_audio_req[i], sync_audio_req, sizeof(struct IOAudio));
+
+       if (a314_connect(SERVICE_NAME) != A314_CONNECT_OK)
+       {
+               printf("Unable to connect to piaudio service\n");
+               goto cleanup;
+       }
+
+       stream_open = TRUE;
+
+       ULONG *buf_ptrs = (ULONG *)awbuf;
+       buf_ptrs[0] = TranslateAddressA314(audio_buffers[0]);
+       buf_ptrs[1] = TranslateAddressA314(audio_buffers[2]);
+       if (a314_write(awbuf, 8) != A314_WRITE_OK)
+       {
+               printf("Unable to write buffer pointers\n");
+               goto cleanup;
+       }
+
+       printf("PiAudio started, allocated channels: L%d, R%d\n",
+               (allocated_channels & LEFT_CHAN_MASK) == L0 ? 0 : 1,
+               (allocated_channels & RIGHT_CHAN_MASK) == R0 ? 0 : 1);
+
+       sync_audio_req->ioa_Request.io_Command = CMD_STOP;
+       DoIO((struct IORequest *)sync_audio_req);
+
+       submit_async_audio_req(back_index + LEFT);
+       submit_async_audio_req(back_index + RIGHT);
+
+       sync_audio_req->ioa_Request.io_Command = CMD_START;
+       DoIO((struct IORequest *)sync_audio_req);
+
+       int pending_audio_reqs = 2;
+
+       ULONG portsig = 1L << async_mp->mp_SigBit;
+
+       printf("Press ctrl-c to exit...\n");
+
+       while (TRUE)
+       {
+               if (pending_audio_reqs <= 2)
+               {
+                       back_index ^= 2;
+
+                       submit_async_audio_req(back_index + LEFT);
+                       submit_async_audio_req(back_index + RIGHT);
+
+                       pending_audio_reqs += 2;
+
+                       if (!pending_a314_write)
+                       {
+                               awbuf[0] = back_index == 0 ? 0 : 1;
+                               start_a314_write(awbuf, 1);
+                       }
+               }
+
+               ULONG signal = Wait(SIGBREAKF_CTRL_C | portsig);
+
+               if (signal & SIGBREAKF_CTRL_C)
+                       break;
+               else if (signal & portsig)
+               {
+                       struct Message *msg;
+                       while (msg = GetMsg(async_mp))
+                       {
+                               if (msg == (struct Message *)write_a314_req)
+                               {
+                                       if (write_a314_req->a314_Request.io_Error == A314_WRITE_OK)
+                                               pending_a314_write = FALSE;
+                                       else
+                                               goto cleanup;
+                               }
+                               else
+                                       pending_audio_reqs--;
+                       }
+               }
+       }
+
+cleanup:
+       if (stream_open)
+               a314_reset();
+       if (audio_device_open)
+               CloseDevice((struct IORequest *)sync_audio_req);
+       for (i = 3; i >= 0; i--)
+               if (async_audio_req[i])
+                       FreeMem(async_audio_req[i], sizeof(struct IOAudio));
+       if (sync_audio_req)
+               DeleteExtIO((struct IORequest *)sync_audio_req);
+       if (audio_buffers[2])
+               FreeMem(audio_buffers[2], SAMPLES * 2);
+       if (audio_buffers[0])
+               FreeMem(audio_buffers[0], SAMPLES * 2);
+       if (a314_device_open)
+               CloseDevice((struct IORequest *)sync_a314_req);
+       if (write_a314_req)
+               DeleteExtIO((struct IORequest *)write_a314_req);
+       if (sync_a314_req)
+               DeleteExtIO((struct IORequest *)sync_a314_req);
+       if (async_mp)
+               DeletePort(async_mp);
+       if (sync_mp)
+               DeletePort(sync_mp);
+
+       return 0;
+}