|  | @@ -34,6 +34,9 @@
 | 
											
												
													
														|  |  #include "BlueSCSI_cdrom.h"
 |  |  #include "BlueSCSI_cdrom.h"
 | 
											
												
													
														|  |  #include <CUEParser.h>
 |  |  #include <CUEParser.h>
 | 
											
												
													
														|  |  #include <assert.h>
 |  |  #include <assert.h>
 | 
											
												
													
														|  | 
 |  | +#ifdef ENABLE_AUDIO_OUTPUT
 | 
											
												
													
														|  | 
 |  | +#include "audio.h"
 | 
											
												
													
														|  | 
 |  | +#endif
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  extern "C" {
 |  |  extern "C" {
 | 
											
												
													
														|  |  #include <scsi.h>
 |  |  #include <scsi.h>
 | 
											
										
											
												
													
														|  | @@ -907,45 +910,171 @@ static void doGetEventStatusNotification(bool immed)
 | 
											
												
													
														|  |  /* CD-ROM audio playback              */
 |  |  /* CD-ROM audio playback              */
 | 
											
												
													
														|  |  /**************************************/
 |  |  /**************************************/
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -// TODO: where to store these? Should be updated as playback progresses.
 |  | 
 | 
											
												
													
														|  | -static CDROMAudioPlaybackStatus g_cdrom_audio_status;
 |  | 
 | 
											
												
													
														|  | -static uint32_t g_cdrom_audio_lba;
 |  | 
 | 
											
												
													
														|  | 
 |  | +typedef struct {
 | 
											
												
													
														|  | 
 |  | +    uint32_t last_lba = 0;
 | 
											
												
													
														|  | 
 |  | +} mechanism_status_t;
 | 
											
												
													
														|  | 
 |  | +static mechanism_status_t mechanism_status[8];
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -void cdromGetAudioPlaybackStatus(CDROMAudioPlaybackStatus *status, uint32_t *current_lba)
 |  | 
 | 
											
												
													
														|  | 
 |  | +void cdromGetAudioPlaybackStatus(uint8_t *status, uint32_t *current_lba, bool current_only)
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  | -    if (status) *status = g_cdrom_audio_status;
 |  | 
 | 
											
												
													
														|  | -    if (current_lba) *current_lba = g_cdrom_audio_lba;
 |  | 
 | 
											
												
													
														|  | 
 |  | +    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
 | 
											
												
													
														|  | 
 |  | +    uint8_t target = img.scsiId & 7;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +#ifdef ENABLE_AUDIO_OUTPUT
 | 
											
												
													
														|  | 
 |  | +    if (status) {
 | 
											
												
													
														|  | 
 |  | +        if (current_only) {
 | 
											
												
													
														|  | 
 |  | +            *status = audio_get_owner() == target ? 1 : 0;
 | 
											
												
													
														|  | 
 |  | +        } else {
 | 
											
												
													
														|  | 
 |  | +            *status = (uint8_t) audio_get_status_code(target);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +    if (current_lba) *current_lba = mechanism_status[target].last_lba
 | 
											
												
													
														|  | 
 |  | +            + audio_get_bytes_read(target) / 2352;
 | 
											
												
													
														|  | 
 |  | +#elif
 | 
											
												
													
														|  | 
 |  | +    if (status) *status = 0; // audio status code for 'unsupported/invalid' and not-playing indicator
 | 
											
												
													
														|  | 
 |  | +    if (current_lba) *current_lba = mechanism_status[target].last_lba;
 | 
											
												
													
														|  | 
 |  | +#endif
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  static void doPlayAudio(uint32_t lba, uint32_t length)
 |  |  static void doPlayAudio(uint32_t lba, uint32_t length)
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  | -    log("------ CD-ROM Play Audio starting at ", lba, " for ", length, " sectors");
 |  | 
 | 
											
												
													
														|  | 
 |  | +#ifdef ENABLE_AUDIO_OUTPUT
 | 
											
												
													
														|  | 
 |  | +    debuglog("------ CD-ROM Play Audio request at ", lba, " for ", length, " sectors");
 | 
											
												
													
														|  | 
 |  | +    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
 | 
											
												
													
														|  | 
 |  | +    uint8_t target_id = img.scsiId & 7;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    g_cdrom_audio_lba = lba;
 |  | 
 | 
											
												
													
														|  | -    g_cdrom_audio_status = CDROMAudio_Stopped; // Playback not actually implemented
 |  | 
 | 
											
												
													
														|  | 
 |  | +    // Per Annex C terminate playback immediately if already in progress on
 | 
											
												
													
														|  | 
 |  | +    // the current target. Non-current targets may also get their audio
 | 
											
												
													
														|  | 
 |  | +    // interrupted later due to hardware limitations
 | 
											
												
													
														|  | 
 |  | +    if ((img.scsiId & 7) == audio_get_owner()) {
 | 
											
												
													
														|  | 
 |  | +        audio_stop();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    scsiDev.status = 0;
 |  | 
 | 
											
												
													
														|  | 
 |  | +    // if transfer length is zero no audio playback happens.
 | 
											
												
													
														|  | 
 |  | +    // don't treat as an error per SCSI-2; handle via short-circuit
 | 
											
												
													
														|  | 
 |  | +    if (length == 0)
 | 
											
												
													
														|  | 
 |  | +    {
 | 
											
												
													
														|  | 
 |  | +        scsiDev.status = 0;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +        return;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    // if actual playback is requested perform steps to verify prior to playback
 | 
											
												
													
														|  | 
 |  | +    CUEParser parser;
 | 
											
												
													
														|  | 
 |  | +    if (loadCueSheet(img, parser))
 | 
											
												
													
														|  | 
 |  | +    {
 | 
											
												
													
														|  | 
 |  | +        CUETrackInfo trackinfo = {};
 | 
											
												
													
														|  | 
 |  | +        getTrackFromLBA(parser, lba, &trackinfo);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        if (lba == 0xFFFFFFFF)
 | 
											
												
													
														|  | 
 |  | +        {
 | 
											
												
													
														|  | 
 |  | +            // request to start playback from 'current position'
 | 
											
												
													
														|  | 
 |  | +            lba = mechanism_status[target_id].last_lba + audio_get_bytes_read(target_id) / 2352;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // --- TODO --- determine proper track offset, software I tested with had a tendency
 | 
											
												
													
														|  | 
 |  | +        // to ask for offsets that seem to hint at 2048 here, not the 2352 you'd assume.
 | 
											
												
													
														|  | 
 |  | +        // Might be due to a mode page reporting something unexpected? Needs investigation.
 | 
											
												
													
														|  | 
 |  | +        uint64_t offset = trackinfo.file_offset + 2048 * (lba - trackinfo.data_start);
 | 
											
												
													
														|  | 
 |  | +        debuglog("------ Play audio CD: ", (int)length, " sectors starting at ", (int)lba,
 | 
											
												
													
														|  | 
 |  | +           ", track number ", trackinfo.track_number, ", data offset in file ", (int)offset);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        if (trackinfo.track_mode != CUETrack_AUDIO)
 | 
											
												
													
														|  | 
 |  | +        {
 | 
											
												
													
														|  | 
 |  | +            debuglog("---- Host tried audio playback on track type ", (int)trackinfo.track_mode);
 | 
											
												
													
														|  | 
 |  | +            scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.target->sense.code = ILLEGAL_REQUEST;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.target->sense.asc = 0x6400; // ILLEGAL MODE FOR THIS TRACK
 | 
											
												
													
														|  | 
 |  | +            scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +            return;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // playback request appears to be sane, so perform it
 | 
											
												
													
														|  | 
 |  | +        char filename[MAX_FILE_PATH];
 | 
											
												
													
														|  | 
 |  | +        if (!img.file.name(filename, sizeof(filename)))
 | 
											
												
													
														|  | 
 |  | +        {
 | 
											
												
													
														|  | 
 |  | +            // No underlying file available?
 | 
											
												
													
														|  | 
 |  | +            log("---- No filename for SCSI ID ", target_id);
 | 
											
												
													
														|  | 
 |  | +            scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.target->sense.code = ILLEGAL_REQUEST;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.target->sense.asc = 0x0000;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +            return;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        if (!audio_play(target_id, filename, offset, offset + length * 2352, false))
 | 
											
												
													
														|  | 
 |  | +        {
 | 
											
												
													
														|  | 
 |  | +            // Underlying data/media error? Fake a disk scratch, which should
 | 
											
												
													
														|  | 
 |  | +            // be a condition most CD-DA players are expecting
 | 
											
												
													
														|  | 
 |  | +            scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.target->sense.code = MEDIUM_ERROR;
 | 
											
												
													
														|  | 
 |  | +            scsiDev.target->sense.asc = 0x1106; // CIRC UNRECOVERED ERROR
 | 
											
												
													
														|  | 
 |  | +            scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +            return;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        mechanism_status[target_id].last_lba = lba;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.status = 0;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +    else
 | 
											
												
													
														|  | 
 |  | +    {
 | 
											
												
													
														|  | 
 |  | +        // virtual drive supports audio, just not with this disk image
 | 
											
												
													
														|  | 
 |  | +        debuglog("---- Request to play audio on non-audio image");
 | 
											
												
													
														|  | 
 |  | +        scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.target->sense.code = ILLEGAL_REQUEST;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.target->sense.asc = 0x6400; // ILLEGAL MODE FOR THIS TRACK
 | 
											
												
													
														|  | 
 |  | +        scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +#elif
 | 
											
												
													
														|  | 
 |  | +    debuglog("---- Target does not support audio playback");
 | 
											
												
													
														|  | 
 |  | +    // per SCSI-2, targets not supporting audio respond to zero-length
 | 
											
												
													
														|  | 
 |  | +    // PLAY AUDIO commands with ILLEGAL REQUEST; this seems to be a check
 | 
											
												
													
														|  | 
 |  | +    // performed by at least some audio playback software
 | 
											
												
													
														|  | 
 |  | +    scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +    scsiDev.target->sense.code = ILLEGAL_REQUEST;
 | 
											
												
													
														|  | 
 |  | +    scsiDev.target->sense.asc = 0x0000; // NO ADDITIONAL SENSE INFORMATION
 | 
											
												
													
														|  |      scsiDev.phase = STATUS;
 |  |      scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +#endif
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  static void doPauseResumeAudio(bool resume)
 |  |  static void doPauseResumeAudio(bool resume)
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  | 
 |  | +#ifdef ENABLE_AUDIO_OUTPUT
 | 
											
												
													
														|  |      log("------ CD-ROM ", resume ? "resume" : "pause", " audio playback");
 |  |      log("------ CD-ROM ", resume ? "resume" : "pause", " audio playback");
 | 
											
												
													
														|  | 
 |  | +    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
 | 
											
												
													
														|  | 
 |  | +    uint8_t target_id = img.scsiId & 7;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    scsiDev.status = 0;
 |  | 
 | 
											
												
													
														|  | 
 |  | +    if (audio_get_owner() == target_id)
 | 
											
												
													
														|  | 
 |  | +    {
 | 
											
												
													
														|  | 
 |  | +        audio_set_paused(!resume);
 | 
											
												
													
														|  | 
 |  | +        scsiDev.status = 0;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +    else
 | 
											
												
													
														|  | 
 |  | +    {
 | 
											
												
													
														|  | 
 |  | +        scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.target->sense.code = ILLEGAL_REQUEST;
 | 
											
												
													
														|  | 
 |  | +        scsiDev.target->sense.asc = 0x2C00; // COMMAND SEQUENCE ERROR
 | 
											
												
													
														|  | 
 |  | +        scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +#elif
 | 
											
												
													
														|  | 
 |  | +    debuglog("---- Target does not support audio pausing");
 | 
											
												
													
														|  | 
 |  | +    scsiDev.status = CHECK_CONDITION;
 | 
											
												
													
														|  | 
 |  | +    scsiDev.target->sense.code = ILLEGAL_REQUEST; // assumed from PLAY AUDIO(10)
 | 
											
												
													
														|  | 
 |  | +    scsiDev.target->sense.asc = 0x0000; // NO ADDITIONAL SENSE INFORMATION
 | 
											
												
													
														|  |      scsiDev.phase = STATUS;
 |  |      scsiDev.phase = STATUS;
 | 
											
												
													
														|  | 
 |  | +#endif
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  static void doMechanismStatus(uint16_t allocation_length)
 |  |  static void doMechanismStatus(uint16_t allocation_length)
 | 
											
												
													
														|  |  {
 |  |  {
 | 
											
												
													
														|  |      uint8_t *buf = scsiDev.data;
 |  |      uint8_t *buf = scsiDev.data;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    CDROMAudioPlaybackStatus status;
 |  | 
 | 
											
												
													
														|  | 
 |  | +    uint8_t status;
 | 
											
												
													
														|  |      uint32_t lba;
 |  |      uint32_t lba;
 | 
											
												
													
														|  | -    cdromGetAudioPlaybackStatus(&status, &lba);
 |  | 
 | 
											
												
													
														|  | 
 |  | +    cdromGetAudioPlaybackStatus(&status, &lba, true);
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      *buf++ = 0x00; // No fault state
 |  |      *buf++ = 0x00; // No fault state
 | 
											
												
													
														|  | -    *buf++ = (status == CDROMAudio_Playing) ? 0x20 : 0x00; // Currently playing?
 |  | 
 | 
											
												
													
														|  | 
 |  | +    *buf++ = (status) ? 0x20 : 0x00; // Currently playing?
 | 
											
												
													
														|  |      *buf++ = (lba >> 16) & 0xFF;
 |  |      *buf++ = (lba >> 16) & 0xFF;
 | 
											
												
													
														|  |      *buf++ = (lba >> 8) & 0xFF;
 |  |      *buf++ = (lba >> 8) & 0xFF;
 | 
											
												
													
														|  |      *buf++ = (lba >> 0) & 0xFF;
 |  |      *buf++ = (lba >> 0) & 0xFF;
 | 
											
										
											
												
													
														|  | @@ -1173,9 +1302,9 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      if (parameter == 0x01)
 |  |      if (parameter == 0x01)
 | 
											
												
													
														|  |      {
 |  |      {
 | 
											
												
													
														|  | -        CDROMAudioPlaybackStatus audiostatus;
 |  | 
 | 
											
												
													
														|  | 
 |  | +        uint8_t audiostatus;
 | 
											
												
													
														|  |          uint32_t lba;
 |  |          uint32_t lba;
 | 
											
												
													
														|  | -        cdromGetAudioPlaybackStatus(&audiostatus, &lba);
 |  | 
 | 
											
												
													
														|  | 
 |  | +        cdromGetAudioPlaybackStatus(&audiostatus, &lba, false);
 | 
											
												
													
														|  |          debuglog("------ Get audio playback position: status ", (int)audiostatus, " lba ", (int)lba);
 |  |          debuglog("------ Get audio playback position: status ", (int)audiostatus, " lba ", (int)lba);
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |          // Fetch current track info
 |  |          // Fetch current track info
 | 
											
										
											
												
													
														|  | @@ -1187,14 +1316,7 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |          // Request sub channel data at current playback position
 |  |          // Request sub channel data at current playback position
 | 
											
												
													
														|  |          *buf++ = 0; // Reserved
 |  |          *buf++ = 0; // Reserved
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        switch (audiostatus)
 |  | 
 | 
											
												
													
														|  | -        {
 |  | 
 | 
											
												
													
														|  | -            case CDROMAudio_Playing: *buf++ = 0x11; break;
 |  | 
 | 
											
												
													
														|  | -            case CDROMAudio_Paused:  *buf++ = 0x12; break;
 |  | 
 | 
											
												
													
														|  | -            case CDROMAudio_Stopped: *buf++ = 0x13; break;
 |  | 
 | 
											
												
													
														|  | -            default: *buf++ = 0; break;
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | 
 |  | +        *buf++ = audiostatus;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |          int len = 12;
 |  |          int len = 12;
 | 
											
												
													
														|  |          *buf++ = 0;  // Subchannel data length (MSB)
 |  |          *buf++ = 0;  // Subchannel data length (MSB)
 |