2020-03-15 16:24:40 +00:00
|
|
|
/*
|
|
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
|
|
|
Copyright (C) 2018-20 divingkatae and maximum
|
|
|
|
(theweirdo) spatium
|
|
|
|
|
|
|
|
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** AWAC sound device emulation.
|
|
|
|
|
|
|
|
Author: Max Poliakovski 2019-20
|
|
|
|
*/
|
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
#include <algorithm>
|
2020-03-23 03:15:12 +00:00
|
|
|
#include <thirdparty/loguru/loguru.hpp>
|
2020-03-19 14:09:24 +00:00
|
|
|
#include "endianswap.h"
|
2020-03-15 16:24:40 +00:00
|
|
|
#include "awacs.h"
|
2020-03-26 01:07:12 +00:00
|
|
|
#include "dbdma.h"
|
2020-03-15 16:24:40 +00:00
|
|
|
#include "machines/machinebase.h"
|
2020-05-08 20:32:29 +00:00
|
|
|
#include "soundserver.h"
|
2020-05-15 00:36:40 +00:00
|
|
|
//#include <thirdparty/libsoundio/soundio/soundio.h>
|
2020-03-19 14:09:24 +00:00
|
|
|
|
|
|
|
static int awac_freqs[8] = {44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350};
|
2020-03-15 16:24:40 +00:00
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
|
2020-03-15 16:24:40 +00:00
|
|
|
AWACDevice::AWACDevice()
|
|
|
|
{
|
|
|
|
this->audio_proc = new AudioProcessor();
|
|
|
|
|
|
|
|
/* register audio processor chip with the I2C bus */
|
|
|
|
I2CBus *i2c_bus = dynamic_cast<I2CBus *>(gMachineObj->get_comp_by_type(HWCompType::I2C_HOST));
|
|
|
|
i2c_bus->register_device(0x45, this->audio_proc);
|
2020-05-08 20:32:29 +00:00
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
this->snd_server = dynamic_cast<SoundServer *>
|
2020-05-08 20:32:29 +00:00
|
|
|
(gMachineObj->get_comp_by_name("SoundServer"));
|
|
|
|
this->out_stream_ready = false;
|
2020-03-15 16:24:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AWACDevice::~AWACDevice()
|
|
|
|
{
|
2020-05-15 00:36:40 +00:00
|
|
|
if (this->out_stream_ready) {
|
|
|
|
snd_server->close_out_stream();
|
|
|
|
}
|
|
|
|
|
2020-03-15 16:24:40 +00:00
|
|
|
delete this->audio_proc;
|
|
|
|
}
|
|
|
|
|
2020-03-26 01:07:12 +00:00
|
|
|
void AWACDevice::set_dma_out(DMAChannel *dma_out_ch)
|
|
|
|
{
|
|
|
|
this->dma_out_ch = dma_out_ch;
|
|
|
|
}
|
|
|
|
|
2020-03-15 16:24:40 +00:00
|
|
|
uint32_t AWACDevice::snd_ctrl_read(uint32_t offset, int size)
|
|
|
|
{
|
|
|
|
switch(offset) {
|
|
|
|
case AWAC_SOUND_CTRL_REG:
|
|
|
|
return this->snd_ctrl_reg;
|
|
|
|
case AWAC_CODEC_CTRL_REG:
|
|
|
|
return this->is_busy;
|
|
|
|
case AWAC_CODEC_STATUS_REG:
|
|
|
|
return (AWAC_AVAILABLE << 8) | (AWAC_MAKER_CRYSTAL << 16) |
|
|
|
|
(AWAC_REV_SCREAMER << 20);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
LOG_F(ERROR, "AWAC: unsupported register at offset 0x%X", offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AWACDevice::snd_ctrl_write(uint32_t offset, uint32_t value, int size)
|
|
|
|
{
|
|
|
|
int subframe, reg_num;
|
|
|
|
uint16_t data;
|
|
|
|
|
|
|
|
switch(offset) {
|
|
|
|
case AWAC_SOUND_CTRL_REG:
|
2020-03-19 14:09:24 +00:00
|
|
|
this->snd_ctrl_reg = BYTESWAP_32(value);
|
2020-03-19 01:00:18 +00:00
|
|
|
LOG_F(INFO, "New sound control value = 0x%X", this->snd_ctrl_reg);
|
2020-03-15 16:24:40 +00:00
|
|
|
break;
|
|
|
|
case AWAC_CODEC_CTRL_REG:
|
|
|
|
subframe = (value >> 14) & 3;
|
|
|
|
reg_num = (value >> 20) & 7;
|
|
|
|
data = ((value >> 8) & 0xF00) | ((value >> 24) & 0xFF);
|
|
|
|
LOG_F(INFO, "AWAC subframe = %d, reg = %d, data = %08X\n", subframe, reg_num, data);
|
|
|
|
if (!subframe)
|
|
|
|
this->control_regs[reg_num] = data;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
LOG_F(ERROR, "AWAC: unsupported register at offset 0x%X", offset);
|
|
|
|
}
|
|
|
|
}
|
2020-03-19 14:09:24 +00:00
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
#if 0
|
2020-05-08 20:32:29 +00:00
|
|
|
static void convert_data(const uint8_t *in, SoundIoChannelArea *out_buf, uint32_t frame_count)
|
2020-03-26 01:07:12 +00:00
|
|
|
{
|
2020-05-08 20:32:29 +00:00
|
|
|
uint16_t *p_in = (uint16_t *)in;
|
|
|
|
|
|
|
|
for (int i = 0; i < frame_count; i += 2, p_in += 4) {
|
|
|
|
*(uint16_t *)(out_buf[0].ptr) = BYTESWAP_16(p_in[0]);
|
|
|
|
out_buf[0].ptr += out_buf[0].step;
|
|
|
|
*(uint16_t *)(out_buf[0].ptr) = BYTESWAP_16(p_in[1]);
|
|
|
|
out_buf[0].ptr += out_buf[0].step;
|
|
|
|
|
|
|
|
*(uint16_t *)(out_buf[1].ptr) = BYTESWAP_16(p_in[2]);
|
|
|
|
out_buf[1].ptr += out_buf[1].step;
|
|
|
|
*(uint16_t *)(out_buf[1].ptr) = BYTESWAP_16(p_in[3]);
|
|
|
|
out_buf[1].ptr += out_buf[1].step;
|
2020-03-26 01:07:12 +00:00
|
|
|
}
|
2020-05-08 20:32:29 +00:00
|
|
|
}
|
2020-03-26 01:07:12 +00:00
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
static void insert_silence(SoundIoChannelArea *out_buf, uint32_t frame_count)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < frame_count; i += 2) {
|
|
|
|
*(uint16_t *)(out_buf[0].ptr) = 0;
|
|
|
|
out_buf[0].ptr += out_buf[0].step;
|
|
|
|
*(uint16_t *)(out_buf[0].ptr) = 0;
|
|
|
|
out_buf[0].ptr += out_buf[0].step;
|
|
|
|
|
|
|
|
*(uint16_t *)(out_buf[1].ptr) = 0;
|
|
|
|
out_buf[1].ptr += out_buf[1].step;
|
|
|
|
*(uint16_t *)(out_buf[1].ptr) = 0;
|
|
|
|
out_buf[1].ptr += out_buf[1].step;
|
2020-03-26 01:07:12 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-15 00:36:40 +00:00
|
|
|
#endif
|
2020-03-26 01:07:12 +00:00
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
#if 0
|
2020-05-08 20:32:29 +00:00
|
|
|
static void sound_out_callback(struct SoundIoOutStream *outstream,
|
|
|
|
int frame_count_min, int frame_count_max)
|
2020-03-26 01:07:12 +00:00
|
|
|
{
|
2020-05-08 20:32:29 +00:00
|
|
|
int err, frame_count;
|
2020-03-26 01:07:12 +00:00
|
|
|
uint8_t *p_in;
|
2020-05-08 20:32:29 +00:00
|
|
|
uint32_t buf_len, rem_len, got_len;
|
|
|
|
struct SoundIoChannelArea *areas;
|
|
|
|
DMAChannel *dma_ch = (DMAChannel *)outstream->userdata; /* C API baby! */
|
|
|
|
int n_channels = outstream->layout.channel_count;
|
2020-05-15 00:36:40 +00:00
|
|
|
bool stop = false;
|
2020-05-08 20:32:29 +00:00
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
//if (!dma_ch->is_active()) {
|
|
|
|
// soundio_outstream_clear_buffer(outstream);
|
|
|
|
// soundio_outstream_pause(outstream, true);
|
|
|
|
// return;
|
|
|
|
//}
|
2020-05-09 12:29:37 +00:00
|
|
|
|
|
|
|
if (frame_count_max > 512) {
|
|
|
|
frame_count = 512;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
frame_count = frame_count_max;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf_len = (frame_count * n_channels) << 1;
|
|
|
|
//frame_count = frame_count_max;
|
2020-05-08 20:32:29 +00:00
|
|
|
|
|
|
|
//LOG_F(INFO, "frame_count_min=%d", frame_count_min);
|
|
|
|
//LOG_F(INFO, "frame_count_max=%d", frame_count_max);
|
|
|
|
//LOG_F(INFO, "channel count: %d", n_channels);
|
|
|
|
//LOG_F(INFO, "buf_len: %d", buf_len);
|
|
|
|
|
|
|
|
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) {
|
|
|
|
LOG_F(ERROR, "unrecoverable stream error: %s\n", soundio_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
2020-03-26 01:07:12 +00:00
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
for (rem_len = buf_len; rem_len > 0; rem_len -= got_len) {
|
2020-03-26 01:07:12 +00:00
|
|
|
if (!dma_ch->get_data(rem_len, &got_len, &p_in)) {
|
2020-05-08 20:32:29 +00:00
|
|
|
//LOG_F(INFO, "SndCallback: got_len = %d", got_len);
|
|
|
|
convert_data(p_in, areas, got_len >> 2);
|
2020-03-26 02:02:33 +00:00
|
|
|
//LOG_F(9, "Converted sound data, len = %d", got_len);
|
2020-03-26 01:07:12 +00:00
|
|
|
} else { /* no more data */
|
2020-05-08 20:32:29 +00:00
|
|
|
//memset(buf, 0, rem_len); /* fill the buffer with silence */
|
2020-03-26 02:02:33 +00:00
|
|
|
//LOG_F(9, "Inserted silence, len = %d", rem_len);
|
2020-05-08 20:32:29 +00:00
|
|
|
|
|
|
|
/* fill the buffer with silence */
|
|
|
|
//LOG_F(ERROR, "rem_len=%d", rem_len);
|
|
|
|
insert_silence(areas, rem_len >> 2);
|
2020-05-15 00:36:40 +00:00
|
|
|
stop = true;
|
2020-03-26 01:07:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
if ((err = soundio_outstream_end_write(outstream))) {
|
|
|
|
LOG_F(ERROR, "unrecoverable stream error: %s\n", soundio_strerror(err));
|
|
|
|
return;
|
2020-03-19 14:09:24 +00:00
|
|
|
}
|
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
if (stop) {
|
|
|
|
LOG_F(INFO, "pausing result: %s",
|
|
|
|
soundio_strerror(soundio_outstream_pause(outstream, true)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
long AWACDevice::sound_out_callback(cubeb_stream *stream, void *user_data,
|
|
|
|
void const *input_buffer, void *output_buffer,
|
|
|
|
long req_frames)
|
2020-05-15 00:36:40 +00:00
|
|
|
{
|
2020-05-18 18:45:37 +00:00
|
|
|
uint8_t *p_in;
|
2020-05-18 00:51:56 +00:00
|
|
|
int16_t* in_buf, * out_buf;
|
2020-05-18 18:45:37 +00:00
|
|
|
uint32_t got_len;
|
2020-05-18 00:51:56 +00:00
|
|
|
long frames, out_frames;
|
|
|
|
AWACDevice *this_ptr = static_cast<AWACDevice*>(user_data); /* C API baby! */
|
2020-05-15 00:36:40 +00:00
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
if (!this_ptr->dma_out_ch->is_active()) {
|
|
|
|
return 0;
|
2020-05-15 00:36:40 +00:00
|
|
|
}
|
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
out_buf = (int16_t*)output_buffer;
|
2020-05-15 00:36:40 +00:00
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
out_frames = 0;
|
2020-05-15 00:36:40 +00:00
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
while (req_frames > 0) {
|
2020-05-18 18:45:37 +00:00
|
|
|
if (!this_ptr->dma_out_ch->get_data(req_frames << 2, &got_len, &p_in)) {
|
|
|
|
frames = got_len >> 2;
|
2020-05-18 00:51:56 +00:00
|
|
|
|
|
|
|
in_buf = (int16_t*)p_in;
|
|
|
|
|
2020-05-18 18:45:37 +00:00
|
|
|
for (int i = frames; i > 0; i--) {
|
2020-05-18 00:51:56 +00:00
|
|
|
out_buf[0] = BYTESWAP_16(in_buf[0]);
|
|
|
|
out_buf[1] = BYTESWAP_16(in_buf[1]);
|
2020-05-18 18:45:37 +00:00
|
|
|
in_buf += 2;
|
|
|
|
out_buf += 2;
|
2020-05-18 00:51:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
req_frames -= frames;
|
|
|
|
out_frames += frames;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
2020-05-15 00:36:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 00:51:56 +00:00
|
|
|
return out_frames;
|
2020-05-15 00:36:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void status_callback(cubeb_stream *stream, void *user_data, cubeb_state state)
|
|
|
|
{
|
2020-05-18 00:51:56 +00:00
|
|
|
LOG_F(9, "Cubeb status callback fired, status = %d", state);
|
2020-03-19 14:09:24 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
void AWACDevice::open_stream(int sample_rate)
|
2020-03-19 14:09:24 +00:00
|
|
|
{
|
2020-05-08 20:32:29 +00:00
|
|
|
int err;
|
2020-03-19 14:09:24 +00:00
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
if ((err = this->snd_server->open_out_stream(sample_rate, sound_out_callback,
|
2020-05-18 00:51:56 +00:00
|
|
|
status_callback, (void *)this))) {
|
2020-05-15 00:36:40 +00:00
|
|
|
LOG_F(ERROR, "AWAC: unable to open sound output stream: %d", err);
|
|
|
|
this->out_stream_ready = false;
|
|
|
|
} else {
|
|
|
|
this->out_sample_rate = sample_rate;
|
|
|
|
this->out_stream_ready = true;
|
|
|
|
}
|
2020-03-19 14:09:24 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
void AWACDevice::dma_start()
|
2020-03-19 14:09:24 +00:00
|
|
|
{
|
2020-05-08 20:32:29 +00:00
|
|
|
int err;
|
2020-03-19 14:09:24 +00:00
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
if (!this->out_stream_ready) {
|
|
|
|
this->open_stream(awac_freqs[(this->snd_ctrl_reg >> 8) & 7]);
|
|
|
|
} else if (this->out_sample_rate != awac_freqs[(this->snd_ctrl_reg >> 8) & 7]) {
|
2020-05-15 00:36:40 +00:00
|
|
|
snd_server->close_out_stream();
|
2020-05-08 20:32:29 +00:00
|
|
|
this->open_stream(awac_freqs[(this->snd_ctrl_reg >> 8) & 7]);
|
|
|
|
} else {
|
2020-05-15 00:36:40 +00:00
|
|
|
LOG_F(ERROR, "AWAC: unpausing attempted!");
|
2020-05-08 20:32:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-03-19 14:09:24 +00:00
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
if (!this->out_stream_ready) {
|
|
|
|
return;
|
2020-03-19 14:09:24 +00:00
|
|
|
}
|
|
|
|
|
2020-05-15 00:36:40 +00:00
|
|
|
if ((err = snd_server->start_out_stream())) {
|
|
|
|
LOG_F(ERROR, "Could not start sound output stream");
|
2020-03-19 14:09:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-08 20:32:29 +00:00
|
|
|
void AWACDevice::dma_end()
|
2020-03-19 14:09:24 +00:00
|
|
|
{
|
|
|
|
}
|