shithub: choc

ref: 439a4b158b47c635100b0006470a7ed1e44e29f3
dir: /src/hexen/i_cdmus.c/

View raw version
// Emacs style mode select   -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
//
// 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 2
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
//-----------------------------------------------------------------------------


// HEADER FILES ------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <stddef.h>
#include <string.h>
#include "h2def.h"
#include "i_sound.h"

// MACROS ------------------------------------------------------------------

#define MAX_AUDIO_TRACKS 25
#define MULTIPLEX_INT 0x2f
#define CDROM_GETDRIVECOUNT 0x1500
#define CDROM_SENDDEVICEREQ 0x1510
#define CDROM_GETVERSION 0x150c
#define HSG_MODE 0
#define RED_MODE 1
#define DRC_IOCTLINPUT 0x03
#define DRC_IOCTLOUTPUT 0x0c
#define DRC_PLAYAUDIO 0x84
#define DRC_STOPAUDIO 0x85
#define DRC_RESUMEAUDIO 0x88

#define DPMI_INT 0x31
#define DPMI_ALLOCREALMEM 0x0100
#define DPMI_FREEREALMEM 0x0101
#define DPMI_SIMREALINT 0x0300

// IOCTL input commands

#define ADRDEVHEAD       0      // Return Address of Device Header
#define HEADLOCATION     1      // Location of Head
#define RESERVED         2      // Reserved
#define ERRSTATISTICS    3      // Error Statistics
#define AUDIOCHANINFO    4      // Audio Channel Info
#define READDRVBYTES     5      // Read Drive Bytes
#define DEVICESTATUS     6      // Device Status
#define GETSECTORSIZE    7      // Return Sector Size
#define GETVOLSIZE       8      // Return Volume Size
#define MEDIACHANGED     9      // Media Changed
#define AUDIODISKINFO   10      // Audio Disk Info
#define AUDIOTRACKINFO  11      // Audio Track Info
#define AUDIOQCHANINFO  12      // Audio Q-Channel Info
#define AUDIOSUBINFO    13      // Audio Sub-Channel Info
#define UPCCODE         14      // UPC Code
#define AUDIOSTATUSINFO 15      // Audio Status Info

// IOCTL output commands

#define EJECTDISK        0      // Eject Disk
#define DOORLOCK         1      // Lock/Unlock Door
#define RESETDRIVE       2      // Reset Drive
#define AUDIOCHANCONTROL 3      // Audio Channel Control
#define WRITEDEVCONTROL  4      // Write Device Control String
#define CLOSETRAY        5      // Close Tray

// TYPES -------------------------------------------------------------------

typedef signed char S_BYTE;
typedef unsigned char U_BYTE;
typedef signed short S_WORD;
typedef unsigned short U_WORD;
typedef signed int S_LONG;
typedef unsigned int U_LONG;

typedef struct
{
    U_LONG size;
    void **address;
    U_WORD *segment;
    U_WORD *selector;
} DOSChunk_t;

typedef struct
{
    U_LONG edi;
    U_LONG esi;
    U_LONG ebp;
    U_LONG reserved;
    U_LONG ebx;
    U_LONG edx;
    U_LONG ecx;
    U_LONG eax;
    U_WORD flags;
    U_WORD es;
    U_WORD ds;
    U_WORD fs;
    U_WORD gs;
    U_WORD ip;
    U_WORD cs;
    U_WORD sp;
    U_WORD ss;
} RegBlock_t;

typedef struct
{
    short lengthMin;
    short lengthSec;
    int redStart;
    int sectorStart;
    int sectorLength;
} AudioTrack_t;

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static U_WORD InputIOCTL(S_WORD request, U_LONG pctrlblk);
static U_WORD OutputIOCTL(S_WORD request, U_LONG pctrlblk);
static U_LONG RedToSectors(U_LONG red);
static int AllocIOCTLBuffers(void);
static void DPMI_SimRealInt(U_LONG intr, RegBlock_t * rBlock);
static void *DPMI_AllocRealMem(U_LONG size, U_WORD * segment,
                               U_WORD * selector);
static void DPMI_FreeRealMem(U_WORD selector);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

int cd_Error;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static int cd_DriveCount;
static int cd_FirstDrive;
static int cd_CurDrive;
static U_WORD cd_Version;
static int cd_FirstTrack;
static int cd_LastTrack;
static int cd_TrackCount;
static U_WORD cd_LeadOutMin;
static U_WORD cd_LeadOutSec;
static U_LONG cd_LeadOutRed;
static U_LONG cd_LeadOutSector;
static U_LONG cd_IOCTLBufferTotal;
static AudioTrack_t cd_AudioTracks[MAX_AUDIO_TRACKS];

static int OkInit = 0;

static RegBlock_t RegBlock;

static struct PlayReq_s
{                               // CD-ROM Play Audio Device Request Struct
    U_BYTE headerSize;
    U_BYTE subUnitCode;
    U_BYTE command;             // = DRC_PLAYAUDIO
    U_WORD status;
    U_BYTE reserved[8];
    U_BYTE addressMode;
    U_LONG startSector;
    U_LONG numberToRead;
} *cd_PlayReq;
static U_WORD cd_PlayReqSeg;
static U_WORD cd_PlayReqSel;

static struct StopReq_s
{                               // CD-ROM Stop Audio Device Request Struct
    U_BYTE headerSize;
    U_BYTE subUnitCode;
    U_BYTE command;             // = DRC_STOPAUDIO
    U_WORD status;
    U_BYTE reserved[8];
} *cd_StopReq;
static U_WORD cd_StopReqSeg;
static U_WORD cd_StopReqSel;

static struct ResumeReq_s
{                               // CD-ROM Resume Audio Device Request Struct
    U_BYTE headerSize;
    U_BYTE subUnitCode;
    U_BYTE command;             // = DRC_RESUMEAUDIO
    U_WORD status;
    U_BYTE reserved[8];
} *cd_ResumeReq;
static U_WORD cd_ResumeReqSeg;
static U_WORD cd_ResumeReqSel;

// IOCTL Input command data buffer structures

static struct IOCTLIn_s
{                               // IOCTL Input Struct
    U_BYTE headerSize;
    U_BYTE subUnitCode;
    U_BYTE command;             // = DRC_IOCTLINPUT
    U_WORD status;
    U_BYTE reserved[8];
    U_BYTE mediaDescriptor;
    U_LONG ctrlBlkAddr;
    U_WORD tranSize;
    U_WORD startSector;
    U_LONG volPtr;
} *cd_IOCTLIn;
static U_WORD cd_IOCTLInSeg;
static U_WORD cd_IOCTLInSel;

static struct RAddrDevHead_s
{
    U_BYTE code;                // ADRDEVHEAD
    U_LONG devHdrAddr;          // Address of device header
} *cd_RAddrDevHead;
static U_WORD cd_RAddrDevHeadSeg;
static U_WORD cd_RAddrDevHeadSel;

static struct LocHead_s
{
    U_BYTE code;                // HEADLOCATION
    U_BYTE addrMode;            // Addressing mode
    U_LONG headLocation;        // Location of drive head
} *cd_LocHead;
static U_WORD cd_LocHeadSeg;
static U_WORD cd_LocHeadSel;

static struct ErrStat_s
{
    U_BYTE code;                // ERRSTATISTICS
    U_BYTE errVal;              // Error statistics
} *cd_ErrStat;
static U_WORD cd_ErrStatSeg;
static U_WORD cd_ErrStatSel;

static struct AudChanInfo_s
{
    U_BYTE code;                // AUDIOCHANINFO
    U_BYTE inChanOut0;          // Input chan(0,1,2,or 3) for output chan 0
    U_BYTE volumeOut0;          // Volume control (0-0xff) for output chan 0
    U_BYTE inChanOut1;          // Input chan(0,1,2,or 3) for output chan 1
    U_BYTE volumeOut1;          // Volume control (0-0xff) for output chan 1
    U_BYTE inChanOut2;          // Input chan(0,1,2,or 3) for output chan 2
    U_BYTE volumeOut2;          // Volume control (0-0xff) for output chan 2
    U_BYTE inChanOut3;          // Input chan(0,1,2,or 3) for output chan 3
    U_BYTE volumeOut3;          // Volume control (0-0xff) for output chan 3
} *cd_AudChanInfo;
static U_WORD cd_AudChanInfoSeg;
static U_WORD cd_AudChanInfoSel;

static struct RDrvBytes_s
{
    U_BYTE code;                // READDRVBYTES
    U_BYTE numBytes;            // Number of bytes to read
    U_BYTE rBuffer[128];        // Read buffer
} *cd_RDrvBytes;
static U_WORD cd_RDrvBytesSeg;
static U_WORD cd_RDrvBytesSel;

static struct DevStat_s
{
    U_BYTE code;                // DEVICESTATUS
    U_LONG devParams;           // Device parameters
} *cd_DevStat;
static U_WORD cd_DevStatSeg;
static U_WORD cd_DevStatSel;

static struct SectSize_s
{
    U_BYTE code;                // GETSECTORSIZE
    U_BYTE readMode;            // Read mode
    U_WORD sectorSize;          // Sector size
} *cd_SectSize;
static U_WORD cd_SectSizeSeg;
static U_WORD cd_SectSizeSel;

static struct VolSize_s
{
    U_BYTE code;                // GETVOLSIZE
    U_LONG volumeSize;          // Volume size
} *cd_VolSize;
static U_WORD cd_VolSizeSeg;
static U_WORD cd_VolSizeSel;

static struct MedChng_s
{
    U_BYTE code;                // MEDIACHANGED
    U_BYTE changed;             // Media byte
} *cd_MedChng;
static U_WORD cd_MedChngSeg;
static U_WORD cd_MedChngSel;

static struct DiskInfo_s
{
    U_BYTE code;                // AUDIODISKINFO
    U_BYTE lowTrack;            // Lowest track number
    U_BYTE highTrack;           // Highest track number
    U_LONG startLeadOut;        // Starting point of the lead-out track
} *cd_DiskInfo;
static U_WORD cd_DiskInfoSeg;
static U_WORD cd_DiskInfoSel;

static struct TrackInfo_s
{
    U_BYTE code;                // AUDIOTRACKINFO
    U_BYTE track;               // Track number
    U_LONG start;               // Starting point of the track
    U_BYTE ctrlInfo;            // Track control information
} *cd_TrackInfo;
static U_WORD cd_TrackInfoSeg;
static U_WORD cd_TrackInfoSel;

static struct QInfo_s
{
    U_BYTE code;                // AUDIOQCHANINFO
    U_BYTE control;             // CONTROL and ADR byte
    U_BYTE tno;                 // Track number (TNO)
    U_BYTE index;               // (POINT) or Index(X)
    U_BYTE min;                 // (MIN) Running time within a track
    U_BYTE sec;                 // (SEC)    "      "    "    "   "
    U_BYTE frame;               // (FRAME)  "      "    "    "   "
    U_BYTE zero;                // (ZERO)   "      "    "    "   "
    U_BYTE aMin;                // (AMIN) or (PMIN) Running time on disk
    U_BYTE aSec;                // (ASEC) or (PSEC)    "      "   "   "
    U_BYTE aFrame;              // (AFRAME) or (PFRAME)"      "   "   "
} *cd_QInfo;
static U_WORD cd_QInfoSeg;
static U_WORD cd_QInfoSel;

static struct SubChanInfo_s
{
    U_BYTE code;                // AUDIOSUBINFO
    U_LONG startSectAddr;       // Starting sector address
    U_LONG transAddr;           // Transfer address
    U_LONG numSects;            // Number of sectors to read
} *cd_SubChanInfo;
static U_WORD cd_SubChanInfoSeg;
static U_WORD cd_SubChanInfoSel;

static struct UPCCode_s
{
    U_BYTE code;                // UPCCODE
    U_BYTE control;             // CONTROL and ADR byte
    U_BYTE upc[7];              // UPC/EAN code
    U_BYTE zero;                // Zero
    U_BYTE aFrame;              // Aframe
} *cd_UPCCode;
static U_WORD cd_UPCCodeSeg;
static U_WORD cd_UPCCodeSel;

static struct AudStat_s
{
    U_BYTE code;                // AUDIOSTATUSINFO
    U_WORD status;              // Audio status bits
    U_LONG startPlay;           // Starting location of last Play/Resume
    U_LONG endPlay;             // Ending location for last Play/Resume
} *cd_AudStat;
static U_WORD cd_AudStatSeg;
static U_WORD cd_AudStatSel;

// IOCTL Output command data buffer structures

static struct IOCTLOut_s
{                               // IOCTL Output struct
    U_BYTE headerSize;
    U_BYTE subUnitCode;
    U_BYTE command;             // = DRC_IOCTLOUTPUT
    U_WORD status;
    U_BYTE reserved[8];
    U_BYTE mediaDescriptor;
    U_LONG ctrlBlkAddr;
    U_WORD tranSize;
    U_WORD startSector;
    U_LONG volPtr;
} *cd_IOCTLOut;
static U_WORD cd_IOCTLOutSeg;
static U_WORD cd_IOCTLOutSel;

static struct Eject_s
{
    U_BYTE code;                // EJECTDISK
} *cd_Eject;
static U_WORD cd_EjectSeg;
static U_WORD cd_EjectSel;

static struct LockDoor_s
{
    U_BYTE code;                // DOORLOCK
    U_BYTE lock;                // Lock function : 0 = unlock, 1 = lock
} *cd_LockDoor;
static U_WORD cd_LockDoorSeg;
static U_WORD cd_LockDoorSel;

static struct ResetDrv_s
{
    U_BYTE code;                // RESETDRIVE
} *cd_ResetDrv;
static U_WORD cd_ResetDrvSeg;
static U_WORD cd_ResetDrvSel;

static struct AudInfo_s
{
    U_BYTE code;                // AUDIOCHANCONTROL
    U_BYTE inChanOut0;          // Input chan(0,1,2,or 3) for output chan 0
    U_BYTE volumeOut0;          // Volume control (0-0xff) for output chan 0
    U_BYTE inChanOut1;          // Input chan(0,1,2,or 3) for output chan 1
    U_BYTE volumeOut1;          // Volume control (0-0xff) for output chan 1
    U_BYTE inChanOut2;          // Input chan(0,1,2,or 3) for output chan 2
    U_BYTE volumeOut2;          // Volume control (0-0xff) for output chan 2
    U_BYTE inChanOut3;          // Input chan(0,1,2,or 3) for output chan 3
    U_BYTE volumeOut3;          // Volume control (0-0xff) for output chan 3
} *cd_AudInfo;
static U_WORD cd_AudInfoSeg;
static U_WORD cd_AudInfoSel;

static struct WDrvBytes_s
{
    U_BYTE code;                // WRITEDEVCONTROL
    U_BYTE buf[5];              // Write buffer - size ??
} *cd_WDrvBytes;
static U_WORD cd_WDrvBytesSeg;
static U_WORD cd_WDrvBytesSel;

static struct CloseTray_s
{
    U_BYTE code;                // CLOSETRAY
} *cd_CloseTray;
static U_WORD cd_CloseTraySeg;
static U_WORD cd_CloseTraySel;

static U_WORD InCtrlBlkSize[16] = {
    0x05, 0x06, 0x00, 0x00,
    0x09, 0x82, 0x05, 0x04,
    0x05, 0x02, 0x07, 0x07,
    0x0b, 0x0d, 0x0b, 0x0b
};

static U_WORD OutCtrlBlkSize[6] = {
    0x01, 0x02,
    0x01, 0x09,
    0x06, 0x01
};

// Structures for allocating conventional memory

static DOSChunk_t DOSChunks[] = {
    {
     sizeof(struct PlayReq_s),
     &cd_PlayReq, &cd_PlayReqSeg, &cd_PlayReqSel},
    {
     sizeof(struct StopReq_s),
     &cd_StopReq, &cd_StopReqSeg, &cd_StopReqSel},
    {
     sizeof(struct ResumeReq_s),
     &cd_ResumeReq, &cd_ResumeReqSeg, &cd_ResumeReqSel},
    {
     sizeof(struct IOCTLOut_s),
     &cd_IOCTLOut, &cd_IOCTLOutSeg, &cd_IOCTLOutSel},
    {
     sizeof(struct Eject_s),
     &cd_Eject, &cd_EjectSeg, &cd_EjectSel},
    {
     sizeof(struct LockDoor_s),
     &cd_LockDoor, &cd_LockDoorSeg, &cd_LockDoorSel},
    {
     sizeof(struct ResetDrv_s),
     &cd_ResetDrv, &cd_ResetDrvSeg, &cd_ResetDrvSel},
    {
     sizeof(struct AudInfo_s),
     &cd_AudInfo, &cd_AudInfoSeg, &cd_AudInfoSel},
    {
     sizeof(struct WDrvBytes_s),
     &cd_WDrvBytes, &cd_WDrvBytesSeg, &cd_WDrvBytesSel},
    {
     sizeof(struct CloseTray_s),
     &cd_CloseTray, &cd_CloseTraySeg, &cd_CloseTraySel},
    {
     sizeof(struct IOCTLIn_s),
     &cd_IOCTLIn, &cd_IOCTLInSeg, &cd_IOCTLInSel},
    {
     sizeof(struct RAddrDevHead_s),
     &cd_RAddrDevHead, &cd_RAddrDevHeadSeg, &cd_RAddrDevHeadSel},
    {
     sizeof(struct LocHead_s),
     &cd_LocHead, &cd_LocHeadSeg, &cd_LocHeadSel},
    {
     sizeof(struct ErrStat_s),
     &cd_ErrStat, &cd_ErrStatSeg, &cd_ErrStatSel},
    {
     sizeof(struct AudChanInfo_s),
     &cd_AudChanInfo, &cd_AudChanInfoSeg, &cd_AudChanInfoSel},
    {
     sizeof(struct RDrvBytes_s),
     &cd_RDrvBytes, &cd_RDrvBytesSeg, &cd_RDrvBytesSel},
    {
     sizeof(struct DevStat_s),
     &cd_DevStat, &cd_DevStatSeg, &cd_DevStatSel},
    {
     sizeof(struct SectSize_s),
     &cd_SectSize, &cd_SectSizeSeg, &cd_SectSizeSel},
    {
     sizeof(struct VolSize_s),
     &cd_VolSize, &cd_VolSizeSeg, &cd_VolSizeSel},
    {
     sizeof(struct MedChng_s),
     &cd_MedChng, &cd_MedChngSeg, &cd_MedChngSel},
    {
     sizeof(struct DiskInfo_s),
     &cd_DiskInfo, &cd_DiskInfoSeg, &cd_DiskInfoSel},
    {
     sizeof(struct TrackInfo_s),
     &cd_TrackInfo, &cd_TrackInfoSeg, &cd_TrackInfoSel},
    {
     sizeof(struct QInfo_s),
     &cd_QInfo, &cd_QInfoSeg, &cd_QInfoSel},
    {
     sizeof(struct SubChanInfo_s),
     &cd_SubChanInfo, &cd_SubChanInfoSeg, &cd_SubChanInfoSel},
    {
     sizeof(struct UPCCode_s),
     &cd_UPCCode, &cd_UPCCodeSeg, &cd_UPCCodeSel},
    {
     sizeof(struct AudStat_s),
     &cd_AudStat, &cd_AudStatSeg, &cd_AudStatSel},
    {
     0, NULL, NULL, NULL}
};

// CODE --------------------------------------------------------------------

//==========================================================================
//
// I_CDMusInit
//
// Initializes the CD audio system.  Must be called before using any
// other I_CDMus functions.
//
// Returns: 0 (ok) or -1 (error, in cd_Error).
//
//==========================================================================

int I_CDMusInit(void)
{
    int i;
    int sect;
    int maxTrack;
    S_BYTE startMin1 = 0;
    S_BYTE startSec1 = 0;
    S_BYTE startMin2 = 0;
    S_BYTE startSec2 = 0;
    S_BYTE lengthMin = 0;
    S_BYTE lengthSec = 0;

    if (OkInit != 1)
    {                           // Only execute if uninitialized

        // Get number of CD-ROM drives and first drive
        memset(&RegBlock, 0, sizeof(RegBlock));
        RegBlock.eax = CDROM_GETDRIVECOUNT;
        RegBlock.ebx = 0;
        DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
        cd_DriveCount = RegBlock.ebx;

        // MSCDEX not installed if number of drives = 0
        if (cd_DriveCount == 0)
        {
            cd_Error = CDERR_NOTINSTALLED;
            return -1;
        }
        cd_FirstDrive = RegBlock.ecx;
        cd_CurDrive = cd_FirstDrive;

        // Allocate the IOCTL buffers
        if (AllocIOCTLBuffers() == -1)
        {
            cd_Error = CDERR_IOCTLBUFFMEM;
            return -1;
        }

        // Get MSCDEX version
        // Major version in upper byte, minor version in lower byte
        memset(&RegBlock, 0, sizeof(RegBlock));
        RegBlock.eax = CDROM_GETVERSION;
        DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
        cd_Version = RegBlock.ebx;

        // Check device status to make sure we can read Audio CD's
        InputIOCTL(DEVICESTATUS, cd_DevStatSeg);
        if ((cd_DevStat->devParams & 0x0010) == 0)
        {
            cd_Error = CDERR_NOAUDIOSUPPORT;
            return -1;
        }
    }

    // Force audio to stop playing
    I_CDMusStop();

    // Make sure we have the current TOC
    InputIOCTL(MEDIACHANGED, cd_MedChngSeg);

    // Set track variables
    InputIOCTL(AUDIODISKINFO, cd_DiskInfoSeg);
    cd_FirstTrack = cd_DiskInfo->lowTrack;
    cd_LastTrack = cd_DiskInfo->highTrack;
    if (cd_FirstTrack == 0 && cd_FirstTrack == cd_LastTrack)
    {
        cd_Error = CDERR_NOAUDIOTRACKS;
        return -1;
    }
    cd_TrackCount = cd_LastTrack - cd_FirstTrack + 1;
    cd_LeadOutMin = cd_DiskInfo->startLeadOut >> 16 & 0xFF;
    cd_LeadOutSec = cd_DiskInfo->startLeadOut >> 8 & 0xFF;
    cd_LeadOutRed = cd_DiskInfo->startLeadOut;
    cd_LeadOutSector = RedToSectors(cd_DiskInfo->startLeadOut);

    // Create Red Book start, sector start, and sector length
    // for all tracks
    sect = cd_LeadOutSector;
    for (i = cd_LastTrack; i >= cd_FirstTrack; i--)
    {
        cd_TrackInfo->track = i;
        InputIOCTL(AUDIOTRACKINFO, cd_TrackInfoSeg);
        if (i < MAX_AUDIO_TRACKS)
        {
            cd_AudioTracks[i].redStart = cd_TrackInfo->start;
            cd_AudioTracks[i].sectorStart = RedToSectors(cd_TrackInfo->start);
            cd_AudioTracks[i].sectorLength =
                sect - RedToSectors(cd_TrackInfo->start);
        }
        sect = RedToSectors(cd_TrackInfo->start);
    }

    // Create track lengths in minutes and seconds
    if (cd_LastTrack >= MAX_AUDIO_TRACKS)
    {
        maxTrack = MAX_AUDIO_TRACKS - 1;
    }
    else
    {
        maxTrack = cd_LastTrack;
    }
    cd_TrackInfo->track = cd_FirstTrack;
    InputIOCTL(AUDIOTRACKINFO, cd_TrackInfoSeg);
    startMin1 = (cd_TrackInfo->start >> 16);
    startSec1 = (cd_TrackInfo->start >> 8);
    for (i = cd_FirstTrack; i <= maxTrack; i++)
    {
        cd_TrackInfo->track = i + 1;
        if (i < cd_LastTrack)
        {
            InputIOCTL(AUDIOTRACKINFO, cd_TrackInfoSeg);
            startMin2 = (cd_TrackInfo->start >> 16);
            startSec2 = (cd_TrackInfo->start >> 8);
        }
        else
        {
            startMin2 = cd_LeadOutRed >> 16;
            startSec2 = cd_LeadOutRed >> 8;
        }
        lengthSec = startSec2 - startSec1;
        lengthMin = startMin2 - startMin1;
        if (lengthSec < 0)
        {
            lengthSec += 60;
            lengthMin--;
        }
        cd_AudioTracks[i].lengthMin = lengthMin;
        cd_AudioTracks[i].lengthSec = lengthSec;
        startMin1 = startMin2;
        startSec1 = startSec2;
    }

    // Clip high tracks
    cd_LastTrack = maxTrack;

    OkInit = 1;
    return 0;
}

//==========================================================================
//
// I_CDMusPlay
//
// Play an audio CD track.
//
// Returns: 0 (ok) or -1 (error, in cd_Error).
//
//==========================================================================

int I_CDMusPlay(int track)
{
    int start;
    int len;

    if (track < cd_FirstTrack || track > cd_LastTrack)
    {
        cd_Error = CDERR_BADTRACK;
        return (-1);
    }
    I_CDMusStop();
    start = cd_AudioTracks[track].redStart;
    len = cd_AudioTracks[track].sectorLength;
    cd_PlayReq->addressMode = RED_MODE;
    cd_PlayReq->startSector = start;
    cd_PlayReq->numberToRead = len;
    memset(&RegBlock, 0, sizeof(RegBlock));
    RegBlock.eax = CDROM_SENDDEVICEREQ;
    RegBlock.ecx = cd_CurDrive;
    RegBlock.ebx = 0;
    RegBlock.es = cd_PlayReqSeg;
    DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
    if (cd_PlayReq->status & 0x8000)
    {
        cd_Error = CDERR_DEVREQBASE + (cd_PlayReq->status) & 0x00ff;
        return (-1);
    }
    return (0);
}

//==========================================================================
//
// I_CDMusStop
//
// Stops the playing of an audio CD.
//
// Returns: 0 (ok) or -1 (error, in cd_Error).
//
//==========================================================================

int I_CDMusStop(void)
{
    memset(&RegBlock, 0, sizeof(RegBlock));
    RegBlock.eax = CDROM_SENDDEVICEREQ;
    RegBlock.ecx = cd_CurDrive;
    RegBlock.ebx = 0;
    RegBlock.es = cd_StopReqSeg;
    DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
    if (cd_StopReq->status & 0x8000)
    {
        cd_Error = CDERR_DEVREQBASE + (cd_StopReq->status) & 0x00ff;
        return -1;
    }
    return 0;
}

//==========================================================================
//
// I_CDMusResume
//
// Resumes the playing of an audio CD.
//
// Returns: 0 (ok) or -1 (error, in cd_Error).
//
//==========================================================================

int I_CDMusResume(void)
{
    memset(&RegBlock, 0, sizeof(RegBlock));
    RegBlock.eax = CDROM_SENDDEVICEREQ;
    RegBlock.ecx = cd_CurDrive;
    RegBlock.ebx = 0;
    RegBlock.es = cd_ResumeReqSeg;
    DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
    if (cd_ResumeReq->status & 0x8000)
    {
        cd_Error = CDERR_DEVREQBASE + (cd_ResumeReq->status) & 0x00ff;
        return -1;
    }
    return 0;
}

//==========================================================================
//
// I_CDMusSetVolume
//
// Sets the CD audio volume (0 - 255).
//
// Returns: 0 (ok) or -1 (error, in cd_Error).
//
//==========================================================================

int I_CDMusSetVolume(int volume)
{

    if (!OkInit)
    {
        cd_Error = CDERR_NOTINSTALLED;
        return -1;
    }

    // Read current channel info
    InputIOCTL(AUDIOCHANINFO, cd_AudChanInfoSeg);

    // Change the volumes
    cd_AudChanInfo->volumeOut0 =
        cd_AudChanInfo->volumeOut1 =
        cd_AudChanInfo->volumeOut2 = cd_AudChanInfo->volumeOut3 = volume;

    // Write modified channel info
    OutputIOCTL(AUDIOCHANCONTROL, cd_AudChanInfoSeg);

    return 0;
}

//==========================================================================
//
// I_CDMusFirstTrack
//
// Returns: the number of the first track.
//
//==========================================================================

int I_CDMusFirstTrack(void)
{
    return cd_FirstTrack;
}

//==========================================================================
//
// I_CDMusLastTrack
//
// Returns: the number of the last track.
//
//==========================================================================

int I_CDMusLastTrack(void)
{
    return cd_LastTrack;
}

//==========================================================================
//
// I_CDMusTrackLength
//
// Returns: Length of the given track in seconds, or -1 (error, in
// cd_Error).
//
//==========================================================================

int I_CDMusTrackLength(int track)
{
    if (track < cd_FirstTrack || track > cd_LastTrack)
    {
        cd_Error = CDERR_BADTRACK;
        return -1;
    }
    return cd_AudioTracks[track].lengthMin * 60
        + cd_AudioTracks[track].lengthSec;
}

//==========================================================================
//
// AllocIOCTLBuffers
//
// Allocates conventional memory for the IOCTL input and output buffers.
// Sets cd_IOCTLBufferTotal to the total allocated.
//
// Returns: 0 (ok) or -1 (error, in cd_Error).
//
//==========================================================================

static int AllocIOCTLBuffers(void)
{
    int i;
    int size;
    DOSChunk_t *ck;

    cd_IOCTLBufferTotal = 0;
    for (i = 0; DOSChunks[i].size != 0; i++)
    {
        ck = &DOSChunks[i];
        size = ck->size;
        cd_IOCTLBufferTotal += (size + 15) & 0xfffffff0;
        *ck->address = DPMI_AllocRealMem(size, ck->segment, ck->selector);
        if (*ck->address == NULL)
        {
            return -1;
        }
        memset(*ck->address, 0, size);
    }
    cd_IOCTLIn->headerSize = sizeof(struct IOCTLIn_s);
    cd_IOCTLIn->command = DRC_IOCTLINPUT;
    cd_IOCTLOut->headerSize = sizeof(struct IOCTLOut_s);
    cd_IOCTLOut->command = DRC_IOCTLOUTPUT;
    cd_PlayReq->headerSize = sizeof(struct PlayReq_s);
    cd_PlayReq->command = DRC_PLAYAUDIO;
    cd_StopReq->headerSize = sizeof(struct StopReq_s);
    cd_StopReq->command = DRC_STOPAUDIO;
    cd_ResumeReq->headerSize = sizeof(struct ResumeReq_s);
    cd_ResumeReq->command = DRC_RESUMEAUDIO;
    return 0;
}

//==========================================================================
//
// InputIOCTL
//
// Sends an IOCTL input device request command.
//
// Returns: the status of the request.
//
//==========================================================================

static U_WORD InputIOCTL(S_LONG request, U_WORD buffSeg)
{
    U_BYTE *code;

    code = (U_BYTE *) (buffSeg << 4);
    *code = (U_BYTE) request;
    cd_IOCTLIn->ctrlBlkAddr = buffSeg << 16;
    cd_IOCTLIn->tranSize = InCtrlBlkSize[request];
    memset(&RegBlock, 0, sizeof(RegBlock));
    RegBlock.eax = CDROM_SENDDEVICEREQ;
    RegBlock.ecx = cd_CurDrive;
    RegBlock.ebx = 0;
    RegBlock.es = cd_IOCTLInSeg;
    DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
    return cd_IOCTLIn->status;
}

//==========================================================================
//
// OutputIOCTL
//
// Sends an IOCTL output device request command.
//
// Returns: the status of the request.
//
//==========================================================================

static U_WORD OutputIOCTL(S_LONG request, U_WORD buffSeg)
{
    U_BYTE *code;

    code = (U_BYTE *) (buffSeg << 4);
    *code = (U_BYTE) request;
    cd_IOCTLOut->ctrlBlkAddr = buffSeg << 16;
    cd_IOCTLOut->tranSize = OutCtrlBlkSize[request];
    RegBlock.eax = CDROM_SENDDEVICEREQ;
    RegBlock.ecx = cd_CurDrive;
    RegBlock.ebx = 0;
    RegBlock.es = cd_IOCTLOutSeg;
    DPMI_SimRealInt(MULTIPLEX_INT, &RegBlock);
    return cd_IOCTLOut->status;
}

//==========================================================================
//
// RedToSectors
//
// Converts Red Book addresses to HSG sectors.
// Sectors = Minutes * 60 * 75 + Seconds * 75 + Frame - 150
//
// Returns: HSG sectors.
//
//==========================================================================

static U_LONG RedToSectors(U_LONG red)
{
    U_LONG sector;

    sector = ((red & 0x00ff0000) >> 16) * 60 * 75;
    sector += ((red & 0x0000ff00) >> 8) * 75;
    sector += (red & 0x000000ff);
    return sector - 150;
}

//==========================================================================
//
// DPMI_SimRealInt
//
//==========================================================================

static void DPMI_SimRealInt(U_LONG intr, RegBlock_t * rBlock)
{
    union REGS regs;
    struct SREGS sRegs;

    regs.x.eax = DPMI_SIMREALINT;
    regs.x.ebx = intr;
    regs.x.ecx = 0;
    regs.x.edi = FP_OFF((void far *) rBlock);
    sRegs.es = FP_SEG((void far *) rBlock);
    sRegs.ds = FP_SEG((void far *) rBlock);
    int386x(DPMI_INT, &regs, &regs, &sRegs);
}

//==========================================================================
//
// DPMI_AllocRealMem
//
//==========================================================================

static void *DPMI_AllocRealMem(U_LONG size, U_WORD * segment,
                               U_WORD * selector)
{
    union REGS inRegs;
    union REGS outRegs;

    inRegs.x.eax = DPMI_ALLOCREALMEM;
    inRegs.x.ebx = (size + 15) / 16;
    int386(DPMI_INT, &inRegs, &outRegs);
    if (outRegs.x.cflag)
    {
        return NULL;
    }
    *segment = outRegs.x.eax & 0xffff;
    *selector = outRegs.x.edx & 0xffff;
    return (void *) (outRegs.x.eax << 4);
}

//==========================================================================
//
// DPMI_FreeRealMem
//
//==========================================================================

static void DPMI_FreeRealMem(U_WORD selector)
{
    union REGS regs;

    regs.x.eax = DPMI_FREEREALMEM;
    regs.x.edx = selector;
    int386(DPMI_INT, &regs, &regs);
}