From: beeanyew Date: Tue, 18 May 2021 11:53:01 +0000 (+0200) Subject: Adapt piaudio to work on PiStorm + A314 emulation X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=6d25aad16bb6014c2ca74bd0d96a0efe7b0b11b6;p=pistorm Adapt piaudio to work on PiStorm + A314 emulation Maybe, maybe not, I have no idea. Untested, and will remain so, by me. --- diff --git a/a314/files_pi/.asoundrc b/a314/files_pi/.asoundrc new file mode 100644 index 0000000..283f999 --- /dev/null +++ b/a314/files_pi/.asoundrc @@ -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 index 0000000..113a4ce --- /dev/null +++ b/a314/files_pi/piaudio.py @@ -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 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 index 0000000..8e26ed0 --- /dev/null +++ b/a314/software-amiga/piaudio_pistorm/README.md @@ -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 index 0000000..9cde638 --- /dev/null +++ b/a314/software-amiga/piaudio_pistorm/build.bat @@ -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 index 0000000..82c23fd --- /dev/null +++ b/a314/software-amiga/piaudio_pistorm/piaudio.c @@ -0,0 +1,319 @@ +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +#include "../../a314device/a314.h" +#include "../../a314device/proto_a314.h" + +#include + +#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; +}