Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental audio playback support from BIN/CUE files. #207

Merged
merged 16 commits into from May 14, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/SCSI2SD/src/firmware/mode.c
Expand Up @@ -220,6 +220,23 @@ static const uint8_t ControlModePage[] =
0x00, 0x00 // AEN holdoff period.
};

#ifdef ENABLE_AUDIO_OUTPUT
PetteriAimonen marked this conversation as resolved.
Show resolved Hide resolved
static const uint8_t CDROMAudioControlParametersPage[] =
{
0x0E, // page code
0x0E, // page length
0x04, // 'Immed' bit set, 'SOTC' bit not set
0x00, // reserved
0x00, // reserved
0x80, // 1 LBAs/sec multip
0x00, 0x4B, // 75 LBAs/sec
0x03, 0xFF, // output port 0 active, max volume
0x03, 0xFF, // output port 1 active, max volume
0x00, 0x00, // output port 2 inactive
0x00, 0x00 // output port 3 inactive
};
#endif

static const uint8_t SequentialDeviceConfigPage[] =
{
0x10, // page code
Expand Down Expand Up @@ -496,6 +513,21 @@ static void doModeSense(
idx += sizeof(ControlModePage);
}

#ifdef ENABLE_AUDIO_OUTPUT
if ((scsiDev.compatMode >= COMPAT_SCSI2)
&& (scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL)
&& (pageCode == 0x0E || pageCode == 0x3F))
{
pageFound = 1;
pageIn(
pc,
idx,
CDROMAudioControlParametersPage,
sizeof(CDROMAudioControlParametersPage));
idx += sizeof(CDROMAudioControlParametersPage);
}
#endif

if ((scsiDev.target->cfg->deviceType == S2S_CFG_SEQUENTIAL) &&
(pageCode == 0x10 || pageCode == 0x3F))
{
Expand Down
84 changes: 72 additions & 12 deletions lib/ZuluSCSI_platform_RP2040/audio.cpp
Expand Up @@ -134,11 +134,18 @@ static uint16_t wire_buf_a[WIRE_BUFFER_SIZE];
static uint16_t wire_buf_b[WIRE_BUFFER_SIZE];

// tracking for audio playback
static bool audio_active = false;
static volatile bool audio_stopping = false;
static uint8_t audio_owner; // SCSI ID or 0xFF when idle
static volatile bool audio_paused = false;
static FsFile audio_file;
static uint32_t fleft;

// historical playback status information
static audio_status_code audio_last_status[8] = {ASC_NO_STATUS};
static uint32_t audio_bytes_read[8] = {0};

// mechanism for cleanly stopping DMA units
static volatile bool audio_stopping = false;

// trackers for the below function call
static uint16_t sfcnt = 0; // sub-frame count; 2 per frame, 192 frames/block
static uint8_t invert = 0; // biphase encode help: set if last wire bit was '1'
Expand Down Expand Up @@ -318,7 +325,15 @@ void audio_dma_irq() {
}

bool audio_is_active() {
return audio_active;
return audio_owner != 0xFF;
}

bool audio_is_paused() {
return audio_paused;
}

uint8_t audio_get_owner() {
return audio_owner;
}

void audio_setup() {
Expand All @@ -338,7 +353,8 @@ void audio_setup() {
}

void audio_poll() {
if (!audio_active) return;
if (!audio_is_active()) return;
if (audio_paused) return;
if (fleft == 0 && sbufst_a == STALE && sbufst_b == STALE) {
// out of data and ready to stop
audio_stop();
Expand Down Expand Up @@ -368,6 +384,7 @@ void audio_poll() {
logmsg("Audio sample data underrun");
}
fleft -= toRead;
audio_bytes_read[audio_owner] += toRead;

if (sbufst_a == FILLING) {
sbufst_a = READY;
Expand All @@ -376,13 +393,17 @@ void audio_poll() {
}
}

bool audio_play(const char* file, uint64_t start, uint64_t end, bool swap) {
bool audio_play(uint8_t owner, const char* file, uint64_t start, uint64_t end, bool swap) {
// stop any existing playback first
if (audio_active) audio_stop();
if (audio_is_active()) audio_stop();

// dbgmsg("Request to play ('", file, "':", start, ":", end, ")");

// verify audio file is present and inputs are (somewhat) sane
if (owner == 0xFF) {
logmsg("Illegal audio owner");
return false;
}
if (start >= end) {
logmsg("Invalid range for audio (", start, ":", end, ")");
return false;
Expand All @@ -394,12 +415,18 @@ bool audio_play(const char* file, uint64_t start, uint64_t end, bool swap) {
return false;
}
uint64_t len = audio_file.size();
if (start > len || end > len) {
logmsg("File '", file, "' playback request (",
start, ":", end, ":", len, ") outside bounds");
if (start > len) {
logmsg("File '", file, "' playback request start (",
start, ":", len, ") outside file bounds");
audio_file.close();
return false;
}
// truncate playback end to end of file
// we will not consider this to be an error at the moment
if (end > len) {
dbgmsg("------ Truncate audio play request end ", end, " to file size ", len);
end = len;
}
fleft = end - start;
if (fleft <= 2 * AUDIO_BUFFER_SIZE) {
logmsg("File '", file, "' playback request (",
Expand Down Expand Up @@ -432,6 +459,9 @@ bool audio_play(const char* file, uint64_t start, uint64_t end, bool swap) {
sbufswap = swap;
sbufst_a = READY;
sbufst_b = READY;
audio_owner = owner & 7;
audio_bytes_read[audio_owner] = AUDIO_BUFFER_SIZE * 2;
audio_last_status[audio_owner] = ASC_PLAYING;

// prepare the wire buffers
for (uint16_t i = 0; i < WIRE_BUFFER_SIZE; i++) {
Expand Down Expand Up @@ -465,12 +495,25 @@ bool audio_play(const char* file, uint64_t start, uint64_t end, bool swap) {

// ready to go
dma_channel_start(SOUND_DMA_CHA);
audio_active = true;
return true;
}

bool audio_set_paused(bool paused) {
if (!audio_is_active()) return false;
else if (audio_paused && paused) return false;
else if (!audio_paused && !paused) return false;

audio_paused = paused;
if (paused) {
audio_last_status[audio_owner] = ASC_PAUSED;
} else {
audio_last_status[audio_owner] = ASC_PLAYING;
}
return true;
}

void audio_stop() {
if (!audio_active) return;
if (!audio_is_active()) return;

// to help mute external hardware, send a bunch of '0' samples prior to
// halting the datastream; easiest way to do this is invalidating the
Expand All @@ -490,7 +533,24 @@ void audio_stop() {
if (audio_file.isOpen()) {
audio_file.close();
}
audio_active = false;
audio_last_status[audio_owner] = ASC_COMPLETED;
audio_owner = 0xFF;
}

audio_status_code audio_get_status_code(uint8_t id) {
audio_status_code tmp = audio_last_status[id & 7];
if (tmp == ASC_COMPLETED || tmp == ASC_ERRORED) {
audio_last_status[id & 7] = ASC_NO_STATUS;
}
return tmp;
}

uint32_t audio_get_bytes_read(uint8_t id) {
return audio_bytes_read[id & 7];
}

void audio_clear_bytes_read(uint8_t id) {
audio_bytes_read[id & 7] = 0;
}

#ifdef __cplusplus
Expand Down
67 changes: 66 additions & 1 deletion lib/ZuluSCSI_platform_RP2040/audio.h
Expand Up @@ -32,6 +32,20 @@ extern "C" {
// these must be divisible by 1024
#define AUDIO_BUFFER_SIZE 8192 // ~46.44ms

/*
* Status codes for audio playback, matching the SCSI 'audio status codes'.
*
* The first two are for a live condition and will be returned repeatedly. The
* following two reflect a historical condition and are only returned once.
*/
enum audio_status_code {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would these parts be common to all platforms if another platform gets audio support in future?
If yes, it could be better to make src/ZuluSCSI_audio.h for the common parts and interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll resolve that.

ASC_PLAYING = 0x11,
ASC_PAUSED = 0x12,
ASC_COMPLETED = 0x13,
ASC_ERRORED = 0x14,
ASC_NO_STATUS = 0x15
};

/**
* Handler for DMA interrupts
*
Expand All @@ -52,6 +66,16 @@ void audio_dma_irq();
*/
bool audio_is_active();

/**
* \return true if audio streaming is paused, false otherwise.
*/
bool audio_is_paused();

/**
* \return the owner value passed to the _play() call, or 0xFF if no owner.
*/
uint8_t audio_get_owner();

/**
* Initializes the audio subsystem. Should be called only once, toward the end
* of platform_late_init().
Expand All @@ -66,19 +90,60 @@ void audio_poll();
/**
* Begins audio playback for a file.
*
* \param owner The SCSI ID that initiated this playback operation.
* \param file Path of a file containing PCM samples to play.
* \param start Byte offset within file where playback will begin, inclusive.
* \param end Byte offset within file where playback will end, exclusive.
* \param swap If false, little-endian sample order, otherwise big-endian.
* \return True if successful, false otherwise.
*/
bool audio_play(const char* file, uint64_t start, uint64_t end, bool swap);
bool audio_play(uint8_t owner, const char* file, uint64_t start, uint64_t end, bool swap);

/**
* Pauses audio playback. This may be delayed slightly to allow sample buffers
* to purge.
*
* \param pause If true, pause, otherwise resume.
* \return True if operation changed audio output, false if no change.
*/
bool audio_set_paused(bool pause);

/**
* Stops audio playback.
*/
void audio_stop();

/**
* Provides SCSI 'audio status code' for the given target. Depending on the
* code this operation may produce side-effects, see the enum for details.
*
* \param id The SCSI ID to provide status codes for.
* \return The matching audio status code.
*/
audio_status_code audio_get_status_code(uint8_t id);

/**
* Provides the number of sample bytes read in during an audio_play() call.
* This can be combined with an (external) starting offset to determine
* virtual CD positioning information. This is only an approximation since
* this tracker is always at the end of the most recently read sample data.
*
* This is intentionally not cleared by audio_stop(): audio_play() events will
* reset this information.
*
* \param id The SCSI ID target to return data for.
* \return The number of bytes read in during a playback.
*/
uint32_t audio_get_bytes_read(uint8_t id);

/**
* Clears the byte counter in the above call. This is insensitive to whether
* audio playback is occurring but is safe to call in any event.
*
* \param id The SCSI ID target to return data for.
*/
void audio_clear_bytes_read(uint8_t id);

#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions src/ImageBackingStore.cpp
Expand Up @@ -313,3 +313,15 @@ void ImageBackingStore::flush()
m_fsfile.flush();
}
}

size_t ImageBackingStore::name(char* filename, size_t len)
{
if (!m_israw && !m_isrom)
PetteriAimonen marked this conversation as resolved.
Show resolved Hide resolved
{
return m_fsfile.getName(filename, len);
}
else
{
return 0;
}
}
3 changes: 3 additions & 0 deletions src/ImageBackingStore.h
Expand Up @@ -71,6 +71,9 @@ class ImageBackingStore
// Flush any pending changes to filesystem
void flush();

// If available, read filename and return actual length.
size_t name(char* filename, size_t len);

protected:
bool m_israw;
bool m_isrom;
Expand Down