Skip to content

Commit

Permalink
Merge pull request #228 from saybur/cdrom-modes
Browse files Browse the repository at this point in the history
CD-ROM: volume control and mode changes
  • Loading branch information
aperezbios committed May 28, 2023
2 parents 77b226c + bc285e0 commit e8e0849
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 71 deletions.
74 changes: 19 additions & 55 deletions lib/SCSI2SD/src/firmware/mode.c
Expand Up @@ -21,6 +21,7 @@
#include "mode.h"
#include "disk.h"
#include "inquiry.h"
#include "ZuluSCSI_mode.h"

#include <string.h>

Expand Down Expand Up @@ -220,33 +221,6 @@ static const uint8_t ControlModePage[] =
0x00, 0x00 // AEN holdoff period.
};

#ifdef ENABLE_AUDIO_OUTPUT
static const uint8_t CDROMCDParametersPage[] =
{
0x0D, // page code
0x06, // page length
0x00, // reserved
0x0D, // reserved, inactivity time 8 min
0x00, 0x3C, // 60 seconds per MSF M unit
0x00, 0x4B // 75 frames per MSF S unit
};

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 @@ -420,7 +394,8 @@ static void doModeSense(
}
}

if (pageCode == 0x03 || pageCode == 0x3F)
if ((pageCode == 0x03 || pageCode == 0x3F) &&
(scsiDev.target->cfg->deviceType != S2S_CFG_OPTICAL))
{
pageFound = 1;
pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
Expand All @@ -445,7 +420,8 @@ static void doModeSense(
idx += sizeof(FormatDevicePage);
}

if (pageCode == 0x04 || pageCode == 0x3F)
if ((pageCode == 0x04 || pageCode == 0x3F) &&
(scsiDev.target->cfg->deviceType != S2S_CFG_OPTICAL))
{
pageFound = 1;
if ((scsiDev.compatMode >= COMPAT_SCSI2))
Expand Down Expand Up @@ -523,31 +499,8 @@ static void doModeSense(
idx += sizeof(ControlModePage);
}

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

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

if ((scsiDev.target->cfg->deviceType == S2S_CFG_SEQUENTIAL) &&
(pageCode == 0x10 || pageCode == 0x3F))
Expand Down Expand Up @@ -670,10 +623,16 @@ static void doModeSelect(void)

while (idx < scsiDev.dataLen)
{
// Change from SCSI2SD: if code page is 0x0 (vendor-specific) it
// will not follow the normal page mode format and cannot be
// parsed, but isn't necessarily an error. Instead, just treat it
// as an 'end of data' field and allow normal command completion.
int pageCode = scsiDev.data[idx] & 0x3F;
if (pageCode == 0) goto out;

int pageLen = scsiDev.data[idx + 1];
if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;

int pageCode = scsiDev.data[idx] & 0x3F;
switch (pageCode)
{
case 0x03: // Format Device Page
Expand All @@ -699,6 +658,11 @@ static void doModeSelect(void)
}
}
break;
case 0x0E: // CD audio control page
{
if (!modeSelectCDAudioControlPage(pageLen, idx)) goto bad;
}
break;
//default:

// Easiest to just ignore for now. We'll get here when changing
Expand Down
50 changes: 34 additions & 16 deletions lib/ZuluSCSI_platform_RP2040/audio.cpp
Expand Up @@ -140,6 +140,12 @@ static uint32_t fleft;
// historical playback status information
static audio_status_code audio_last_status[8] = {ASC_NO_STATUS};

// volume information for targets
static volatile uint16_t volumes[8] = {
DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL,
DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL
};

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

Expand All @@ -158,31 +164,35 @@ static uint8_t invert = 0; // biphase encode help: set if last wire bit was '1'
* output.
*/
static void snd_encode(uint8_t* samples, uint16_t* wire_patterns, uint16_t len, uint8_t swap) {
uint16_t wvol = volumes[audio_owner & 7];
uint8_t vol = ((wvol >> 8) + (wvol & 0xFF)) >> 1; // average of both values
// limit maximum volume; with my DACs I've had persistent issues
// with signal clipping when sending data in the highest bit position
vol = vol >> 2;

uint16_t widx = 0;
for (uint16_t i = 0; i < len; i += 2) {
uint32_t sample = 0;
uint8_t parity = 0;
if (samples != NULL) {
int32_t rsamp;
if (swap) {
sample = samples[i + 1] + (samples[i] << 8);
rsamp = (int16_t)(samples[i + 1] + (samples[i] << 8));
} else {
sample = samples[i] + (samples[i + 1] << 8);
rsamp = (int16_t)(samples[i] + (samples[i + 1] << 8));
}
// determine parity, simplified to one lookup via an XOR
parity = (sample >> 8) ^ sample;
// linear scale to requested audio value
rsamp *= vol;
// use 20 bits of value only, which allows ignoring the lowest 8
// bits during biphase conversion (after including sample shift)
sample = ((uint32_t)rsamp) & 0xFFFFF0;

// determine parity, simplified to one lookup via XOR
parity = ((sample >> 16) ^ (sample >> 8)) ^ sample;
parity = snd_parity[parity];

/*
* Shift sample into the correct bit positions of the sub-frame. This
* would normally be << 12, but with my DACs I've had persistent issues
* with signal clipping when sending data in the highest bit position.
*/
sample = sample << 11;
if (sample & 0x04000000) {
// handle two's complement
sample |= 0x08000000;
parity++;
}
// shift sample into the correct bit positions of the sub-frame.
sample = sample << 4;
}

// if needed, establish even parity with P bit
Expand All @@ -202,7 +212,7 @@ static void snd_encode(uint8_t* samples, uint16_t* wire_patterns, uint16_t len,
if (invert) wp = ~wp;
invert = wp & 1;
wire_patterns[widx++] = wp;
// next 8 bits (only high 4 have data)
// next 8 bits
wp = biphase[(uint8_t) (sample >> 8)];
if (invert) wp = ~wp;
invert = wp & 1;
Expand Down Expand Up @@ -544,4 +554,12 @@ audio_status_code audio_get_status_code(uint8_t id) {
return tmp;
}

uint16_t audio_get_volume(uint8_t id) {
return volumes[id & 7];
}

void audio_set_volume(uint8_t id, uint16_t vol) {
volumes[id & 7] = vol;
}

#endif // ENABLE_AUDIO_OUTPUT
31 changes: 31 additions & 0 deletions src/ZuluSCSI_audio.h
Expand Up @@ -24,6 +24,17 @@
#include <stdint.h>
#include "ImageBackingStore.h"

/*
* Starting volume level for audio output, with 0 being muted and 255 being
* max volume. SCSI-2 says this should be 25% of maximum by default, MMC-1
* says 100%. Testing shows this tends to be obnoxious at high volumes, so
* go with SCSI-2.
*
* This implementation uses the high byte for output port 1 and the low byte
* for port 0. The two values are averaged to determine final volume level.
*/
#define DEFAULT_VOLUME_LEVEL 0x3F3F

/*
* Status codes for audio playback, matching the SCSI 'audio status codes'.
*
Expand Down Expand Up @@ -86,3 +97,23 @@ void audio_stop(uint8_t id);
* \return The matching audio status code.
*/
audio_status_code audio_get_status_code(uint8_t id);

/**
* Gets the current volume level for a target. This is a pair of 8-bit values
* ranging from 0-255 that are averaged together to determine the final output
* level, where 0 is muted and 255 is maximum volume. The high byte corresponds
* to 0x0E channel 1 and the low byte to 0x0E channel 0. See the spec's mode
* page documentation for more details.
*
* \param id SCSI ID to provide volume for.
* \return The matching volume level.
*/
uint16_t audio_get_volume(uint8_t id);

/**
* Sets the volume level for a target, as above. See 0x0E mode page for more.
*
* \param id SCSI ID to set volume for.
* \param vol The new volume level.
*/
void audio_set_volume(uint8_t id, uint16_t vol);

0 comments on commit e8e0849

Please sign in to comment.